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.service.impl;
017    
018    import java.io.BufferedInputStream;
019    import java.io.BufferedWriter;
020    import java.io.ByteArrayOutputStream;
021    import java.io.File;
022    import java.io.FileFilter;
023    import java.io.FileInputStream;
024    import java.io.FileNotFoundException;
025    import java.io.IOException;
026    import java.io.PrintWriter;
027    import java.math.BigDecimal;
028    import java.text.MessageFormat;
029    import java.util.ArrayList;
030    import java.util.Collection;
031    import java.util.HashMap;
032    import java.util.Iterator;
033    import java.util.List;
034    import java.util.Map;
035    
036    import javax.xml.parsers.DocumentBuilder;
037    import javax.xml.parsers.DocumentBuilderFactory;
038    import javax.xml.parsers.ParserConfigurationException;
039    
040    import org.apache.commons.io.FileUtils;
041    import org.apache.commons.io.FilenameUtils;
042    import org.apache.commons.lang.ArrayUtils;
043    import org.apache.commons.lang.StringUtils;
044    import org.apache.commons.lang.math.NumberUtils;
045    import org.apache.xml.serialize.OutputFormat;
046    import org.apache.xml.serialize.XMLSerializer;
047    import org.kuali.kfs.module.purap.PurapConstants;
048    import org.kuali.kfs.module.purap.PurapConstants.PurchaseOrderStatuses;
049    import org.kuali.kfs.module.purap.PurapKeyConstants;
050    import org.kuali.kfs.module.purap.PurapParameterConstants;
051    import org.kuali.kfs.module.purap.batch.ElectronicInvoiceInputFileType;
052    import org.kuali.kfs.module.purap.batch.ElectronicInvoiceStep;
053    import org.kuali.kfs.module.purap.businessobject.ElectronicInvoice;
054    import org.kuali.kfs.module.purap.businessobject.ElectronicInvoiceItem;
055    import org.kuali.kfs.module.purap.businessobject.ElectronicInvoiceLoad;
056    import org.kuali.kfs.module.purap.businessobject.ElectronicInvoiceLoadSummary;
057    import org.kuali.kfs.module.purap.businessobject.ElectronicInvoiceOrder;
058    import org.kuali.kfs.module.purap.businessobject.ElectronicInvoiceRejectReason;
059    import org.kuali.kfs.module.purap.businessobject.ElectronicInvoiceRejectReasonType;
060    import org.kuali.kfs.module.purap.businessobject.ItemType;
061    import org.kuali.kfs.module.purap.businessobject.PaymentRequestItem;
062    import org.kuali.kfs.module.purap.businessobject.PurApItem;
063    import org.kuali.kfs.module.purap.dataaccess.ElectronicInvoicingDao;
064    import org.kuali.kfs.module.purap.document.ElectronicInvoiceRejectDocument;
065    import org.kuali.kfs.module.purap.document.PaymentRequestDocument;
066    import org.kuali.kfs.module.purap.document.PurchaseOrderDocument;
067    import org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument;
068    import org.kuali.kfs.module.purap.document.RequisitionDocument;
069    import org.kuali.kfs.module.purap.document.service.AccountsPayableService;
070    import org.kuali.kfs.module.purap.document.service.PaymentRequestService;
071    import org.kuali.kfs.module.purap.document.service.PurchaseOrderService;
072    import org.kuali.kfs.module.purap.document.service.RequisitionService;
073    import org.kuali.kfs.module.purap.document.validation.event.AttributedCalculateAccountsPayableEvent;
074    import org.kuali.kfs.module.purap.document.validation.event.AttributedPaymentRequestForEInvoiceEvent;
075    import org.kuali.kfs.module.purap.exception.CxmlParseException;
076    import org.kuali.kfs.module.purap.exception.PurError;
077    import org.kuali.kfs.module.purap.service.ElectronicInvoiceHelperService;
078    import org.kuali.kfs.module.purap.service.ElectronicInvoiceMatchingService;
079    import org.kuali.kfs.module.purap.util.ElectronicInvoiceUtils;
080    import org.kuali.kfs.module.purap.util.ExpiredOrClosedAccountEntry;
081    import org.kuali.kfs.sys.KFSConstants;
082    import org.kuali.kfs.sys.batch.service.BatchInputFileService;
083    import org.kuali.kfs.sys.businessobject.Bank;
084    import org.kuali.kfs.sys.context.SpringContext;
085    import org.kuali.kfs.sys.exception.ParseException;
086    import org.kuali.kfs.sys.service.BankService;
087    import org.kuali.kfs.vnd.businessobject.VendorDetail;
088    import org.kuali.kfs.vnd.document.service.VendorService;
089    import org.kuali.rice.kew.exception.WorkflowException;
090    import org.kuali.rice.kim.bo.Person;
091    import org.kuali.rice.kns.bo.Attachment;
092    import org.kuali.rice.kns.bo.DocumentHeader;
093    import org.kuali.rice.kns.bo.Note;
094    import org.kuali.rice.kns.bo.PersistableBusinessObject;
095    import org.kuali.rice.kns.exception.ValidationException;
096    import org.kuali.rice.kns.mail.InvalidAddressException;
097    import org.kuali.rice.kns.mail.MailMessage;
098    import org.kuali.rice.kns.service.AttachmentService;
099    import org.kuali.rice.kns.service.BusinessObjectService;
100    import org.kuali.rice.kns.service.DataDictionaryService;
101    import org.kuali.rice.kns.service.DateTimeService;
102    import org.kuali.rice.kns.service.DocumentService;
103    import org.kuali.rice.kns.service.KualiConfigurationService;
104    import org.kuali.rice.kns.service.KualiRuleService;
105    import org.kuali.rice.kns.service.MailService;
106    import org.kuali.rice.kns.service.NoteService;
107    import org.kuali.rice.kns.service.ParameterService;
108    import org.kuali.rice.kns.util.ErrorMessage;
109    import org.kuali.rice.kns.util.GlobalVariables;
110    import org.kuali.rice.kns.util.KNSPropertyConstants;
111    import org.kuali.rice.kns.util.KualiDecimal;
112    import org.kuali.rice.kns.util.ObjectUtils;
113    import org.kuali.rice.kns.util.TypedArrayList;
114    import org.springframework.transaction.annotation.Transactional;
115    import org.w3c.dom.Document;
116    import org.w3c.dom.Element;
117    import org.w3c.dom.Node;
118    
119    /**
120     * This is a helper service to parse electronic invoice file, match it with a PO and create PREQs based on the eInvoice. Also, it 
121     * provides helper methods to the reject document to match it with a PO and create PREQ.
122     */
123    
124    @Transactional
125    public class ElectronicInvoiceHelperServiceImpl implements ElectronicInvoiceHelperService {
126        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ElectronicInvoiceHelperServiceImpl.class);
127    
128        protected final String UNKNOWN_DUNS_IDENTIFIER = "Unknown";
129        protected final String INVOICE_FILE_MIME_TYPE = "text/xml";  
130        
131        private StringBuffer emailTextErrorList;
132        
133        protected ElectronicInvoiceInputFileType electronicInvoiceInputFileType;
134        protected MailService mailService;
135        protected ElectronicInvoiceMatchingService matchingService; 
136        protected ElectronicInvoicingDao electronicInvoicingDao;
137        protected BatchInputFileService batchInputFileService;
138        protected VendorService vendorService;
139        protected PurchaseOrderService purchaseOrderService;
140        protected PaymentRequestService paymentRequestService;
141        protected KualiConfigurationService kualiConfigurationService;
142        protected DateTimeService dateTimeService;
143        protected ParameterService parameterService;
144        
145        public ElectronicInvoiceLoad loadElectronicInvoices() {
146    
147            String baseDirName = getBaseDirName();
148            String rejectDirName = getRejectDirName();
149            String acceptDirName = getAcceptDirName();
150            emailTextErrorList = new StringBuffer();
151    
152            boolean moveFiles = SpringContext.getBean(ParameterService.class).getIndicatorParameter(ElectronicInvoiceStep.class, PurapParameterConstants.ElectronicInvoiceParameters.FILE_MOVE_AFTER_LOAD_IND);
153    
154            int failedCnt = 0;
155    
156            if (LOG.isInfoEnabled()){
157                LOG.info("Invoice Base Directory - " + electronicInvoiceInputFileType.getDirectoryPath());
158                LOG.info("Invoice Accept Directory - " + acceptDirName);
159                LOG.info("Invoice Reject Directory - " + rejectDirName);
160                LOG.info("Is moving files allowed - " + moveFiles);
161            }
162    
163            if (StringUtils.isBlank(rejectDirName)) {
164                throw new RuntimeException("Reject directory name should not be empty");
165            }
166    
167            if (StringUtils.isBlank(acceptDirName)) {
168                throw new RuntimeException("Accept directory name should not be empty");
169            }
170    
171            File baseDir = new File(baseDirName);
172            File[] filesToBeProcessed = baseDir.listFiles(new FileFilter() {
173                                                                public boolean accept(File file) {
174                                                                    String fullPath = FilenameUtils.getFullPath(file.getAbsolutePath());
175                                                                    String fileName = FilenameUtils.getBaseName(file.getAbsolutePath());
176                                                                    File processedFile = new File(fullPath + File.separator + fileName + ".processed");
177                                                                    return (!file.isDirectory() && 
178                                                                            file.getName().endsWith(".xml") &&
179                                                                            !processedFile.exists());
180                                                                }
181                                                            });
182    
183            if (!baseDir.exists()){
184                throw new RuntimeException("Base dir [" + baseDirName + "] doesn't exists in the system");
185            }
186            
187            ElectronicInvoiceLoad eInvoiceLoad = new ElectronicInvoiceLoad();
188            
189            if (filesToBeProcessed == null || 
190                filesToBeProcessed.length == 0) {
191    
192                StringBuffer mailText = new StringBuffer();
193                
194                mailText.append("\n\n");
195                mailText.append(PurapConstants.ElectronicInvoice.NO_FILES_PROCESSED_EMAIL_MESSAGE);
196                mailText.append("\n\n");
197                
198                sendSummary(mailText);
199                return eInvoiceLoad;
200            }
201    
202            try {
203                /**
204                 * Create, if not there
205                 */
206                FileUtils.forceMkdir(new File(acceptDirName));
207                FileUtils.forceMkdir(new File(rejectDirName));
208            }catch (IOException e) {
209                throw new RuntimeException(e);
210            }
211            
212            if (LOG.isInfoEnabled()){
213                LOG.info(filesToBeProcessed.length + " file(s) available for processing");
214            }
215    
216            StringBuilder emailMsg = new StringBuilder();
217    
218            for (int i = 0; i < filesToBeProcessed.length; i++) {
219    
220                LOG.info("Processing " + filesToBeProcessed[i].getName() + "....");
221    
222                byte[] modifiedXML = addNamespaceDefinition(eInvoiceLoad, filesToBeProcessed[i]);
223                
224                boolean isRejected = false;
225                
226                if (modifiedXML == null){//Not able to parse the xml
227                    isRejected = true;
228                } else {
229                    try {
230                        isRejected = processElectronicInvoice(eInvoiceLoad, filesToBeProcessed[i], modifiedXML);
231                    } catch (Exception e) {
232                        String msg = filesToBeProcessed[i].getName() + "\n";
233                        LOG.error(msg);
234    
235                        //since getMessage() is empty we'll compose the stack trace and nicely format it.
236                        StackTraceElement[] elements = e.getStackTrace();
237                        StringBuffer trace = new StringBuffer();
238                        trace.append(e.getClass().getName());
239                        if (e.getMessage() != null) {
240                            trace.append(": ");
241                            trace.append(e.getMessage());
242                        }
243                        trace.append("\n");
244                        for (int j = 0; j < elements.length; ++j) {
245                            StackTraceElement element = elements[j];
246    
247                            trace.append("    at ");
248                            trace.append(describeStackTraceElement(element));
249                            trace.append("\n");
250                        }
251                        
252                        LOG.error(trace);
253                        emailMsg.append(msg);
254                        msg += "\n--------------------------------------------------------------------------------------\n" + trace;
255                        logProcessElectronicInvoiceError(msg);
256                        failedCnt++;
257                        //Do not execute rest of code below
258                        continue;
259                    }
260                }
261                
262                /**
263                 * If there is a single order has rejects and the remainings are accepted in a invoice file, 
264                 * then the entire file has been moved to the reject dir. 
265                 */
266                if (isRejected) {
267                    if (LOG.isInfoEnabled()){
268                        LOG.info(filesToBeProcessed[i].getName() + " has been rejected");
269                    }
270                    if (moveFiles) {
271                        if (LOG.isInfoEnabled()){
272                            LOG.info(filesToBeProcessed[i].getName() + " has been marked to move to " + rejectDirName);
273                        }
274                        eInvoiceLoad.addRejectFileToMove(filesToBeProcessed[i], rejectDirName);
275                    }
276                } else {
277                    if (LOG.isInfoEnabled()){
278                        LOG.info(filesToBeProcessed[i].getName() + " has been accepted");
279                    }
280                    if (moveFiles) {
281                        if (!moveFile(filesToBeProcessed[i], acceptDirName)) {
282                            String msg = filesToBeProcessed[i].getName() + " unable to move";
283                            LOG.error(msg);
284                            throw new PurError(msg);
285                        }
286                    }
287                }
288                
289                if (!moveFiles){
290                    String fullPath = FilenameUtils.getFullPath(filesToBeProcessed[i].getAbsolutePath());
291                    String fileName = FilenameUtils.getBaseName(filesToBeProcessed[i].getAbsolutePath());
292                    File processedFile = new File(fullPath + File.separator + fileName + ".processed");
293                    try {
294                        FileUtils.touch(processedFile);
295                    }
296                    catch (IOException e) {
297                        throw new RuntimeException(e);
298                    }
299                }
300                
301                //  delete the .done file
302                deleteDoneFile(filesToBeProcessed[i]);
303            }
304    
305            emailTextErrorList.append("\nFAILED FILES\n");
306            emailTextErrorList.append("-----------------------------------------------------------\n\n");
307            emailTextErrorList.append(emailMsg);
308            emailTextErrorList.append("\nTOTAL COUNT\n");
309            emailTextErrorList.append("===========================\n");
310            emailTextErrorList.append("      "+failedCnt+" FAILED\n");
311            emailTextErrorList.append("===========================\n");
312    
313             StringBuffer summaryText = saveLoadSummary(eInvoiceLoad);
314    
315             StringBuffer finalText = new StringBuffer();
316             finalText.append(summaryText);
317             finalText.append("\n");
318             finalText.append(emailTextErrorList);
319             sendSummary(finalText);
320    
321             LOG.info("Processing completed");
322    
323             return eInvoiceLoad;
324    
325        }
326    
327        protected void logProcessElectronicInvoiceError(String msg) {
328            File file = new File(electronicInvoiceInputFileType.getReportPath() + "/" + 
329                    electronicInvoiceInputFileType.getReportPrefix() + "_" + 
330                    dateTimeService.toDateTimeStringForFilename(dateTimeService.getCurrentDate()) + "." + 
331                    electronicInvoiceInputFileType.getReportExtension());
332            BufferedWriter writer = null;
333            
334            try {
335                writer = new BufferedWriter(new PrintWriter(file));
336                writer.write(msg);
337                writer.newLine();
338            }
339            catch (FileNotFoundException e) {
340                LOG.error(file + " not found " + " " + e.getMessage());
341                throw new RuntimeException(file + " not found " + e.getMessage(), e);
342            }
343            catch (IOException e) {
344                LOG.error("Error writing to BufferedWriter " + e.getMessage());
345                throw new RuntimeException("Error writing to BufferedWriter " + e.getMessage(), e);
346            }
347            finally {
348                try {
349                    writer.flush();
350                    writer.close();
351                }
352                catch (Exception e) {}
353            }
354        }
355    
356        /**
357         * @param element
358         * @return String describing the given StackTraceElement
359         */
360        private static String describeStackTraceElement(StackTraceElement element) {
361            StringBuffer description = new StringBuffer();
362            if (element == null) {
363                description.append("invalid (null) element");
364            }
365            description.append(element.getClassName());
366            description.append(".");
367            description.append(element.getMethodName());
368            description.append("(");
369            description.append(element.getFileName());
370            description.append(":");
371            description.append(element.getLineNumber());
372            description.append(")");
373    
374            return description.toString();
375        }
376    
377        protected byte[] addNamespaceDefinition(ElectronicInvoiceLoad eInvoiceLoad, 
378                                              File invoiceFile) {
379            
380            
381            boolean result = true;
382            
383            if (LOG.isInfoEnabled()){
384                LOG.info("Adding namespace definition");
385            }
386            
387            DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
388            builderFactory.setValidating(false); // It's not needed to validate here
389            builderFactory.setIgnoringElementContentWhitespace(true); 
390            
391            DocumentBuilder builder = null;
392            try {
393              builder = builderFactory.newDocumentBuilder();  // Create the parser
394            } catch(ParserConfigurationException e) {
395                LOG.error("Error getting document builder - " + e.getMessage());
396                throw new RuntimeException(e);
397            }
398            
399            Document xmlDoc = null;
400    
401            try {
402                xmlDoc = builder.parse(invoiceFile);
403            } catch(Exception e) {
404                if (LOG.isInfoEnabled()){
405                    LOG.info("Error parsing the file - " + e.getMessage());
406                }
407                rejectElectronicInvoiceFile(eInvoiceLoad, UNKNOWN_DUNS_IDENTIFIER, invoiceFile, e.getMessage(),PurapConstants.ElectronicInvoice.FILE_FORMAT_INVALID);
408                return null;
409            }
410            
411            Node node = xmlDoc.getDocumentElement();
412            Element element = (Element)node;
413    
414            String xmlnsValue = element.getAttribute("xmlns");
415            String xmlnsXsiValue = element.getAttribute("xmlns:xsi");
416            
417            File namespaceAddedFile = getInvoiceFile(invoiceFile.getName());
418            
419            if (StringUtils.equals(xmlnsValue, "http://www.kuali.org/kfs/purap/electronicInvoice") && 
420                StringUtils.equals(xmlnsXsiValue, "http://www.w3.org/2001/XMLSchema-instance")){
421                if (LOG.isInfoEnabled()){
422                    LOG.info("xmlns and xmlns:xsi attributes already exists in the invoice xml");
423                }
424            }else{
425                element.setAttribute("xmlns", "http://www.kuali.org/kfs/purap/electronicInvoice");
426                element.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
427            }
428            
429            OutputFormat outputFormat = new OutputFormat(xmlDoc);
430            outputFormat.setOmitDocumentType(true);
431            
432            ByteArrayOutputStream out = new ByteArrayOutputStream();
433            XMLSerializer serializer = new XMLSerializer( out,outputFormat );
434            try {
435                serializer.asDOMSerializer();
436                serializer.serialize( xmlDoc.getDocumentElement());
437            }
438            catch (IOException e) {
439                throw new RuntimeException(e);
440            }
441            
442            if (LOG.isInfoEnabled()){
443                LOG.info("Namespace validation completed");
444            }
445    
446            return out.toByteArray();
447    
448        }
449        
450        /**
451         * This method processes a single electronic invoice file
452         * 
453         * @param eInvoiceLoad the load summary to be modified
454         * @return boolean where true means there has been some type of reject
455         */
456        protected boolean processElectronicInvoice(ElectronicInvoiceLoad eInvoiceLoad, 
457                                                 File invoiceFile,
458                                                 byte[] xmlAsBytes) {
459    
460            ElectronicInvoice eInvoice = null;
461    
462            try {
463                eInvoice = loadElectronicInvoice(xmlAsBytes);
464            }catch (CxmlParseException e) {
465                LOG.info("Error loading file - " + e.getMessage());
466                rejectElectronicInvoiceFile(eInvoiceLoad, UNKNOWN_DUNS_IDENTIFIER, invoiceFile, e.getMessage(),PurapConstants.ElectronicInvoice.FILE_FORMAT_INVALID);
467                return true;
468            }
469    
470            eInvoice.setFileName(invoiceFile.getName());
471            
472            boolean isCompleteFailure = checkForCompleteFailure(eInvoiceLoad,eInvoice,invoiceFile); 
473            
474            if (isCompleteFailure){
475                return true;
476            }
477    
478            setVendorDUNSNumber(eInvoice);
479            setVendorDetails(eInvoice);
480            
481            Map itemTypeMappings = getItemTypeMappings(eInvoice.getVendorHeaderID(),eInvoice.getVendorDetailID());
482            Map kualiItemTypes = getKualiItemTypes();
483            
484            if (LOG.isInfoEnabled()){
485                if (itemTypeMappings != null && itemTypeMappings.size() > 0){
486                    LOG.info("Item mappings found");
487                }
488            }
489            
490            boolean validateHeader = true;
491            
492            for (ElectronicInvoiceOrder order : eInvoice.getInvoiceDetailOrders()) {
493    
494                String poID = order.getOrderReferenceOrderID();
495                PurchaseOrderDocument po = null;
496                
497                if (NumberUtils.isDigits(StringUtils.defaultString(poID))){
498                    po = purchaseOrderService.getCurrentPurchaseOrder(new Integer(poID));    
499                    if (po != null){
500                        order.setInvoicePurchaseOrderID(poID);
501                        order.setPurchaseOrderID(po.getPurapDocumentIdentifier());
502                        order.setPurchaseOrderCampusCode(po.getDeliveryCampusCode());
503                        
504                        if (LOG.isInfoEnabled()){
505                            LOG.info("PO matching Document found");
506                        }
507                    }
508                }
509                
510                ElectronicInvoiceOrderHolder orderHolder = new ElectronicInvoiceOrderHolder(eInvoice,order,po,itemTypeMappings,kualiItemTypes,validateHeader);
511                matchingService.doMatchingProcess(orderHolder);
512                
513                if (orderHolder.isInvoiceRejected()){
514                    
515                    ElectronicInvoiceRejectDocument rejectDocument = createRejectDocument(eInvoice, order,eInvoiceLoad);
516                    
517                    if (orderHolder.getAccountsPayablePurchasingDocumentLinkIdentifier() != null){
518                        rejectDocument.setAccountsPayablePurchasingDocumentLinkIdentifier(orderHolder.getAccountsPayablePurchasingDocumentLinkIdentifier());
519                    }
520                    
521                    String dunsNumber = StringUtils.isEmpty(eInvoice.getDunsNumber()) ?
522                                        UNKNOWN_DUNS_IDENTIFIER :
523                                        eInvoice.getDunsNumber();
524                    
525                    ElectronicInvoiceLoadSummary loadSummary = getOrCreateLoadSummary(eInvoiceLoad, dunsNumber);
526                    loadSummary.addFailedInvoiceOrder(rejectDocument.getTotalAmount(),eInvoice);
527                    eInvoiceLoad.insertInvoiceLoadSummary(loadSummary);
528                    
529                }else{
530                    
531                    PaymentRequestDocument preqDoc  = createPaymentRequest(orderHolder);
532                    
533                    if (orderHolder.isInvoiceRejected()){
534                        /**
535                         * This is required. If there is anything in the error map, then it's not possible to route the doc since the rice
536                         * is throwing error if errormap is not empty before routing the doc. 
537                         */
538                        GlobalVariables.getMessageMap().clear();
539                        
540                        ElectronicInvoiceRejectDocument rejectDocument = createRejectDocument(eInvoice, order,eInvoiceLoad);
541                        
542                        if (orderHolder.getAccountsPayablePurchasingDocumentLinkIdentifier() != null){
543                            rejectDocument.setAccountsPayablePurchasingDocumentLinkIdentifier(orderHolder.getAccountsPayablePurchasingDocumentLinkIdentifier());
544                        }
545                        
546                        ElectronicInvoiceLoadSummary loadSummary = getOrCreateLoadSummary(eInvoiceLoad, eInvoice.getDunsNumber());
547                        loadSummary.addFailedInvoiceOrder(rejectDocument.getTotalAmount(),eInvoice);
548                        eInvoiceLoad.insertInvoiceLoadSummary(loadSummary);
549                        
550                    }else{
551                        ElectronicInvoiceLoadSummary loadSummary = getOrCreateLoadSummary(eInvoiceLoad, eInvoice.getDunsNumber());
552                        loadSummary.addSuccessfulInvoiceOrder(preqDoc.getTotalDollarAmount(),eInvoice);
553                        eInvoiceLoad.insertInvoiceLoadSummary(loadSummary);
554                    }
555                    
556                }
557                
558                validateHeader = false;
559            }
560            
561            return eInvoice.isFileRejected();
562        }
563        
564        protected void setVendorDUNSNumber(ElectronicInvoice eInvoice) {
565            
566            String dunsNumber = null;
567            
568            if (StringUtils.equals(eInvoice.getCxmlHeader().getFromDomain(),"DUNS")) {
569                dunsNumber = eInvoice.getCxmlHeader().getFromIdentity();
570            }else if (StringUtils.equals(eInvoice.getCxmlHeader().getSenderDomain(),"DUNS")) {
571                dunsNumber = eInvoice.getCxmlHeader().getSenderIdentity();
572            }
573            
574            if (StringUtils.isNotEmpty((dunsNumber))) {
575                if (LOG.isInfoEnabled()){
576                    LOG.info("Setting Vendor DUNS number - " + dunsNumber);
577                }
578                eInvoice.setDunsNumber(dunsNumber);
579            }
580            
581        }
582        
583        protected void setVendorDetails(ElectronicInvoice eInvoice){
584            
585            if (StringUtils.isNotEmpty(eInvoice.getDunsNumber())){
586                
587                VendorDetail vendorDetail = vendorService.getVendorByDunsNumber(eInvoice.getDunsNumber());
588                
589                if (vendorDetail != null) {
590                    if (LOG.isInfoEnabled()){
591                        LOG.info("Vendor match found - " + vendorDetail.getVendorNumber());
592                    }
593                    eInvoice.setVendorHeaderID(vendorDetail.getVendorHeaderGeneratedIdentifier());
594                    eInvoice.setVendorDetailID(vendorDetail.getVendorDetailAssignedIdentifier());
595                    eInvoice.setVendorName(vendorDetail.getVendorName());
596                }else{
597                    eInvoice.setVendorHeaderID(null);
598                    eInvoice.setVendorDetailID(null);
599                    eInvoice.setVendorName(null);
600                }
601            }
602        }
603        
604        protected void validateVendorDetails(ElectronicInvoiceRejectDocument rejectDocument){
605            
606            boolean vendorFound = false;
607            
608            if (StringUtils.isNotEmpty(rejectDocument.getVendorDunsNumber())){
609                
610                VendorDetail vendorDetail = vendorService.getVendorByDunsNumber(rejectDocument.getVendorDunsNumber());
611                
612                if (vendorDetail != null) {
613                    if (LOG.isInfoEnabled()){
614                        LOG.info("Vendor [" + vendorDetail.getVendorNumber() + "] match found for the DUNS - " + rejectDocument.getVendorDunsNumber());
615                    }
616                    rejectDocument.setVendorHeaderGeneratedIdentifier(vendorDetail.getVendorHeaderGeneratedIdentifier());
617                    rejectDocument.setVendorDetailAssignedIdentifier(vendorDetail.getVendorDetailAssignedIdentifier());
618                    rejectDocument.setVendorDetail(vendorDetail);
619                    vendorFound = true;
620                }
621            }
622            
623            if (!vendorFound){
624                rejectDocument.setVendorHeaderGeneratedIdentifier(null);
625                rejectDocument.setVendorDetailAssignedIdentifier(null);
626                rejectDocument.setVendorDetail(null);
627            }
628            
629            String newDocumentDesc = generateRejectDocumentDescription(rejectDocument); 
630            rejectDocument.getDocumentHeader().setDocumentDescription(newDocumentDesc);
631        }
632        
633        protected Map getItemTypeMappings(Integer vendorHeaderId,
634                                        Integer vendorDetailId) {
635    
636            Map itemTypeMappings = null;
637    
638            if (vendorHeaderId != null && vendorDetailId != null) {
639                   String vendorNumber = getVendorNumber(vendorHeaderId,vendorDetailId);
640                   itemTypeMappings = electronicInvoicingDao.getItemMappingMap(vendorHeaderId,vendorDetailId);
641            }
642    
643            if (itemTypeMappings == null || itemTypeMappings.isEmpty()){
644                itemTypeMappings = electronicInvoicingDao.getDefaultItemMappingMap();
645            }
646    
647            return itemTypeMappings;
648        }
649        
650        protected String getVendorNumber(Integer vendorHeaderId,
651                                       Integer vendorDetailId ){
652            
653            if (vendorHeaderId != null && vendorDetailId != null) {
654                VendorDetail forVendorNo = new VendorDetail();
655                forVendorNo.setVendorHeaderGeneratedIdentifier(vendorHeaderId);
656                forVendorNo.setVendorDetailAssignedIdentifier(vendorDetailId);
657                return forVendorNo.getVendorNumber();
658            }else{
659                return null;
660            }
661        }
662        
663        protected Map<String, ItemType> getKualiItemTypes(){
664            
665            Collection<ItemType> collection = SpringContext.getBean(BusinessObjectService.class).findAll(ItemType.class);
666            Map kualiItemTypes = new HashMap<String, ItemType>();
667            
668            if (collection == null || collection.size() == 0){
669                throw new RuntimeException("Kauli Item types not available");
670            }else{
671                if (collection != null){
672                    ItemType[] itemTypes = new ItemType[collection.size()];
673                    collection.toArray(itemTypes);
674                    for (int i = 0; i < itemTypes.length; i++) {
675                        kualiItemTypes.put(itemTypes[i].getItemTypeCode(),itemTypes[i]);
676                    }
677                }
678            }
679            
680            return kualiItemTypes;
681        }
682        
683        protected boolean checkForCompleteFailure(ElectronicInvoiceLoad electronicInvoiceLoad, 
684                                                ElectronicInvoice electronicInvoice,
685                                                File invoiceFile){
686            
687            if (LOG.isInfoEnabled()){
688                LOG.info("Checking for complete failure...");
689            }
690            
691            if (electronicInvoice.getInvoiceDetailRequestHeader().isHeaderInvoiceIndicator()) {
692                rejectElectronicInvoiceFile(electronicInvoiceLoad, UNKNOWN_DUNS_IDENTIFIER, invoiceFile,PurapConstants.ElectronicInvoice.HEADER_INVOICE_IND_ON);
693                return true;
694            }
695            
696            if (electronicInvoice.getInvoiceDetailOrders().size() < 1) {
697                rejectElectronicInvoiceFile(electronicInvoiceLoad, UNKNOWN_DUNS_IDENTIFIER, invoiceFile,PurapConstants.ElectronicInvoice.INVOICE_ORDERS_NOT_FOUND);
698                return true;
699            }
700            
701            //it says - Future Release - Enter valid location for Customer Number from E-Invoice
702            //mappingService.getInvoiceCustomerNumber() doesnt have any implementation
703    //        electronicInvoice.setCustomerNumber(mappingService.getInvoiceCustomerNumber(electronicInvoice));
704            
705            for (Iterator orderIter = electronicInvoice.getInvoiceDetailOrders().iterator(); orderIter.hasNext();) {
706              ElectronicInvoiceOrder invoiceOrder = (ElectronicInvoiceOrder) orderIter.next();
707              for (Iterator itemIter = invoiceOrder.getInvoiceItems().iterator(); itemIter.hasNext();) {
708                ElectronicInvoiceItem invoiceItem = (ElectronicInvoiceItem) itemIter.next();
709                if (invoiceItem != null) {
710                  invoiceItem.setCatalogNumber(invoiceItem.getReferenceItemIDSupplierPartID());
711                }
712              }
713            }
714            
715            if (LOG.isInfoEnabled()){
716                LOG.info("No Complete failure");
717            }
718            
719            return false;
720            
721        }
722        
723        protected ElectronicInvoiceRejectReasonType getRejectReasonType(String rejectReasonTypeCode){
724            return matchingService.getElectronicInvoiceRejectReasonType(rejectReasonTypeCode);
725        }
726        
727        protected void rejectElectronicInvoiceFile(ElectronicInvoiceLoad eInvoiceLoad, 
728                                                 String fileDunsNumber, 
729                                                 File filename, 
730                                                 String rejectReasonTypeCode) {
731    
732            rejectElectronicInvoiceFile(eInvoiceLoad,fileDunsNumber,filename,null,rejectReasonTypeCode);
733        }
734        
735        protected void rejectElectronicInvoiceFile(ElectronicInvoiceLoad eInvoiceLoad, 
736                                                 String fileDunsNumber, 
737                                                 File invoiceFile, 
738                                                 String extraDescription,
739                                                 String rejectReasonTypeCode) {
740            if (LOG.isInfoEnabled()){
741                LOG.info("Rejecting the entire invoice file - " + invoiceFile.getName());
742            }
743            
744            ElectronicInvoiceLoadSummary eInvoiceLoadSummary = getOrCreateLoadSummary(eInvoiceLoad, fileDunsNumber);
745            eInvoiceLoadSummary.addFailedInvoiceOrder();
746            eInvoiceLoad.insertInvoiceLoadSummary(eInvoiceLoadSummary);
747            
748            ElectronicInvoiceRejectDocument eInvoiceRejectDocument = null;
749            try {
750                eInvoiceRejectDocument = (ElectronicInvoiceRejectDocument) SpringContext.getBean(DocumentService.class).getNewDocument("EIRT");
751                
752                eInvoiceRejectDocument.setInvoiceProcessTimestamp(SpringContext.getBean(DateTimeService.class).getCurrentTimestamp());
753                eInvoiceRejectDocument.setVendorDunsNumber(fileDunsNumber);
754                eInvoiceRejectDocument.setDocumentCreationInProgress(true);
755                
756                if (invoiceFile != null){
757                    eInvoiceRejectDocument.setInvoiceFileName(invoiceFile.getName());
758                }
759                
760                List<ElectronicInvoiceRejectReason> list = new ArrayList<ElectronicInvoiceRejectReason>(1);
761                
762                String message = "Complete failure document has been created for the Invoice with Filename '" + invoiceFile.getName() + "' due to the following error:\n";
763                emailTextErrorList.append(message);
764                
765                ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(rejectReasonTypeCode,extraDescription, invoiceFile.getName());
766                list.add(rejectReason);
767                
768                emailTextErrorList.append("    - " + rejectReason.getInvoiceRejectReasonDescription());
769                emailTextErrorList.append("\n\n");
770                
771                eInvoiceRejectDocument.setInvoiceRejectReasons(list);
772                eInvoiceRejectDocument.getDocumentHeader().setDocumentDescription("Complete failure");
773                
774                String noteText = "Invoice file";
775                attachInvoiceXMLWithRejectDoc(eInvoiceRejectDocument,invoiceFile,noteText);
776                
777                eInvoiceLoad.addInvoiceReject(eInvoiceRejectDocument);
778                
779            }catch (WorkflowException e) {
780                throw new RuntimeException(e);
781            }
782    
783            if (LOG.isInfoEnabled()){
784                LOG.info("Complete failure document has been created (DocNo:" + eInvoiceRejectDocument.getDocumentNumber() + ")");
785            }
786        }
787        
788        protected void attachInvoiceXMLWithRejectDoc(ElectronicInvoiceRejectDocument eInvoiceRejectDocument,
789                                                   File attachmentFile,
790                                                   String noteText){
791            
792            Note note;
793            try {
794                note = SpringContext.getBean(DocumentService.class).createNoteFromDocument(eInvoiceRejectDocument, noteText);
795            }catch (Exception e1) {
796                throw new RuntimeException(e1);
797            }
798            
799            String attachmentType = null;
800            BufferedInputStream fileStream = null;
801            try {
802                fileStream = new BufferedInputStream(new FileInputStream(attachmentFile));
803            }catch (FileNotFoundException e) {
804                e.printStackTrace();
805            }
806            
807            Attachment attachment = null;
808            try {
809                attachment = SpringContext.getBean(AttachmentService.class).createAttachment(eInvoiceRejectDocument, attachmentFile.getName(),INVOICE_FILE_MIME_TYPE , (int)attachmentFile.length(), fileStream, attachmentType);
810            }
811            catch (IOException e) {
812                throw new RuntimeException(e);
813            }finally{
814                if (fileStream != null){
815                    try {
816                        fileStream.close();
817                    }catch (IOException e) {
818                        e.printStackTrace();
819                    }
820                }
821            }
822            
823            note.setAttachment(attachment);
824            attachment.setNote(note);
825            
826            PersistableBusinessObject noteParent = getNoteParent(eInvoiceRejectDocument, note);
827            noteParent.addNote(note);
828            //eInvoiceRejectDocument.getDocumentHeader().addNote(note);
829        }
830        
831        protected PersistableBusinessObject getNoteParent(ElectronicInvoiceRejectDocument document, Note newNote) {
832            //get the property name to set (this assumes this is a document type note)
833            String propertyName = SpringContext.getBean(NoteService.class).extractNoteProperty(newNote);
834            //get BO to set
835            PersistableBusinessObject noteParent = (PersistableBusinessObject)ObjectUtils.getPropertyValue(document, propertyName);
836            return noteParent;
837        }
838        
839        public ElectronicInvoiceRejectDocument createRejectDocument(ElectronicInvoice eInvoice,
840                                                                    ElectronicInvoiceOrder electronicInvoiceOrder,
841                                                                    ElectronicInvoiceLoad eInvoiceLoad) {
842    
843            if (LOG.isInfoEnabled()){
844                LOG.info("Creating reject document [DUNS=" + eInvoice.getDunsNumber() + ",POID=" + electronicInvoiceOrder.getInvoicePurchaseOrderID() + "]");
845            }
846    
847            ElectronicInvoiceRejectDocument eInvoiceRejectDocument;
848            
849            try {
850    
851                eInvoiceRejectDocument = (ElectronicInvoiceRejectDocument) SpringContext.getBean(DocumentService.class).getNewDocument("EIRT");
852    
853                eInvoiceRejectDocument.setInvoiceProcessTimestamp(SpringContext.getBean(DateTimeService.class).getCurrentTimestamp());
854                String rejectdocDesc = generateRejectDocumentDescription(eInvoice,electronicInvoiceOrder);
855                eInvoiceRejectDocument.getDocumentHeader().setDocumentDescription(rejectdocDesc);
856                eInvoiceRejectDocument.setDocumentCreationInProgress(true);
857                
858                eInvoiceRejectDocument.setFileLevelData(eInvoice);
859                eInvoiceRejectDocument.setInvoiceOrderLevelData(eInvoice, electronicInvoiceOrder);
860    
861                String noteText = "Invoice file";
862                attachInvoiceXMLWithRejectDoc(eInvoiceRejectDocument, getInvoiceFile(eInvoice.getFileName()), noteText);
863    
864                eInvoiceLoad.addInvoiceReject(eInvoiceRejectDocument);
865                
866            }catch (WorkflowException e) {
867                throw new RuntimeException(e);
868            }
869            
870            if (LOG.isInfoEnabled()){
871                LOG.info("Reject document has been created (DocNo=" + eInvoiceRejectDocument.getDocumentNumber() + ")");
872            }
873            
874            emailTextErrorList.append("DUNS Number - " + eInvoice.getDunsNumber() + " " +eInvoice.getVendorName()+ ":\n");
875            emailTextErrorList.append("An Invoice from file '" + eInvoice.getFileName() + "' has been rejected due to the following error(s):\n");
876            
877            int index = 1;
878            for (ElectronicInvoiceRejectReason reason : eInvoiceRejectDocument.getInvoiceRejectReasons()) {
879                emailTextErrorList.append("    - " + reason.getInvoiceRejectReasonDescription() + "\n");
880                addRejectReasonsToNote("Reject Reason " + index + ". " + reason.getInvoiceRejectReasonDescription(), eInvoiceRejectDocument);
881                index++;
882            }
883            
884            emailTextErrorList.append("\n");
885            
886            return eInvoiceRejectDocument;
887        }
888        
889        protected void addRejectReasonsToNote(String rejectReasons, ElectronicInvoiceRejectDocument eInvoiceRejectDocument){
890    
891            try {
892                Note note = SpringContext.getBean(DocumentService.class).createNoteFromDocument(eInvoiceRejectDocument, rejectReasons);
893                PersistableBusinessObject noteParent = getNoteParent(eInvoiceRejectDocument, note);
894                noteParent.addNote(note);
895            }catch (Exception e) {
896                LOG.error("Error creating reject reason note - " + e.getMessage());
897            }
898        }
899        
900        
901        protected String generateRejectDocumentDescription(ElectronicInvoice eInvoice,
902                                                         ElectronicInvoiceOrder electronicInvoiceOrder){
903            
904            String poID = StringUtils.isEmpty(electronicInvoiceOrder.getInvoicePurchaseOrderID()) ?
905                          "UNKNOWN" :
906                          electronicInvoiceOrder.getInvoicePurchaseOrderID();
907            
908            String vendorName = StringUtils.isEmpty(eInvoice.getVendorName()) ? 
909                                "UNKNOWN" :
910                                eInvoice.getVendorName();
911            
912            String description = "PO: " + poID + " Vendor: " + vendorName;
913            
914            return checkDescriptionLengthAndStripIfNeeded(description);
915        }
916        
917        protected String generateRejectDocumentDescription(ElectronicInvoiceRejectDocument rejectDoc) {
918    
919            String poID = StringUtils.isEmpty(rejectDoc.getInvoicePurchaseOrderNumber()) ? 
920                          "UNKNOWN" : 
921                          rejectDoc.getInvoicePurchaseOrderNumber();
922    
923            String vendorName = "UNKNOWN";
924            if (rejectDoc.getVendorDetail() != null){
925                vendorName = rejectDoc.getVendorDetail().getVendorName();
926            }
927    
928            String description = "PO: " + poID + " Vendor: " + vendorName;
929    
930            return checkDescriptionLengthAndStripIfNeeded(description);
931        }
932        
933        protected String checkDescriptionLengthAndStripIfNeeded(String description){
934            
935            int noteTextMaxLength = SpringContext.getBean(DataDictionaryService.class).getAttributeMaxLength(DocumentHeader.class, KNSPropertyConstants.DOCUMENT_DESCRIPTION).intValue();
936    
937            if (noteTextMaxLength < description.length()) {
938                description = description.substring(0, noteTextMaxLength);
939            }
940            
941            return description;
942        }
943        
944        public ElectronicInvoiceLoadSummary getOrCreateLoadSummary(ElectronicInvoiceLoad eInvoiceLoad,
945                                                                   String fileDunsNumber){
946            ElectronicInvoiceLoadSummary eInvoiceLoadSummary;
947            
948            if (eInvoiceLoad.getInvoiceLoadSummaries().containsKey(fileDunsNumber)) {
949                eInvoiceLoadSummary = (ElectronicInvoiceLoadSummary) eInvoiceLoad.getInvoiceLoadSummaries().get(fileDunsNumber);
950            }
951            else {
952                eInvoiceLoadSummary = new ElectronicInvoiceLoadSummary(fileDunsNumber);
953            }
954            
955            return eInvoiceLoadSummary;
956            
957        }
958        
959        public ElectronicInvoice loadElectronicInvoice(byte[] xmlAsBytes)
960        throws CxmlParseException {
961          
962          if (LOG.isInfoEnabled()){
963              LOG.info("Loading Invoice File");
964          }
965          
966          ElectronicInvoice electronicInvoice = null;
967          
968          try {
969              electronicInvoice = (ElectronicInvoice) batchInputFileService.parse(electronicInvoiceInputFileType, xmlAsBytes);
970          }catch (ParseException e) {
971              throw new CxmlParseException(e.getMessage());
972          }
973          
974          if (LOG.isInfoEnabled()){
975              LOG.info("Successfully loaded the Invoice File");
976          }
977          
978          return electronicInvoice;
979          
980        }
981        
982        protected StringBuffer saveLoadSummary(ElectronicInvoiceLoad eInvoiceLoad) {
983    
984            Map savedLoadSummariesMap = new HashMap();
985            StringBuffer summaryMessage = new StringBuffer();
986            
987            for (Iterator iter = eInvoiceLoad.getInvoiceLoadSummaries().keySet().iterator(); iter.hasNext();) {
988                
989                String dunsNumber = (String) iter.next();
990                ElectronicInvoiceLoadSummary eInvoiceLoadSummary = (ElectronicInvoiceLoadSummary) eInvoiceLoad.getInvoiceLoadSummaries().get(dunsNumber);
991                
992                  if (!eInvoiceLoadSummary.isEmpty().booleanValue()){  
993                    LOG.info("Saving Load Summary for DUNS '" + dunsNumber + "'");
994                    
995                    ElectronicInvoiceLoadSummary currentLoadSummary = saveElectronicInvoiceLoadSummary(eInvoiceLoadSummary);
996                    
997                    summaryMessage.append("DUNS Number - " + eInvoiceLoadSummary.getVendorDescriptor() + ":\n");
998                    summaryMessage.append("     " + eInvoiceLoadSummary.getInvoiceLoadSuccessCount() + " successfully processed invoices for a total of $ " + eInvoiceLoadSummary.getInvoiceLoadSuccessAmount().doubleValue() + "\n");
999                    summaryMessage.append("     " + eInvoiceLoadSummary.getInvoiceLoadFailCount() + " rejected invoices for an approximate total of $ " + eInvoiceLoadSummary.getInvoiceLoadFailAmount().doubleValue() + "\n");
1000                    summaryMessage.append("\n\n");
1001                    
1002                    savedLoadSummariesMap.put(currentLoadSummary.getVendorDunsNumber(), eInvoiceLoadSummary);
1003                    
1004                } else {
1005                    LOG.info("Not saving Load Summary for DUNS '" + dunsNumber + "' because empty indicator is '" + eInvoiceLoadSummary.isEmpty().booleanValue() + "'");
1006                }
1007            }
1008            
1009            summaryMessage.append("\n\n");
1010            
1011            for (Iterator rejectIter = eInvoiceLoad.getRejectDocuments().iterator(); rejectIter.hasNext();) {
1012                ElectronicInvoiceRejectDocument rejectDoc = (ElectronicInvoiceRejectDocument) rejectIter.next();
1013                routeRejectDocument(rejectDoc,savedLoadSummariesMap);
1014            }
1015            
1016            /**
1017             * Even if there is an exception in the reject doc routing, all the files marked as reject will
1018             * be moved to the reject dir
1019             */
1020            moveFileList(eInvoiceLoad.getRejectFilesToMove());
1021            
1022            return summaryMessage;
1023        }
1024        
1025        protected void routeRejectDocument(ElectronicInvoiceRejectDocument rejectDoc,
1026                                         Map savedLoadSummariesMap){
1027            
1028            LOG.info("Saving Invoice Reject for DUNS '" + rejectDoc.getVendorDunsNumber() + "'");
1029            
1030            if (savedLoadSummariesMap.containsKey(rejectDoc.getVendorDunsNumber())) {
1031                rejectDoc.setInvoiceLoadSummary((ElectronicInvoiceLoadSummary) savedLoadSummariesMap.get(rejectDoc.getVendorDunsNumber()));
1032            }
1033            else {
1034                rejectDoc.setInvoiceLoadSummary((ElectronicInvoiceLoadSummary) savedLoadSummariesMap.get(UNKNOWN_DUNS_IDENTIFIER));
1035            }
1036            
1037            try{
1038                SpringContext.getBean(DocumentService.class).routeDocument(rejectDoc, "Routed by electronic invoice batch job", null);
1039            }
1040            catch (WorkflowException e) {
1041                e.printStackTrace();
1042            }
1043            
1044        }
1045        
1046        protected void sendSummary(StringBuffer message) {
1047    
1048            String fromMailId = SpringContext.getBean(ParameterService.class).getParameterValue(ElectronicInvoiceStep.class, PurapParameterConstants.ElectronicInvoiceParameters.DAILY_SUMMARY_REPORT_FROM_EMAIL_ADDRESS);
1049            List<String> toMailIds = SpringContext.getBean(ParameterService.class).getParameterValues(ElectronicInvoiceStep.class, PurapParameterConstants.ElectronicInvoiceParameters.DAILY_SUMMARY_REPORT_TO_EMAIL_ADDRESSES);
1050            
1051            LOG.info("From email address parameter value:"+fromMailId);
1052            LOG.info("To email address parameter value:"+toMailIds);
1053            
1054            if (StringUtils.isBlank(fromMailId) || toMailIds.isEmpty()){
1055                LOG.error("From/To mail addresses are empty. Unable to send the message");
1056            }else{
1057            
1058                MailMessage mailMessage = new MailMessage();
1059                
1060                mailMessage.setFromAddress(fromMailId);
1061                setMessageToAddressesAndSubject(mailMessage,toMailIds);
1062                mailMessage.setMessage(message.toString());
1063                
1064                try {
1065                    mailService.sendMessage(mailMessage);
1066                }catch (InvalidAddressException e) {
1067                    LOG.error("Invalid email address. Message not sent", e);
1068                }
1069            }
1070            
1071        }
1072        
1073        protected MailMessage setMessageToAddressesAndSubject(MailMessage message, List<String> toAddressList) {
1074            
1075            if (!toAddressList.isEmpty()) {
1076                for (int i = 0; i < toAddressList.size(); i++) {
1077                    if (StringUtils.isNotEmpty(toAddressList.get(i))) {
1078                        message.addToAddress(toAddressList.get(i).trim());
1079                    }
1080                }
1081            }
1082    
1083            String mailTitle = "E-Invoice Load Results for " + ElectronicInvoiceUtils.getDateDisplayText(SpringContext.getBean(DateTimeService.class).getCurrentDate());
1084            
1085            if (kualiConfigurationService.isProductionEnvironment()) {
1086                message.setSubject(mailTitle);
1087            } else {
1088                message.setSubject(kualiConfigurationService.getPropertyString(KFSConstants.ENVIRONMENT_KEY) + " - " + mailTitle);
1089            }
1090            return message;
1091        }
1092        
1093        /**
1094         * This method is responsible for the matching process for a reject document
1095         *    
1096         * @return true if the matching process is succeed
1097         */
1098        public boolean doMatchingProcess(ElectronicInvoiceRejectDocument rejectDocument){
1099    
1100            /**
1101             * This is needed here since if the user changes the DUNS number.
1102             */
1103            validateVendorDetails(rejectDocument);
1104            
1105            Map itemTypeMappings = getItemTypeMappings(rejectDocument.getVendorHeaderGeneratedIdentifier(),
1106                                                       rejectDocument.getVendorDetailAssignedIdentifier());
1107            
1108            Map kualiItemTypes = getKualiItemTypes();
1109    
1110            ElectronicInvoiceOrderHolder rejectDocHolder = new ElectronicInvoiceOrderHolder(rejectDocument,itemTypeMappings,kualiItemTypes);
1111            matchingService.doMatchingProcess(rejectDocHolder);
1112            
1113            /**
1114             * Once we're through with the matching process, it's needed to check whether it's possible
1115             * to create PREQ for the reject doc
1116             */
1117            if (!rejectDocHolder.isInvoiceRejected()){
1118                validateInvoiceOrderValidForPREQCreation(rejectDocHolder);
1119            }
1120        
1121            //  determine which of the reject reasons we should suppress based on the parameter
1122            List<String> ignoreRejectTypes = parameterService.getParameterValues(PurapConstants.PURAP_NAMESPACE, "ElectronicInvoiceReject", "SUPPRESS_REJECT_REASON_CODES_ON_EIRT_APPROVAL");
1123            List<ElectronicInvoiceRejectReason> rejectReasonsToDelete = new ArrayList<ElectronicInvoiceRejectReason>();
1124            for (ElectronicInvoiceRejectReason rejectReason : rejectDocument.getInvoiceRejectReasons()) {
1125                String rejectedReasonTypeCode = rejectReason.getInvoiceRejectReasonTypeCode();
1126                if (StringUtils.isNotBlank(rejectedReasonTypeCode)) {
1127                    if (ignoreRejectTypes.contains(rejectedReasonTypeCode)) {
1128                        rejectReasonsToDelete.add(rejectReason);
1129                    }
1130                }
1131         }
1132            
1133            //  remove the flagged reject reasons
1134            if (!rejectReasonsToDelete.isEmpty()) {
1135                rejectDocument.getInvoiceRejectReasons().removeAll(rejectReasonsToDelete);
1136            }
1137            
1138            //  if no reject reasons, then clear error messages
1139            if (rejectDocument.getInvoiceRejectReasons().isEmpty()) {
1140                GlobalVariables.getMessageMap().clearErrorMessages();
1141            }
1142            
1143            //  this automatically returns false if there are no reject reasons
1144            return !rejectDocHolder.isInvoiceRejected();
1145        }
1146        
1147        public boolean createPaymentRequest(ElectronicInvoiceRejectDocument rejectDocument){
1148         
1149            if (rejectDocument.getInvoiceRejectReasons().size() > 0){
1150                throw new RuntimeException("Not possible to create payment request since the reject document contains " + rejectDocument.getInvoiceRejectReasons().size() + " rejects");
1151            }
1152            
1153            Map itemTypeMappings = getItemTypeMappings(rejectDocument.getVendorHeaderGeneratedIdentifier(),
1154                                                       rejectDocument.getVendorDetailAssignedIdentifier());
1155    
1156            Map kualiItemTypes = getKualiItemTypes();
1157    
1158            ElectronicInvoiceOrderHolder rejectDocHolder = new ElectronicInvoiceOrderHolder(rejectDocument,itemTypeMappings,kualiItemTypes);
1159            
1160            /**
1161             * First, create a new payment request document.  Once this document is created, then update the reject document's PREQ_ID field
1162             * with the payment request document identifier.  This identifier is used to associate the reject document with the payment request.
1163             */
1164            PaymentRequestDocument preqDocument = createPaymentRequest(rejectDocHolder);
1165            rejectDocument.setPaymentRequestIdentifier(preqDocument.getPurapDocumentIdentifier());
1166            
1167            return !rejectDocHolder.isInvoiceRejected();
1168            
1169        }
1170        
1171        protected PaymentRequestDocument createPaymentRequest(ElectronicInvoiceOrderHolder orderHolder){
1172            
1173            if (LOG.isInfoEnabled()){
1174                LOG.info("Creating Payment Request document");
1175            }
1176            
1177            GlobalVariables.getMessageList().clear();
1178            
1179            validateInvoiceOrderValidForPREQCreation(orderHolder);
1180            
1181            if (LOG.isInfoEnabled()){
1182                if (orderHolder.isInvoiceRejected()){
1183                    LOG.info("Not possible to convert einvoice details into payment request");
1184                }else{
1185                    LOG.info("Payment request document creation validation succeeded");
1186                }
1187            }
1188            
1189            if (orderHolder.isInvoiceRejected()){
1190                return null;
1191            }
1192            
1193            PaymentRequestDocument preqDoc = null;
1194            try {
1195                preqDoc = (PaymentRequestDocument) SpringContext.getBean(DocumentService.class).getNewDocument("PREQ");
1196            }
1197            catch (WorkflowException e) {
1198                String extraDescription = "Error=" + e.getMessage();
1199                ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.PREQ_WORKLOW_EXCEPTION,          
1200                                                                                                extraDescription, 
1201                                                                                                orderHolder.getFileName());
1202                orderHolder.addInvoiceOrderRejectReason(rejectReason);
1203                LOG.error("Error creating Payment request document - " + e.getMessage());
1204                return null;
1205            }
1206            
1207            PurchaseOrderDocument poDoc = orderHolder.getPurchaseOrderDocument();
1208            if (poDoc == null){
1209                throw new RuntimeException("Purchase Order document (POId=" + poDoc.getPurapDocumentIdentifier() + ") does not exist in the system");
1210            }
1211            
1212            preqDoc.getDocumentHeader().setDocumentDescription(generatePREQDocumentDescription(poDoc));        
1213            preqDoc.setStatusCode(PurapConstants.PaymentRequestStatuses.IN_PROCESS);
1214            preqDoc.setInvoiceDate(orderHolder.getInvoiceDate());
1215            preqDoc.setInvoiceNumber(orderHolder.getInvoiceNumber());
1216            preqDoc.setVendorInvoiceAmount(new KualiDecimal(orderHolder.getInvoiceNetAmount()));
1217            preqDoc.setAccountsPayableProcessorIdentifier("E-Invoice");
1218            preqDoc.setVendorCustomerNumber(orderHolder.getCustomerNumber());
1219            preqDoc.setPaymentRequestElectronicInvoiceIndicator(true);
1220            
1221            if (orderHolder.getAccountsPayablePurchasingDocumentLinkIdentifier() != null){
1222                preqDoc.setAccountsPayablePurchasingDocumentLinkIdentifier(orderHolder.getAccountsPayablePurchasingDocumentLinkIdentifier());
1223            }
1224            
1225            //Copied from PaymentRequestServiceImpl.populatePaymentRequest()
1226            //set bank code to default bank code in the system parameter
1227            Bank defaultBank = SpringContext.getBean(BankService.class).getDefaultBankByDocType(preqDoc.getClass());
1228            if (defaultBank != null) {
1229                preqDoc.setBankCode(defaultBank.getBankCode());
1230                preqDoc.setBank(defaultBank);
1231            }
1232            
1233            RequisitionDocument reqDoc = SpringContext.getBean(RequisitionService.class).getRequisitionById(poDoc.getRequisitionIdentifier());
1234            String reqDocInitiator = reqDoc.getDocumentHeader().getWorkflowDocument().getInitiatorNetworkId();
1235            try {
1236                Person user = SpringContext.getBean(org.kuali.rice.kim.service.PersonService.class).getPersonByPrincipalName(reqDocInitiator);
1237                preqDoc.setProcessingCampusCode(user.getCampusCode());
1238            }catch(Exception e){
1239                String extraDescription = "Error setting processing campus code - " + e.getMessage();
1240                ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.PREQ_ROUTING_VALIDATION_ERROR, extraDescription, orderHolder.getFileName());
1241                orderHolder.addInvoiceOrderRejectReason(rejectReason);
1242                return null;
1243            }
1244            
1245            HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccountList = SpringContext.getBean(AccountsPayableService.class).expiredOrClosedAccountsList(poDoc);
1246            if (expiredOrClosedAccountList == null){
1247                expiredOrClosedAccountList = new HashMap();
1248            }
1249            
1250            if (LOG.isInfoEnabled()){
1251                 LOG.info(expiredOrClosedAccountList.size() + " accounts has been found as Expired or Closed");
1252            }
1253            
1254            preqDoc.populatePaymentRequestFromPurchaseOrder(orderHolder.getPurchaseOrderDocument(),expiredOrClosedAccountList);
1255            
1256            populateItemDetails(preqDoc,orderHolder);
1257            
1258            /**
1259             * Validate totals,paydate
1260             */
1261            //PaymentRequestDocumentRule.processCalculateAccountsPayableBusinessRules
1262            SpringContext.getBean(KualiRuleService.class).applyRules(new AttributedCalculateAccountsPayableEvent(preqDoc));
1263            
1264            SpringContext.getBean(PaymentRequestService.class).calculatePaymentRequest(preqDoc,true);
1265            
1266            processItemsForDiscount(preqDoc,orderHolder);
1267            
1268            if (orderHolder.isInvoiceRejected()){
1269                return null;
1270            }
1271            
1272            SpringContext.getBean(PaymentRequestService.class).calculatePaymentRequest(preqDoc,false);
1273            /**
1274             * PaymentRequestReview 
1275             */
1276            //PaymentRequestDocumentRule.processRouteDocumentBusinessRules
1277            SpringContext.getBean(KualiRuleService.class).applyRules(new AttributedPaymentRequestForEInvoiceEvent(preqDoc));
1278            
1279            if(GlobalVariables.getMessageMap().hasErrors()){
1280                if (LOG.isInfoEnabled()){
1281                    LOG.info("***************Error in rules processing - " + GlobalVariables.getMessageMap());
1282                }
1283                ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.PREQ_ROUTING_VALIDATION_ERROR, getErrorMessages(GlobalVariables.getMessageMap().getErrorMessages()), orderHolder.getFileName());
1284                orderHolder.addInvoiceOrderRejectReason(rejectReason);
1285                return null;
1286            }
1287            
1288            if(GlobalVariables.getMessageList().size() > 0){
1289                if (LOG.isInfoEnabled()){
1290                    LOG.info("Payment request contains " + GlobalVariables.getMessageList().size() + " warning message(s)");
1291                    for (int i = 0; i < GlobalVariables.getMessageList().size(); i++) {
1292                        LOG.info("Warning " + i + "  - " +GlobalVariables.getMessageList().get(i));
1293                    }
1294                }
1295            }
1296            
1297            addShipToNotes(preqDoc,orderHolder);
1298            
1299            String routingAnnotation = null;
1300            if (!orderHolder.isRejectDocumentHolder()){
1301                routingAnnotation = "Routed by electronic invoice batch job";
1302            }
1303            
1304            try {
1305                SpringContext.getBean(DocumentService.class).routeDocument(preqDoc,routingAnnotation, null);
1306            }
1307            catch (WorkflowException e) {
1308                e.printStackTrace();
1309                ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.PREQ_ROUTING_FAILURE, e.getMessage(), orderHolder.getFileName());
1310                orderHolder.addInvoiceOrderRejectReason(rejectReason);
1311                return null;
1312            }catch(ValidationException e){
1313                String extraDescription = GlobalVariables.getMessageMap().toString();
1314                ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.PREQ_ROUTING_VALIDATION_ERROR, extraDescription, orderHolder.getFileName());
1315                orderHolder.addInvoiceOrderRejectReason(rejectReason);
1316                return null;
1317            }
1318            
1319            return preqDoc;
1320        }
1321        
1322        protected void addShipToNotes(PaymentRequestDocument preqDoc, 
1323                                    ElectronicInvoiceOrderHolder orderHolder){
1324            
1325            String shipToAddress = orderHolder.getInvoiceShipToAddressAsString();
1326            
1327            try {
1328                Note noteObj = SpringContext.getBean(DocumentService.class).createNoteFromDocument(preqDoc, shipToAddress);
1329                preqDoc.addNote(noteObj);
1330            }catch (Exception e) {
1331                 LOG.error("Error creating ShipTo notes - " + e.getMessage());
1332            }
1333        }
1334        
1335        protected void processItemsForDiscount(PaymentRequestDocument preqDocument, 
1336                                             ElectronicInvoiceOrderHolder orderHolder){
1337            
1338            if (LOG.isInfoEnabled()){
1339                LOG.info("Processing payment request items for discount");
1340            }
1341            
1342            if (!orderHolder.isItemTypeAvailableInItemMapping(ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DISCOUNT)){
1343                if (LOG.isInfoEnabled()){
1344                    LOG.info("Skipping discount processing since there is no mapping of discount type for this vendor");
1345                }
1346                return;
1347            }
1348            
1349            if (orderHolder.getInvoiceDiscountAmount() == null ||
1350                orderHolder.getInvoiceDiscountAmount() == BigDecimal.ZERO){
1351                if (LOG.isInfoEnabled()){
1352                    LOG.info("Skipping discount processing since there is no discount amount found in the invoice file");
1353                }
1354                return;
1355            }
1356            
1357            KualiDecimal discountValueToUse = new KualiDecimal(orderHolder.getInvoiceDiscountAmount().negate());
1358            List<PaymentRequestItem> preqItems = preqDocument.getItems();
1359            
1360            boolean alreadyProcessedInvoiceDiscount = false;
1361            boolean hasKualiPaymentTermsDiscountItem = false;
1362            
1363            //if e-invoice amount is negative... it is a penalty and we must pay extra 
1364            for (int i = 0; i < preqItems.size(); i++) {
1365                
1366                PaymentRequestItem preqItem = preqItems.get(i);
1367                
1368                hasKualiPaymentTermsDiscountItem = hasKualiPaymentTermsDiscountItem || (StringUtils.equals(PurapConstants.ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE,preqItem.getItemTypeCode()));
1369                
1370                if (isItemValidForUpdation(preqItem.getItemTypeCode(),ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DISCOUNT,orderHolder)){
1371                    
1372                    alreadyProcessedInvoiceDiscount = true;
1373                    
1374                    if (StringUtils.equals(preqItem.getItemTypeCode(),PurapConstants.ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE)){
1375                        //Item is kuali payment terms discount item... must perform calculation
1376                        // if discount item exists on PREQ and discount dollar amount exists... use greater amount
1377                        if (LOG.isInfoEnabled()){
1378                            LOG.info("Discount Check - E-Invoice matches PREQ item type '" + preqItem.getItemTypeCode() + "'... now checking for amount");
1379                        }
1380                        
1381                        KualiDecimal preqExtendedPrice = preqItem.getExtendedPrice() == null ? KualiDecimal.ZERO : preqItem.getExtendedPrice();
1382                        if ( (discountValueToUse.compareTo(preqExtendedPrice)) < 0 ) {
1383                            if (LOG.isInfoEnabled()){
1384                                LOG.info("Discount Check - Using E-Invoice amount (" + discountValueToUse + ") as it is more discount than current payment terms amount " + preqExtendedPrice);
1385                            }
1386                            preqItem.setItemUnitPrice(discountValueToUse.bigDecimalValue());
1387                            preqItem.setExtendedPrice(discountValueToUse);
1388                          }
1389                    }else {
1390                        // item is not payment terms discount item... just add value
1391                        // if discount item exists on PREQ and discount dollar amount exists... use greater amount
1392                        if (LOG.isInfoEnabled()){
1393                            LOG.info("Discount Check - E-Invoice matches PREQ item type '" + preqItem.getItemTypeCode() + "'");
1394                            LOG.info("Discount Check - Using E-Invoice amount (" + discountValueToUse + ") as it is greater than payment terms amount");
1395                        }
1396                        preqItem.addToUnitPrice(discountValueToUse.bigDecimalValue());
1397                        preqItem.addToExtendedPrice(discountValueToUse);
1398                      }
1399                    }
1400             }
1401            
1402            /*
1403             *   If we have not already processed the discount amount then the mapping is pointed
1404             *   to an item that is not in the PREQ item list
1405             *   
1406             *   FYI - FILE DISCOUNT AMOUNT CURRENTLY HARD CODED TO GO INTO PAYMENT TERMS DISCOUNT ITEM ONLY... ALL OTHERS WILL FAIL
1407             */
1408            
1409            if (!alreadyProcessedInvoiceDiscount) {
1410                String itemTypeRequired = PurapConstants.ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE;
1411                // if we already have a PMT TERMS DISC item but the e-invoice discount wasn't processed... error out
1412                // if the item mapping for e-invoice discount item is not PMT TERMS DISC item and we haven't processed it... error out
1413                
1414                if (hasKualiPaymentTermsDiscountItem || 
1415                    !orderHolder.isItemTypeAvailableInItemMapping(ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DISCOUNT)) {
1416                    ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.PREQ_DISCOUNT_ERROR, null, orderHolder.getFileName());
1417                    orderHolder.addInvoiceOrderRejectReason(rejectReason);
1418                    return;
1419                }
1420                else {
1421                    PaymentRequestItem newItem = new PaymentRequestItem();
1422                    newItem.setItemUnitPrice(discountValueToUse.bigDecimalValue());
1423                    newItem.setItemTypeCode(PurapConstants.ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE);
1424                    newItem.setExtendedPrice(discountValueToUse);
1425                    newItem.setPurapDocument(preqDocument);
1426                    preqDocument.addItem(newItem);                
1427                }
1428            }
1429            
1430            if (LOG.isInfoEnabled()){
1431                LOG.info("Completed processing payment request items for discount");
1432            }
1433            
1434        }
1435        
1436        protected void populateItemDetails(PaymentRequestDocument preqDocument, ElectronicInvoiceOrderHolder orderHolder) {
1437            
1438            if (LOG.isInfoEnabled()) {
1439                LOG.info("Populating invoice order items into the payment request document");
1440            }
1441    
1442            List<PurApItem> preqItems = preqDocument.getItems();
1443            
1444            //process all preq items and apply amounts from order holder
1445            for (int i = 0; i < preqItems.size(); i++) {
1446                            
1447                PaymentRequestItem preqItem = (PaymentRequestItem) preqItems.get(i);            
1448                processInvoiceItem(preqItem, orderHolder);
1449                            
1450                /**
1451                 * This is not needed since if we have default desc from misc item, then preq rules are expecting the account details for this items
1452                 * AccountsPayableItemBase.isConsideredEntered() returns true if there is any item desc available. 
1453                 * 
1454                 */
1455    //            setItemDefaultDescription(preqItem);
1456            }
1457    
1458            //Now we'll add any missing mapping items that did not have
1459            // an existing payment request item
1460            addMissingMappedItems(preqItems, orderHolder);
1461    
1462            //as part of a clean up, remove any preq items that have zero or null unit/extended price
1463            removeEmptyItems(preqItems);
1464            
1465            if (LOG.isInfoEnabled()) {
1466                LOG.info("Successfully populated the invoice order items");
1467            }
1468    
1469        }
1470        
1471        /**
1472         * Removes preq items from the list that have null or zero unit and extended price
1473         * 
1474         * @param preqItems
1475         */
1476        protected void removeEmptyItems(List<PurApItem> preqItems){
1477         
1478            for(int i=preqItems.size()-1; i >= 0; i--){
1479                PurApItem item = preqItems.get(i);
1480                
1481                //if the unit and extended price have null or zero as a combo, remove item
1482                if( isNullOrZero(item.getItemUnitPrice()) && isNullOrZero(item.getExtendedPrice()) ){
1483                    preqItems.remove(i);
1484                }
1485            }
1486        }
1487        
1488        /**
1489         * Ensures that the mapped items, item type code, exist as a payment request item so they're
1490         * process correctly within populateItemDetails
1491         * 
1492         * @param preqItems
1493         * @param orderHolder
1494         */
1495        protected void addMissingMappedItems(List<PurApItem> preqItems, ElectronicInvoiceOrderHolder orderHolder){
1496            
1497            PurchasingAccountsPayableDocument purapDoc = null;
1498            Integer purapDocIdentifier = null;
1499            
1500            //grab all the required item types that should be on the payment request
1501            List requiredItemTypeCodeList = createInvoiceRequiresItemTypeCodeList(orderHolder);
1502            
1503            if( ObjectUtils.isNotNull(requiredItemTypeCodeList) && !requiredItemTypeCodeList.isEmpty()) {
1504            
1505                //loop through existing payment request items and remove ones we already have
1506                for (int i=0; i < preqItems.size(); i++) {
1507                    //if the preq item exists in the list already, remove
1508                    if( requiredItemTypeCodeList.contains(preqItems.get(i).getItemTypeCode()) ){
1509                        requiredItemTypeCodeList.remove(preqItems.get(i).getItemTypeCode());
1510                    }
1511                    
1512                    //utility grab the document identifier and document
1513                    purapDoc = preqItems.get(i).getPurapDocument();
1514                    purapDocIdentifier = preqItems.get(i).getPurapDocumentIdentifier();
1515                }
1516                            
1517                if( ObjectUtils.isNotNull(requiredItemTypeCodeList) && !requiredItemTypeCodeList.isEmpty()) {
1518                    //if we have any left, it means they didn't exist on the payment request
1519                    // and we must add them.                
1520                    for(int i=0; i < requiredItemTypeCodeList.size(); i++){
1521                        PaymentRequestItem preqItem = new PaymentRequestItem();
1522                        preqItem.resetAccount();
1523                        preqItem.setPurapDocumentIdentifier(purapDocIdentifier);
1524                        preqItem.setPurapDocument(purapDoc);
1525                        preqItem.setItemTypeCode((String)requiredItemTypeCodeList.get(i));
1526                        
1527                        //process item
1528                        processInvoiceItem(preqItem, orderHolder);
1529                        
1530                        //Add to preq Items if the value is not zero
1531                        if( (ObjectUtils.isNotNull(preqItem.getItemUnitPrice()) && preqItem.getItemUnitPrice() != BigDecimal.ZERO) && 
1532                            (ObjectUtils.isNotNull(preqItem.getExtendedPrice()) && preqItem.getExtendedPrice() != KualiDecimal.ZERO) ){
1533                            
1534                            preqItems.add(preqItem);    
1535                        }
1536                                            
1537                        
1538                    }
1539                }
1540                
1541            }
1542        }
1543    
1544        /**
1545         * Creates a list of item types the eInvoice requirs on
1546         * the payment request due to valid amounts.
1547         */
1548        protected List createInvoiceRequiresItemTypeCodeList(ElectronicInvoiceOrderHolder orderHolder){
1549            List itemTypeCodeList = new ArrayList();
1550                            
1551            addToListIfExists(itemTypeCodeList, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_TAX, orderHolder);
1552            addToListIfExists(itemTypeCodeList, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_SHIPPING, orderHolder);
1553            addToListIfExists(itemTypeCodeList, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_SPECIAL_HANDLING, orderHolder);
1554            addToListIfExists(itemTypeCodeList, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DEPOSIT, orderHolder);
1555            addToListIfExists(itemTypeCodeList, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DUE, orderHolder);
1556            addToListIfExists(itemTypeCodeList, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DISCOUNT, orderHolder);
1557            
1558            return itemTypeCodeList;
1559        }
1560    
1561        /**
1562         * Utility method to add a kuali item type code to a list from a invoice item type code
1563         * 
1564         * @param itemTypeCodeList
1565         * @param invoiceItemTypeCode
1566         * @param orderHolder
1567         */
1568        protected void addToListIfExists(List itemTypeCodeList, String invoiceItemTypeCode, ElectronicInvoiceOrderHolder orderHolder){
1569            
1570            String itemTypeCode = orderHolder.getKualiItemTypeCodeFromMappings(invoiceItemTypeCode);
1571            
1572            if( ObjectUtils.isNotNull(itemTypeCode) ){
1573                itemTypeCodeList.add(itemTypeCode);
1574            }
1575        }
1576            
1577        /**
1578         * Finds the mapped item type code to invoice item type code and applies the appropriate values
1579         * to the correct payment request item. 
1580         * 
1581         * @param preqItem
1582         * @param orderHolder
1583         */
1584        protected void processInvoiceItem(PaymentRequestItem preqItem, ElectronicInvoiceOrderHolder orderHolder){
1585    
1586            if (isItemValidForUpdation(preqItem.getItemTypeCode(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_ITEM, orderHolder)) {
1587                processAboveTheLineItem(preqItem, orderHolder);
1588            }else if (isItemValidForUpdation(preqItem.getItemTypeCode(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_TAX, orderHolder)) {
1589                processTaxItem(preqItem, orderHolder);
1590            } else if (isItemValidForUpdation(preqItem.getItemTypeCode(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_SHIPPING, orderHolder)) {
1591                processShippingItem(preqItem, orderHolder);
1592            } else if (isItemValidForUpdation(preqItem.getItemTypeCode(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_SPECIAL_HANDLING, orderHolder)) {
1593                processSpecialHandlingItem(preqItem, orderHolder);
1594            } else if (isItemValidForUpdation(preqItem.getItemTypeCode(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DEPOSIT, orderHolder)) {
1595                processDepositItem(preqItem, orderHolder);
1596            } else if (isItemValidForUpdation(preqItem.getItemTypeCode(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DUE, orderHolder)) {
1597                processDueItem(preqItem, orderHolder);
1598            } else if (isItemValidForUpdation(preqItem.getItemTypeCode(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DISCOUNT, orderHolder)) {
1599                processDiscountItem(preqItem, orderHolder);
1600            }else if (isItemValidForUpdation(preqItem.getItemTypeCode(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_EXMT, orderHolder)) {
1601                processAboveTheLineItem(preqItem, orderHolder);
1602            }
1603            
1604        }
1605        
1606        protected void processAboveTheLineItem(PaymentRequestItem purapItem,
1607                                             ElectronicInvoiceOrderHolder orderHolder){
1608    
1609            if (LOG.isInfoEnabled()){
1610                LOG.info("Processing above the line item");
1611            }
1612            
1613            ElectronicInvoiceItemHolder itemHolder = orderHolder.getItemByLineNumber(purapItem.getItemLineNumber().intValue());
1614            if (itemHolder == null){
1615                LOG.info("Electronic Invoice does not have item with Ref Item Line number " + purapItem.getItemLineNumber());
1616                return;
1617            }
1618            
1619            purapItem.setItemUnitPrice(itemHolder.getInvoiceItemUnitPrice());
1620            purapItem.setItemQuantity(new KualiDecimal(itemHolder.getInvoiceItemQuantity()));
1621            purapItem.setItemTaxAmount(new KualiDecimal(itemHolder.getTaxAmount()));
1622            purapItem.setItemCatalogNumber(itemHolder.getInvoiceItemCatalogNumber());
1623            purapItem.setItemDescription(itemHolder.getInvoiceItemDescription());
1624            
1625            if (itemHolder.getSubTotalAmount() != null && 
1626                itemHolder.getSubTotalAmount().compareTo(KualiDecimal.ZERO) != 0){
1627    
1628                purapItem.setExtendedPrice(itemHolder.getSubTotalAmount());
1629                
1630            }else{
1631                
1632                if (purapItem.getItemQuantity() != null) {
1633                    if (LOG.isInfoEnabled()){
1634                        LOG.info("Item number " + purapItem.getItemLineNumber() + " needs calculation of extended " +
1635                                 "price from quantity " + purapItem.getItemQuantity() + " and unit cost " + purapItem.getItemUnitPrice());
1636                    }
1637                    purapItem.setExtendedPrice(purapItem.getItemQuantity().multiply(new KualiDecimal(purapItem.getItemUnitPrice())));
1638                  } else {
1639                      if (LOG.isInfoEnabled()){
1640                          LOG.info("Item number " + purapItem.getItemLineNumber() + " has no quantity so extended price " +
1641                                   "equals unit price of " + purapItem.getItemUnitPrice());
1642                      }
1643                      purapItem.setExtendedPrice(new KualiDecimal(purapItem.getItemUnitPrice()));
1644                  }
1645            }
1646            
1647        }
1648        
1649        protected void processSpecialHandlingItem(PaymentRequestItem purapItem,
1650                                                ElectronicInvoiceOrderHolder orderHolder){
1651            
1652            if (LOG.isInfoEnabled()){
1653                LOG.info("Processing special handling item");
1654            }
1655    
1656            purapItem.addToUnitPrice(orderHolder.getInvoiceSpecialHandlingAmount());
1657            purapItem.addToExtendedPrice(new KualiDecimal(orderHolder.getInvoiceSpecialHandlingAmount()));
1658            
1659            String invoiceSpecialHandlingDescription = orderHolder.getInvoiceSpecialHandlingDescription();
1660            
1661            if(invoiceSpecialHandlingDescription == null && (orderHolder.getInvoiceSpecialHandlingAmount() != null && BigDecimal.ZERO.compareTo(orderHolder.getInvoiceSpecialHandlingAmount())!= 0) ){
1662                invoiceSpecialHandlingDescription = PurapConstants.ElectronicInvoice.DEFAULT_SPECIAL_HANDLING_DESCRIPTION;
1663            }
1664            if (StringUtils.isNotEmpty(invoiceSpecialHandlingDescription)) {
1665                if (StringUtils.isEmpty(purapItem.getItemDescription())) {
1666                    purapItem.setItemDescription(invoiceSpecialHandlingDescription);
1667                }
1668                else {
1669                    purapItem.setItemDescription(purapItem.getItemDescription() + " - " + invoiceSpecialHandlingDescription);
1670                }
1671            }
1672        }
1673        
1674        protected void processTaxItem (PaymentRequestItem preqItem,
1675                                     ElectronicInvoiceOrderHolder orderHolder){
1676            
1677            if (LOG.isInfoEnabled()){
1678                LOG.info("Processing Tax Item");
1679            }
1680            
1681            preqItem.addToUnitPrice(orderHolder.getTaxAmount());
1682            preqItem.addToExtendedPrice(new KualiDecimal(orderHolder.getTaxAmount()));
1683            
1684            if (StringUtils.isNotEmpty(orderHolder.getTaxDescription())) {
1685                if (StringUtils.isEmpty(preqItem.getItemDescription())) {
1686                    preqItem.setItemDescription(orderHolder.getTaxDescription());
1687                } else {
1688                    preqItem.setItemDescription(preqItem.getItemDescription() + " - " + orderHolder.getTaxDescription());
1689                }
1690            }
1691        }
1692           
1693        protected void processShippingItem(PaymentRequestItem preqItem,
1694                                                       ElectronicInvoiceOrderHolder orderHolder){
1695            
1696            if (LOG.isInfoEnabled()){
1697                LOG.info("Processing Shipping Item");
1698            }
1699            
1700            preqItem.addToUnitPrice(orderHolder.getInvoiceShippingAmount());
1701            preqItem.addToExtendedPrice(new KualiDecimal(orderHolder.getInvoiceShippingAmount()));
1702            
1703            if (StringUtils.isNotEmpty(orderHolder.getInvoiceShippingDescription())) {
1704                if (StringUtils.isEmpty(preqItem.getItemDescription())) {
1705                    preqItem.setItemDescription(orderHolder.getInvoiceShippingDescription());
1706                } else {
1707                    preqItem.setItemDescription(preqItem.getItemDescription() + " - " + orderHolder.getInvoiceShippingDescription());
1708                }
1709            }
1710        }
1711    
1712        protected void processDiscountItem(PaymentRequestItem preqItem,
1713                ElectronicInvoiceOrderHolder orderHolder){
1714    
1715            if (LOG.isInfoEnabled()){
1716                LOG.info("Processing Discount Item");
1717            }
1718    
1719            preqItem.addToUnitPrice(orderHolder.getInvoiceDiscountAmount());
1720            preqItem.addToExtendedPrice(new KualiDecimal(orderHolder.getInvoiceDiscountAmount()));
1721        }
1722    
1723        
1724        protected void processDepositItem(PaymentRequestItem preqItem,
1725                                        ElectronicInvoiceOrderHolder orderHolder){
1726    
1727            LOG.info("Processing Deposit Item");
1728            
1729            preqItem.addToUnitPrice(orderHolder.getInvoiceDepositAmount());
1730            preqItem.addToExtendedPrice(new KualiDecimal(orderHolder.getInvoiceDepositAmount()));
1731        }
1732        
1733        protected void processDueItem(PaymentRequestItem preqItem,
1734                                    ElectronicInvoiceOrderHolder orderHolder){
1735    
1736            LOG.info("Processing Deposit Item");
1737            
1738            preqItem.addToUnitPrice(orderHolder.getInvoiceDueAmount());
1739            preqItem.addToExtendedPrice(new KualiDecimal(orderHolder.getInvoiceDueAmount()));
1740        }
1741    
1742        protected boolean isNullOrZero(BigDecimal value){
1743            
1744            if(ObjectUtils.isNull(value) || value.compareTo(BigDecimal.ZERO) == 0 ){
1745                return true;
1746            }else{
1747                return false;
1748            }
1749        }
1750    
1751        protected boolean isNullOrZero(KualiDecimal value){
1752            
1753            if(ObjectUtils.isNull(value) || value.isZero()){
1754                return true;
1755            }else{
1756                return false;
1757            }
1758        }
1759    
1760        protected void setItemDefaultDescription(PaymentRequestItem preqItem){
1761            
1762            //If description is empty and item is not type "ITEM"... use default description
1763            if (StringUtils.isEmpty(preqItem.getItemDescription()) &&
1764                !StringUtils.equals(PurapConstants.ItemTypeCodes.ITEM_TYPE_ITEM_CODE, preqItem.getItemTypeCode())){
1765                if (ArrayUtils.contains(PurapConstants.ElectronicInvoice.ITEM_TYPES_REQUIRES_DESCRIPTION, preqItem.getItemTypeCode())){
1766                    preqItem.setItemDescription(PurapConstants.ElectronicInvoice.DEFAULT_BELOW_LINE_ITEM_DESCRIPTION);
1767                }
1768            }
1769        }
1770        
1771        protected boolean isItemValidForUpdation(String itemTypeCode,
1772                                               String invoiceItemTypeCode,
1773                                               ElectronicInvoiceOrderHolder orderHolder){
1774            
1775            boolean isItemTypeAvailableInItemMapping = orderHolder.isItemTypeAvailableInItemMapping(invoiceItemTypeCode);
1776            String itemTypeCodeFromMappings = orderHolder.getKualiItemTypeCodeFromMappings(invoiceItemTypeCode);
1777            return isItemTypeAvailableInItemMapping && StringUtils.equals(itemTypeCodeFromMappings, itemTypeCode);
1778        }
1779         
1780        
1781        protected String generatePREQDocumentDescription(PurchaseOrderDocument poDocument) {
1782            String description = "PO: " + poDocument.getPurapDocumentIdentifier() + " Vendor: " + poDocument.getVendorName() + " Electronic Invoice";
1783            return checkDescriptionLengthAndStripIfNeeded(description);
1784        }
1785        
1786        /**
1787         * This validates an electronic invoice and makes sure it can be turned into a Payment Request
1788         * 
1789         */
1790        public void validateInvoiceOrderValidForPREQCreation(ElectronicInvoiceOrderHolder orderHolder){
1791            
1792            if (LOG.isInfoEnabled()){
1793                LOG.info("Validiting ElectronicInvoice Order to make sure that it can be turned into a Payment Request document");
1794            }
1795            
1796            PurchaseOrderDocument poDoc = orderHolder.getPurchaseOrderDocument();
1797            
1798            if ( poDoc == null){
1799                throw new RuntimeException("PurchaseOrder not available");
1800            }
1801                
1802            if (!poDoc.getStatusCode().equals(PurchaseOrderStatuses.OPEN)) {
1803                orderHolder.addInvoiceOrderRejectReason(matchingService.createRejectReason(PurapConstants.ElectronicInvoice.PO_NOT_OPEN,null,orderHolder.getFileName()));
1804                return;
1805            }
1806            
1807            if (!orderHolder.isInvoiceNumberAcceptIndicatorEnabled()){
1808                List preqs = paymentRequestService.getPaymentRequestsByVendorNumberInvoiceNumber(poDoc.getVendorHeaderGeneratedIdentifier(), 
1809                                                                                                 poDoc.getVendorDetailAssignedIdentifier(), 
1810                                                                                                 orderHolder.getInvoiceNumber());
1811                
1812                if (preqs != null && preqs.size() > 0){
1813                    ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_ORDER_DUPLICATE,null,orderHolder.getFileName());
1814                    orderHolder.addInvoiceOrderRejectReason(rejectReason,PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_FILE_NUMBER,PurapKeyConstants.ERROR_REJECT_INVOICE_DUPLICATE);
1815                    return;
1816                }
1817            }
1818            
1819            if (orderHolder.getInvoiceDate() == null){
1820                ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_DATE_INVALID,null,orderHolder.getFileName());
1821                orderHolder.addInvoiceOrderRejectReason(rejectReason,PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_FILE_DATE,PurapKeyConstants.ERROR_REJECT_INVOICE_DATE_INVALID);
1822                return;
1823            }else if (orderHolder.getInvoiceDate().after(dateTimeService.getCurrentDate())) {
1824                ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_DATE_GREATER,null,orderHolder.getFileName()); 
1825                orderHolder.addInvoiceOrderRejectReason(rejectReason,PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_FILE_DATE,PurapKeyConstants.ERROR_REJECT_INVOICE_DATE_GREATER);
1826                return;
1827            }
1828            
1829        }
1830        
1831        protected void moveFileList(Map filesToMove) {
1832            for (Iterator iter = filesToMove.keySet().iterator(); iter.hasNext();) {
1833                File fileToMove = (File) iter.next();
1834    
1835                boolean success = this.moveFile(fileToMove, (String) filesToMove.get(fileToMove));
1836                if (!success) {
1837                    String errorMessage = "File with name '" + fileToMove.getName() + "' could not be moved";
1838                    throw new PurError(errorMessage);
1839                }
1840            }
1841        }
1842    
1843        protected boolean moveFile(File fileForMove, String location) {
1844            File moveDir = new File(location);
1845            boolean success = fileForMove.renameTo(new File(moveDir, fileForMove.getName()));
1846            return success;
1847        }
1848    
1849        protected void deleteDoneFile(File invoiceFile) {
1850            File doneFile = new File(invoiceFile.getAbsolutePath().replace(".xml", ".done"));
1851            if (doneFile.exists()) {
1852                doneFile.delete();
1853            }
1854        }
1855    
1856        /**
1857         * returns a list of all error messages as a string
1858         * 
1859         * @param errorMap
1860         * @return
1861         */
1862        protected String getErrorMessages(Map<String, TypedArrayList> errorMap){
1863                    
1864            TypedArrayList errorMessages = null;
1865            ErrorMessage errorMessage = null;
1866            StringBuffer errorList = new StringBuffer("");
1867            String errorText = null;
1868            
1869            for (Map.Entry<String, TypedArrayList> errorEntry : errorMap.entrySet()) {
1870        
1871                errorMessages = errorEntry.getValue();
1872        
1873                for (int i = 0; i < errorMessages.size(); i++) {
1874        
1875                    errorMessage = (ErrorMessage) errorMessages.get(i);
1876                                    
1877                    // get error text
1878                    errorText = kualiConfigurationService
1879                            .getPropertyString(errorMessage.getErrorKey());
1880                    // apply parameters
1881                    errorText = MessageFormat.format(errorText,
1882                            (Object[]) errorMessage.getMessageParameters());
1883        
1884                    // add key and error message together
1885                    errorList.append(errorText + "\n");
1886                }
1887            } 
1888            
1889            return errorList.toString();
1890        }
1891        
1892        protected String getBaseDirName(){
1893            return electronicInvoiceInputFileType.getDirectoryPath() + File.separator;
1894        }
1895        
1896        protected String getRejectDirName(){
1897            return getBaseDirName() + "reject" + File.separator;
1898        }
1899        
1900        protected String getAcceptDirName(){
1901            return getBaseDirName() + "accept" + File.separator;
1902        }
1903        
1904        protected File getInvoiceFile(String fileName){
1905            return new File(getBaseDirName() + fileName);
1906        }
1907        
1908        protected ElectronicInvoiceLoadSummary saveElectronicInvoiceLoadSummary(ElectronicInvoiceLoadSummary eils) {
1909            return electronicInvoicingDao.saveElectronicInvoiceLoadSummary(eils);
1910        }
1911        
1912        public void setElectronicInvoiceInputFileType(ElectronicInvoiceInputFileType electronicInvoiceInputFileType) {
1913            this.electronicInvoiceInputFileType = electronicInvoiceInputFileType;
1914        }
1915    
1916        public void setMailService(MailService mailService) {
1917            this.mailService = mailService;
1918        }
1919    
1920        public void setElectronicInvoicingDao(ElectronicInvoicingDao electronicInvoicingDao) {
1921            this.electronicInvoicingDao = electronicInvoicingDao;
1922        }
1923        
1924        public void setBatchInputFileService(BatchInputFileService batchInputFileService) {
1925            this.batchInputFileService = batchInputFileService;
1926        }
1927    
1928        public void setElectronicInvoiceMatchingService(ElectronicInvoiceMatchingService matchingService) {
1929            this.matchingService = matchingService;
1930        }
1931    
1932        public void setVendorService(VendorService vendorService) {
1933            this.vendorService = vendorService;
1934        }
1935    
1936        public void setPurchaseOrderService(PurchaseOrderService purchaseOrderService) {
1937            this.purchaseOrderService = purchaseOrderService;
1938        }
1939    
1940        public void setPaymentRequestService(PaymentRequestService paymentRequestService) {
1941            this.paymentRequestService = paymentRequestService;
1942        }
1943        
1944        public void setKualiConfigurationService(KualiConfigurationService kualiConfigurationService) {
1945            this.kualiConfigurationService = kualiConfigurationService;
1946        }
1947    
1948        public void setDateTimeService(DateTimeService dateTimeService) {
1949            this.dateTimeService = dateTimeService;
1950        }
1951    
1952        public void setParameterService(ParameterService parameterService) {
1953            this.parameterService = parameterService;
1954        }
1955        
1956    }
1957