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