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