/*
* The Kuali Financial System, a comprehensive financial management system for higher education.
*
* Copyright 2005-2014 The Kuali Foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kuali.kfs.module.purap.service.impl;
import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;
import org.kuali.kfs.gl.service.impl.StringHelper;
import org.kuali.kfs.module.purap.PurapConstants;
import org.kuali.kfs.module.purap.PurapKeyConstants;
import org.kuali.kfs.module.purap.PurapParameterConstants;
import org.kuali.kfs.module.purap.batch.ElectronicInvoiceInputFileType;
import org.kuali.kfs.module.purap.batch.ElectronicInvoiceStep;
import org.kuali.kfs.module.purap.businessobject.ElectronicInvoice;
import org.kuali.kfs.module.purap.businessobject.ElectronicInvoiceItem;
import org.kuali.kfs.module.purap.businessobject.ElectronicInvoiceLoad;
import org.kuali.kfs.module.purap.businessobject.ElectronicInvoiceLoadSummary;
import org.kuali.kfs.module.purap.businessobject.ElectronicInvoiceOrder;
import org.kuali.kfs.module.purap.businessobject.ElectronicInvoiceRejectReason;
import org.kuali.kfs.module.purap.businessobject.ElectronicInvoiceRejectReasonType;
import org.kuali.kfs.module.purap.businessobject.ItemType;
import org.kuali.kfs.module.purap.businessobject.PaymentRequestItem;
import org.kuali.kfs.module.purap.businessobject.PurApItem;
import org.kuali.kfs.module.purap.dataaccess.ElectronicInvoicingDao;
import org.kuali.kfs.module.purap.document.ElectronicInvoiceRejectDocument;
import org.kuali.kfs.module.purap.document.PaymentRequestDocument;
import org.kuali.kfs.module.purap.document.PurchaseOrderDocument;
import org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument;
import org.kuali.kfs.module.purap.document.RequisitionDocument;
import org.kuali.kfs.module.purap.document.service.AccountsPayableService;
import org.kuali.kfs.module.purap.document.service.PaymentRequestService;
import org.kuali.kfs.module.purap.document.service.PurchaseOrderService;
import org.kuali.kfs.module.purap.document.service.RequisitionService;
import org.kuali.kfs.module.purap.document.validation.event.AttributedCalculateAccountsPayableEvent;
import org.kuali.kfs.module.purap.document.validation.event.AttributedPaymentRequestForEInvoiceEvent;
import org.kuali.kfs.module.purap.exception.CxmlParseException;
import org.kuali.kfs.module.purap.exception.PurError;
import org.kuali.kfs.module.purap.service.ElectronicInvoiceHelperService;
import org.kuali.kfs.module.purap.service.ElectronicInvoiceMatchingService;
import org.kuali.kfs.module.purap.util.ElectronicInvoiceUtils;
import org.kuali.kfs.module.purap.util.ExpiredOrClosedAccountEntry;
import org.kuali.kfs.sys.batch.InitiateDirectoryBase;
import org.kuali.kfs.sys.batch.service.BatchInputFileService;
import org.kuali.kfs.sys.businessobject.Bank;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.exception.ParseException;
import org.kuali.kfs.sys.service.BankService;
import org.kuali.kfs.sys.service.NonTransactional;
import org.kuali.kfs.vnd.businessobject.VendorDetail;
import org.kuali.kfs.vnd.document.service.VendorService;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.mail.MailMessage;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.coreservice.framework.parameter.ParameterService;
import org.kuali.rice.kew.api.exception.WorkflowException;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kim.api.services.KimApiServiceLocator;
import org.kuali.rice.kns.service.DataDictionaryService;
import org.kuali.rice.kns.util.KNSGlobalVariables;
import org.kuali.rice.krad.bo.Attachment;
import org.kuali.rice.krad.bo.DocumentHeader;
import org.kuali.rice.krad.bo.Note;
import org.kuali.rice.krad.bo.PersistableBusinessObject;
import org.kuali.rice.krad.exception.ValidationException;
import org.kuali.rice.krad.service.AttachmentService;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.kuali.rice.krad.service.DocumentService;
import org.kuali.rice.krad.service.KualiRuleService;
import org.kuali.rice.krad.service.MailService;
import org.kuali.rice.krad.service.NoteService;
import org.kuali.rice.krad.util.ErrorMessage;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.KRADPropertyConstants;
import org.kuali.rice.krad.util.ObjectUtils;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.AutoPopulatingList;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* This is a helper service to parse electronic invoice file, match it with a PO and create PREQs based on the eInvoice. Also, it
* provides helper methods to the reject document to match it with a PO and create PREQ.
*/
public class ElectronicInvoiceHelperServiceImpl extends InitiateDirectoryBase implements ElectronicInvoiceHelperService {
private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ElectronicInvoiceHelperServiceImpl.class);
protected final String UNKNOWN_DUNS_IDENTIFIER = "Unknown";
protected final String INVOICE_FILE_MIME_TYPE = "text/xml";
private StringBuffer emailTextErrorList;
protected ElectronicInvoiceInputFileType electronicInvoiceInputFileType;
protected MailService mailService;
protected ElectronicInvoiceMatchingService matchingService;
protected ElectronicInvoicingDao electronicInvoicingDao;
protected BatchInputFileService batchInputFileService;
protected VendorService vendorService;
protected PurchaseOrderService purchaseOrderService;
protected PaymentRequestService paymentRequestService;
protected ConfigurationService kualiConfigurationService;
protected DateTimeService dateTimeService;
protected ParameterService parameterService;
@Override
@NonTransactional
public ElectronicInvoiceLoad loadElectronicInvoices() {
//add a step to check for directory paths
prepareDirectories(getRequiredDirectoryNames());
String rejectDirName = getRejectDirName();
String acceptDirName = getAcceptDirName();
emailTextErrorList = new StringBuffer();
boolean moveFiles = SpringContext.getBean(ParameterService.class).getParameterValueAsBoolean(ElectronicInvoiceStep.class, PurapParameterConstants.ElectronicInvoiceParameters.FILE_MOVE_AFTER_LOAD_IND);
int failedCnt = 0;
if (LOG.isInfoEnabled()){
LOG.info("Invoice Base Directory - " + electronicInvoiceInputFileType.getDirectoryPath());
LOG.info("Invoice Accept Directory - " + acceptDirName);
LOG.info("Invoice Reject Directory - " + rejectDirName);
LOG.info("Is moving files allowed - " + moveFiles);
}
if (StringUtils.isBlank(rejectDirName)) {
throw new RuntimeException("Reject directory name should not be empty");
}
if (StringUtils.isBlank(acceptDirName)) {
throw new RuntimeException("Accept directory name should not be empty");
}
File[] filesToBeProcessed = getFilesToBeProcessed();
ElectronicInvoiceLoad eInvoiceLoad = new ElectronicInvoiceLoad();
if (filesToBeProcessed == null ||
filesToBeProcessed.length == 0) {
StringBuffer mailText = new StringBuffer();
mailText.append("\n\n");
mailText.append(PurapConstants.ElectronicInvoice.NO_FILES_PROCESSED_EMAIL_MESSAGE);
mailText.append("\n\n");
sendSummary(mailText);
return eInvoiceLoad;
}
try {
/**
* Create, if not there
*/
FileUtils.forceMkdir(new File(acceptDirName));
FileUtils.forceMkdir(new File(rejectDirName));
}catch (IOException e) {
throw new RuntimeException(e);
}
if (LOG.isInfoEnabled()){
LOG.info(filesToBeProcessed.length + " file(s) available for processing");
}
StringBuilder emailMsg = new StringBuilder();
for (int i = 0; i < filesToBeProcessed.length; i++) {
File xmlFile = filesToBeProcessed[i];
LOG.info("Processing " + xmlFile.getName() + "....");
byte[] modifiedXML = null;
//process only if file exists and not empty
if (xmlFile.length() != 0L) {
modifiedXML = addNamespaceDefinition(eInvoiceLoad, xmlFile);
}
boolean isRejected = false;
if (modifiedXML == null){//Not able to parse the xml
isRejected = true;
} else {
try {
isRejected = processElectronicInvoice(eInvoiceLoad, xmlFile, modifiedXML);
} catch (Exception e) {
String msg = xmlFile.getName() + "\n";
LOG.error(msg);
//since getMessage() is empty we'll compose the stack trace and nicely format it.
StackTraceElement[] elements = e.getStackTrace();
StringBuffer trace = new StringBuffer();
trace.append(e.getClass().getName());
if (e.getMessage() != null) {
trace.append(": ");
trace.append(e.getMessage());
}
trace.append("\n");
for (int j = 0; j < elements.length; ++j) {
StackTraceElement element = elements[j];
trace.append(" at ");
trace.append(describeStackTraceElement(element));
trace.append("\n");
}
LOG.error(trace);
emailMsg.append(msg);
msg += "\n--------------------------------------------------------------------------------------\n" + trace;
logProcessElectronicInvoiceError(msg);
failedCnt++;
/**
* Clear the error map, so that subsequent EIRT routing isn't prevented since rice
* is throwing a ValidationException if the error map is not empty before routing the doc.
*/
GlobalVariables.getMessageMap().clearErrorMessages();
//Do not execute rest of code below
continue;
}
}
/**
* If there is a single order has rejects and the remainings are accepted in a invoice file,
* then the entire file has been moved to the reject dir.
*/
if (isRejected) {
if (LOG.isInfoEnabled()){
LOG.info(xmlFile.getName() + " has been rejected");
}
if (moveFiles) {
if (LOG.isInfoEnabled()){
LOG.info(xmlFile.getName() + " has been marked to move to " + rejectDirName);
}
eInvoiceLoad.addRejectFileToMove(xmlFile, rejectDirName);
}
} else {
if (LOG.isInfoEnabled()){
LOG.info(xmlFile.getName() + " has been accepted");
}
if (moveFiles) {
if (!moveFile(xmlFile, acceptDirName)) {
String msg = xmlFile.getName() + " unable to move";
LOG.error(msg);
throw new PurError(msg);
}
}
}
if (!moveFiles){
String fullPath = FilenameUtils.getFullPath(xmlFile.getAbsolutePath());
String fileName = FilenameUtils.getBaseName(xmlFile.getAbsolutePath());
File processedFile = new File(fullPath + File.separator + fileName + ".processed");
try {
FileUtils.touch(processedFile);
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
// delete the .done file
deleteDoneFile(xmlFile);
}
emailTextErrorList.append("\nFAILED FILES\n");
emailTextErrorList.append("-----------------------------------------------------------\n\n");
emailTextErrorList.append(emailMsg);
emailTextErrorList.append("\nTOTAL COUNT\n");
emailTextErrorList.append("===========================\n");
emailTextErrorList.append(" "+failedCnt+" FAILED\n");
emailTextErrorList.append("===========================\n");
StringBuffer summaryText = saveLoadSummary(eInvoiceLoad);
StringBuffer finalText = new StringBuffer();
finalText.append(summaryText);
finalText.append("\n");
finalText.append(emailTextErrorList);
sendSummary(finalText);
LOG.info("Processing completed");
return eInvoiceLoad;
}
protected File [] getFilesToBeProcessed() {
File [] filesToBeProcessed;
String baseDirName = getBaseDirName();
File baseDir = new File(baseDirName);
if (!baseDir.exists()){
throw new RuntimeException("Base dir [" + baseDirName + "] doesn't exists in the system");
}
filesToBeProcessed = baseDir.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
String fullPath = FilenameUtils.getFullPath(file.getAbsolutePath());
String fileName = FilenameUtils.getBaseName(file.getAbsolutePath());
File processedFile = new File(fullPath + File.separator + fileName + ".processed");
return (!file.isDirectory() &&
file.getName().endsWith(electronicInvoiceInputFileType.getFileExtension()) &&
!processedFile.exists());
}
});
return filesToBeProcessed;
}
protected void logProcessElectronicInvoiceError(String msg) {
File file = new File(electronicInvoiceInputFileType.getReportPath() + "/" +
electronicInvoiceInputFileType.getReportPrefix() + "_" +
dateTimeService.toDateTimeStringForFilename(dateTimeService.getCurrentDate()) + "." +
electronicInvoiceInputFileType.getReportExtension());
BufferedWriter writer = null;
try {
writer = new BufferedWriter(new PrintWriter(file));
writer.write(msg);
writer.newLine();
}
catch (FileNotFoundException e) {
LOG.error(file + " not found " + " " + e.getMessage());
throw new RuntimeException(file + " not found " + e.getMessage(), e);
}
catch (IOException e) {
LOG.error("Error writing to BufferedWriter " + e.getMessage());
throw new RuntimeException("Error writing to BufferedWriter " + e.getMessage(), e);
}
finally {
try {
writer.flush();
writer.close();
}
catch (Exception e) {}
}
}
/**
* @param element
* @return String describing the given StackTraceElement
*/
private static String describeStackTraceElement(StackTraceElement element) {
StringBuffer description = new StringBuffer();
if (element == null) {
description.append("invalid (null) element");
}
description.append(element.getClassName());
description.append(".");
description.append(element.getMethodName());
description.append("(");
description.append(element.getFileName());
description.append(":");
description.append(element.getLineNumber());
description.append(")");
return description.toString();
}
protected byte[] addNamespaceDefinition(ElectronicInvoiceLoad eInvoiceLoad,
File invoiceFile) {
boolean result = true;
if (LOG.isInfoEnabled()){
LOG.info("Adding namespace definition");
}
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
builderFactory.setValidating(false); // It's not needed to validate here
builderFactory.setIgnoringElementContentWhitespace(true);
DocumentBuilder builder = null;
try {
builder = builderFactory.newDocumentBuilder(); // Create the parser
} catch(ParserConfigurationException e) {
LOG.error("Error getting document builder - " + e.getMessage());
throw new RuntimeException(e);
}
Document xmlDoc = null;
try {
xmlDoc = builder.parse(invoiceFile);
} catch(Exception e) {
if (LOG.isInfoEnabled()){
LOG.info("Error parsing the file - " + e.getMessage());
}
rejectElectronicInvoiceFile(eInvoiceLoad, UNKNOWN_DUNS_IDENTIFIER, invoiceFile, e.getMessage(),PurapConstants.ElectronicInvoice.FILE_FORMAT_INVALID);
return null;
}
Node node = xmlDoc.getDocumentElement();
Element element = (Element)node;
String xmlnsValue = element.getAttribute("xmlns");
String xmlnsXsiValue = element.getAttribute("xmlns:xsi");
File namespaceAddedFile = getInvoiceFile(invoiceFile.getName());
if (StringUtils.equals(xmlnsValue, "http://www.kuali.org/kfs/purap/electronicInvoice") &&
StringUtils.equals(xmlnsXsiValue, "http://www.w3.org/2001/XMLSchema-instance")){
if (LOG.isInfoEnabled()){
LOG.info("xmlns and xmlns:xsi attributes already exists in the invoice xml");
}
}else{
element.setAttribute("xmlns", "http://www.kuali.org/kfs/purap/electronicInvoice");
element.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
}
OutputFormat outputFormat = new OutputFormat(xmlDoc);
outputFormat.setOmitDocumentType(true);
ByteArrayOutputStream out = new ByteArrayOutputStream();
XMLSerializer serializer = new XMLSerializer( out,outputFormat );
try {
serializer.asDOMSerializer();
serializer.serialize( xmlDoc.getDocumentElement());
}
catch (IOException e) {
throw new RuntimeException(e);
}
if (LOG.isInfoEnabled()){
LOG.info("Namespace validation completed");
}
return out.toByteArray();
}
/**
* This method processes a single electronic invoice file
*
* @param eInvoiceLoad the load summary to be modified
* @return boolean where true means there has been some type of reject
*/
@Transactional
protected boolean processElectronicInvoice(ElectronicInvoiceLoad eInvoiceLoad,
File invoiceFile,
byte[] xmlAsBytes) {
ElectronicInvoice eInvoice = null;
try {
eInvoice = loadElectronicInvoice(xmlAsBytes);
}catch (CxmlParseException e) {
LOG.info("Error loading file - " + e.getMessage());
rejectElectronicInvoiceFile(eInvoiceLoad, UNKNOWN_DUNS_IDENTIFIER, invoiceFile, e.getMessage(),PurapConstants.ElectronicInvoice.FILE_FORMAT_INVALID);
return true;
}
eInvoice.setFileName(invoiceFile.getName());
boolean isCompleteFailure = checkForCompleteFailure(eInvoiceLoad,eInvoice,invoiceFile);
if (isCompleteFailure){
return true;
}
setVendorDUNSNumber(eInvoice);
setVendorDetails(eInvoice);
Map itemTypeMappings = getItemTypeMappings(eInvoice.getVendorHeaderID(),eInvoice.getVendorDetailID());
Map kualiItemTypes = getKualiItemTypes();
if (LOG.isInfoEnabled()){
if (itemTypeMappings != null && itemTypeMappings.size() > 0){
LOG.info("Item mappings found");
}
}
boolean validateHeader = true;
for (ElectronicInvoiceOrder order : eInvoice.getInvoiceDetailOrders()) {
String poID = order.getOrderReferenceOrderID();
PurchaseOrderDocument po = null;
if (NumberUtils.isDigits(StringUtils.defaultString(poID))){
po = purchaseOrderService.getCurrentPurchaseOrder(new Integer(poID));
if (po != null){
order.setInvoicePurchaseOrderID(poID);
order.setPurchaseOrderID(po.getPurapDocumentIdentifier());
order.setPurchaseOrderCampusCode(po.getDeliveryCampusCode());
if (LOG.isInfoEnabled()){
LOG.info("PO matching Document found");
}
}
}
ElectronicInvoiceOrderHolder orderHolder = new ElectronicInvoiceOrderHolder(eInvoice,order,po,itemTypeMappings,kualiItemTypes,validateHeader);
matchingService.doMatchingProcess(orderHolder);
if (orderHolder.isInvoiceRejected()){
ElectronicInvoiceRejectDocument rejectDocument = createRejectDocument(eInvoice, order,eInvoiceLoad);
if (orderHolder.getAccountsPayablePurchasingDocumentLinkIdentifier() != null){
rejectDocument.setAccountsPayablePurchasingDocumentLinkIdentifier(orderHolder.getAccountsPayablePurchasingDocumentLinkIdentifier());
}
String dunsNumber = StringUtils.isEmpty(eInvoice.getDunsNumber()) ?
UNKNOWN_DUNS_IDENTIFIER :
eInvoice.getDunsNumber();
ElectronicInvoiceLoadSummary loadSummary = getOrCreateLoadSummary(eInvoiceLoad, dunsNumber);
loadSummary.addFailedInvoiceOrder(rejectDocument.getTotalAmount(),eInvoice);
eInvoiceLoad.insertInvoiceLoadSummary(loadSummary);
}else{
PaymentRequestDocument preqDoc = createPaymentRequest(orderHolder);
if (orderHolder.isInvoiceRejected()){
/**
* This is required. If there is anything in the error map, then it's not possible to route the doc since the rice
* is throwing error if errormap is not empty before routing the doc.
*/
GlobalVariables.getMessageMap().clearErrorMessages();
ElectronicInvoiceRejectDocument rejectDocument = createRejectDocument(eInvoice, order,eInvoiceLoad);
if (orderHolder.getAccountsPayablePurchasingDocumentLinkIdentifier() != null){
rejectDocument.setAccountsPayablePurchasingDocumentLinkIdentifier(orderHolder.getAccountsPayablePurchasingDocumentLinkIdentifier());
}
ElectronicInvoiceLoadSummary loadSummary = getOrCreateLoadSummary(eInvoiceLoad, eInvoice.getDunsNumber());
loadSummary.addFailedInvoiceOrder(rejectDocument.getTotalAmount(),eInvoice);
eInvoiceLoad.insertInvoiceLoadSummary(loadSummary);
}else{
ElectronicInvoiceLoadSummary loadSummary = getOrCreateLoadSummary(eInvoiceLoad, eInvoice.getDunsNumber());
loadSummary.addSuccessfulInvoiceOrder(preqDoc.getTotalDollarAmount(),eInvoice);
eInvoiceLoad.insertInvoiceLoadSummary(loadSummary);
}
}
validateHeader = false;
}
return eInvoice.isFileRejected();
}
protected void setVendorDUNSNumber(ElectronicInvoice eInvoice) {
String dunsNumber = null;
if (StringUtils.equals(eInvoice.getCxmlHeader().getFromDomain(),"DUNS")) {
dunsNumber = eInvoice.getCxmlHeader().getFromIdentity();
}else if (StringUtils.equals(eInvoice.getCxmlHeader().getSenderDomain(),"DUNS")) {
dunsNumber = eInvoice.getCxmlHeader().getSenderIdentity();
}
if (StringUtils.isNotEmpty((dunsNumber))) {
if (LOG.isInfoEnabled()){
LOG.info("Setting Vendor DUNS number - " + dunsNumber);
}
eInvoice.setDunsNumber(dunsNumber);
}
}
protected void setVendorDetails(ElectronicInvoice eInvoice){
if (StringUtils.isNotEmpty(eInvoice.getDunsNumber())){
VendorDetail vendorDetail = vendorService.getVendorByDunsNumber(eInvoice.getDunsNumber());
if (vendorDetail != null) {
if (LOG.isInfoEnabled()){
LOG.info("Vendor match found - " + vendorDetail.getVendorNumber());
}
eInvoice.setVendorHeaderID(vendorDetail.getVendorHeaderGeneratedIdentifier());
eInvoice.setVendorDetailID(vendorDetail.getVendorDetailAssignedIdentifier());
eInvoice.setVendorName(vendorDetail.getVendorName());
}else{
eInvoice.setVendorHeaderID(null);
eInvoice.setVendorDetailID(null);
eInvoice.setVendorName(null);
}
}
}
protected void validateVendorDetails(ElectronicInvoiceRejectDocument rejectDocument){
boolean vendorFound = false;
if (StringUtils.isNotEmpty(rejectDocument.getVendorDunsNumber())){
VendorDetail vendorDetail = vendorService.getVendorByDunsNumber(rejectDocument.getVendorDunsNumber());
if (vendorDetail != null) {
if (LOG.isInfoEnabled()){
LOG.info("Vendor [" + vendorDetail.getVendorNumber() + "] match found for the DUNS - " + rejectDocument.getVendorDunsNumber());
}
rejectDocument.setVendorHeaderGeneratedIdentifier(vendorDetail.getVendorHeaderGeneratedIdentifier());
rejectDocument.setVendorDetailAssignedIdentifier(vendorDetail.getVendorDetailAssignedIdentifier());
rejectDocument.setVendorDetail(vendorDetail);
vendorFound = true;
}
}
if (!vendorFound){
rejectDocument.setVendorHeaderGeneratedIdentifier(null);
rejectDocument.setVendorDetailAssignedIdentifier(null);
rejectDocument.setVendorDetail(null);
}
String newDocumentDesc = generateRejectDocumentDescription(rejectDocument);
rejectDocument.getDocumentHeader().setDocumentDescription(newDocumentDesc);
}
protected Map getItemTypeMappings(Integer vendorHeaderId,
Integer vendorDetailId) {
Map itemTypeMappings = null;
if (vendorHeaderId != null && vendorDetailId != null) {
String vendorNumber = getVendorNumber(vendorHeaderId,vendorDetailId);
itemTypeMappings = electronicInvoicingDao.getItemMappingMap(vendorHeaderId,vendorDetailId);
}
if (itemTypeMappings == null || itemTypeMappings.isEmpty()){
itemTypeMappings = electronicInvoicingDao.getDefaultItemMappingMap();
}
return itemTypeMappings;
}
protected String getVendorNumber(Integer vendorHeaderId,
Integer vendorDetailId ){
if (vendorHeaderId != null && vendorDetailId != null) {
VendorDetail forVendorNo = new VendorDetail();
forVendorNo.setVendorHeaderGeneratedIdentifier(vendorHeaderId);
forVendorNo.setVendorDetailAssignedIdentifier(vendorDetailId);
return forVendorNo.getVendorNumber();
}else{
return null;
}
}
protected Map<String, ItemType> getKualiItemTypes(){
Collection<ItemType> collection = SpringContext.getBean(BusinessObjectService.class).findAll(ItemType.class);
Map kualiItemTypes = new HashMap<String, ItemType>();
if (collection == null || collection.size() == 0){
throw new RuntimeException("Kauli Item types not available");
}else{
if (collection != null){
ItemType[] itemTypes = new ItemType[collection.size()];
collection.toArray(itemTypes);
for (int i = 0; i < itemTypes.length; i++) {
kualiItemTypes.put(itemTypes[i].getItemTypeCode(),itemTypes[i]);
}
}
}
return kualiItemTypes;
}
protected boolean checkForCompleteFailure(ElectronicInvoiceLoad electronicInvoiceLoad,
ElectronicInvoice electronicInvoice,
File invoiceFile){
if (LOG.isInfoEnabled()){
LOG.info("Checking for complete failure...");
}
if (electronicInvoice.getInvoiceDetailRequestHeader().isHeaderInvoiceIndicator()) {
rejectElectronicInvoiceFile(electronicInvoiceLoad, UNKNOWN_DUNS_IDENTIFIER, invoiceFile,PurapConstants.ElectronicInvoice.HEADER_INVOICE_IND_ON);
return true;
}
if (electronicInvoice.getInvoiceDetailOrders().size() < 1) {
rejectElectronicInvoiceFile(electronicInvoiceLoad, UNKNOWN_DUNS_IDENTIFIER, invoiceFile,PurapConstants.ElectronicInvoice.INVOICE_ORDERS_NOT_FOUND);
return true;
}
//it says - Future Release - Enter valid location for Customer Number from E-Invoice
//mappingService.getInvoiceCustomerNumber() doesnt have any implementation
// electronicInvoice.setCustomerNumber(mappingService.getInvoiceCustomerNumber(electronicInvoice));
for (Iterator orderIter = electronicInvoice.getInvoiceDetailOrders().iterator(); orderIter.hasNext();) {
ElectronicInvoiceOrder invoiceOrder = (ElectronicInvoiceOrder) orderIter.next();
for (Iterator itemIter = invoiceOrder.getInvoiceItems().iterator(); itemIter.hasNext();) {
ElectronicInvoiceItem invoiceItem = (ElectronicInvoiceItem) itemIter.next();
if (invoiceItem != null) {
invoiceItem.setCatalogNumber(invoiceItem.getReferenceItemIDSupplierPartID());
}
}
}
if (LOG.isInfoEnabled()){
LOG.info("No Complete failure");
}
return false;
}
protected ElectronicInvoiceRejectReasonType getRejectReasonType(String rejectReasonTypeCode){
return matchingService.getElectronicInvoiceRejectReasonType(rejectReasonTypeCode);
}
protected void rejectElectronicInvoiceFile(ElectronicInvoiceLoad eInvoiceLoad,
String fileDunsNumber,
File filename,
String rejectReasonTypeCode) {
rejectElectronicInvoiceFile(eInvoiceLoad,fileDunsNumber,filename,null,rejectReasonTypeCode);
}
protected void rejectElectronicInvoiceFile(ElectronicInvoiceLoad eInvoiceLoad,
String fileDunsNumber,
File invoiceFile,
String extraDescription,
String rejectReasonTypeCode) {
if (LOG.isInfoEnabled()){
LOG.info("Rejecting the entire invoice file - " + invoiceFile.getName());
}
ElectronicInvoiceLoadSummary eInvoiceLoadSummary = getOrCreateLoadSummary(eInvoiceLoad, fileDunsNumber);
eInvoiceLoadSummary.addFailedInvoiceOrder();
eInvoiceLoad.insertInvoiceLoadSummary(eInvoiceLoadSummary);
ElectronicInvoiceRejectDocument eInvoiceRejectDocument = null;
try {
eInvoiceRejectDocument = (ElectronicInvoiceRejectDocument) SpringContext.getBean(DocumentService.class).getNewDocument("EIRT");
eInvoiceRejectDocument.setInvoiceProcessTimestamp(SpringContext.getBean(DateTimeService.class).getCurrentTimestamp());
eInvoiceRejectDocument.setVendorDunsNumber(fileDunsNumber);
eInvoiceRejectDocument.setDocumentCreationInProgress(true);
if (invoiceFile != null){
eInvoiceRejectDocument.setInvoiceFileName(invoiceFile.getName());
}
List<ElectronicInvoiceRejectReason> list = new ArrayList<ElectronicInvoiceRejectReason>(1);
String message = "Complete failure document has been created for the Invoice with Filename '" + invoiceFile.getName() + "' due to the following error:\n";
emailTextErrorList.append(message);
ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(rejectReasonTypeCode,extraDescription, invoiceFile.getName());
list.add(rejectReason);
emailTextErrorList.append(" - " + rejectReason.getInvoiceRejectReasonDescription());
emailTextErrorList.append("\n\n");
eInvoiceRejectDocument.setInvoiceRejectReasons(list);
eInvoiceRejectDocument.getDocumentHeader().setDocumentDescription("Complete failure");
// KFSCNTRB-1369: Need to Save document
SpringContext.getBean(DocumentService.class).saveDocument(eInvoiceRejectDocument);
String noteText = "Invoice file";
attachInvoiceXMLWithRejectDoc(eInvoiceRejectDocument,invoiceFile,noteText);
eInvoiceLoad.addInvoiceReject(eInvoiceRejectDocument);
}catch (WorkflowException e) {
throw new RuntimeException(e);
}
if (LOG.isInfoEnabled()){
LOG.info("Complete failure document has been created (DocNo:" + eInvoiceRejectDocument.getDocumentNumber() + ")");
}
}
protected void attachInvoiceXMLWithRejectDoc(ElectronicInvoiceRejectDocument eInvoiceRejectDocument,
File attachmentFile,
String noteText){
Note note = null;
try {
note = SpringContext.getBean(DocumentService.class).createNoteFromDocument(eInvoiceRejectDocument, noteText);
// KFSCNTRB-1369: Can't add note without remoteObjectIdentifier
note.setRemoteObjectIdentifier(eInvoiceRejectDocument.getDocumentHeader().getObjectId());
}catch (Exception e1) {
throw new RuntimeException("Unable to create note from document: ", e1);
}
String attachmentType = null;
BufferedInputStream fileStream = null;
try {
fileStream = new BufferedInputStream(new FileInputStream(attachmentFile));
}catch (FileNotFoundException e) {
LOG.error( "Exception opening attachment file", e);
}
Attachment attachment = null;
try {
attachment = SpringContext.getBean(AttachmentService.class).createAttachment(eInvoiceRejectDocument.getNoteTarget(), attachmentFile.getName(),INVOICE_FILE_MIME_TYPE , (int)attachmentFile.length(), fileStream, attachmentType);
} catch (IOException e) {
throw new RuntimeException("Unable to create attachment", e);
}finally{
if (fileStream != null){
try {
fileStream.close();
}catch (IOException e) {
LOG.error( "Exception closing file", e);
}
}
}
note.setAttachment(attachment);
attachment.setNote(note);
SpringContext.getBean(NoteService.class).save(note);
}
@NonTransactional
public ElectronicInvoiceRejectDocument createRejectDocument(ElectronicInvoice eInvoice,
ElectronicInvoiceOrder electronicInvoiceOrder,
ElectronicInvoiceLoad eInvoiceLoad) {
if (LOG.isInfoEnabled()){
LOG.info("Creating reject document [DUNS=" + eInvoice.getDunsNumber() + ",POID=" + electronicInvoiceOrder.getInvoicePurchaseOrderID() + "]");
}
ElectronicInvoiceRejectDocument eInvoiceRejectDocument;
try {
eInvoiceRejectDocument = (ElectronicInvoiceRejectDocument) SpringContext.getBean(DocumentService.class).getNewDocument("EIRT");
eInvoiceRejectDocument.setInvoiceProcessTimestamp(SpringContext.getBean(DateTimeService.class).getCurrentTimestamp());
String rejectdocDesc = generateRejectDocumentDescription(eInvoice,electronicInvoiceOrder);
eInvoiceRejectDocument.getDocumentHeader().setDocumentDescription(rejectdocDesc);
eInvoiceRejectDocument.setDocumentCreationInProgress(true);
eInvoiceRejectDocument.setFileLevelData(eInvoice);
eInvoiceRejectDocument.setInvoiceOrderLevelData(eInvoice, electronicInvoiceOrder);
//MSU fix
SpringContext.getBean(DocumentService.class).saveDocument(eInvoiceRejectDocument);
String noteText = "Invoice file";
attachInvoiceXMLWithRejectDoc(eInvoiceRejectDocument, getInvoiceFile(eInvoice.getFileName()), noteText);
eInvoiceLoad.addInvoiceReject(eInvoiceRejectDocument);
}catch (WorkflowException e) {
throw new RuntimeException(e);
}
if (LOG.isInfoEnabled()){
LOG.info("Reject document has been created (DocNo=" + eInvoiceRejectDocument.getDocumentNumber() + ")");
}
emailTextErrorList.append("DUNS Number - " + eInvoice.getDunsNumber() + " " +eInvoice.getVendorName()+ ":\n");
emailTextErrorList.append("An Invoice from file '" + eInvoice.getFileName() + "' has been rejected due to the following error(s):\n");
int index = 1;
for (ElectronicInvoiceRejectReason reason : eInvoiceRejectDocument.getInvoiceRejectReasons()) {
emailTextErrorList.append(" - " + reason.getInvoiceRejectReasonDescription() + "\n");
addRejectReasonsToNote("Reject Reason " + index + ". " + reason.getInvoiceRejectReasonDescription(), eInvoiceRejectDocument);
index++;
}
emailTextErrorList.append("\n");
return eInvoiceRejectDocument;
}
protected void addRejectReasonsToNote(String rejectReasons, ElectronicInvoiceRejectDocument eInvoiceRejectDocument){
try {
Note note = SpringContext.getBean(DocumentService.class).createNoteFromDocument(eInvoiceRejectDocument, rejectReasons);
// KFSCNTRB-1369: Can't add note without remoteObjectIdentifier
note.setRemoteObjectIdentifier(eInvoiceRejectDocument.getDocumentHeader().getObjectId());
PersistableBusinessObject noteParent = eInvoiceRejectDocument.getNoteTarget();
SpringContext.getBean(NoteService.class).save(note);
}catch (Exception e) {
LOG.error("Error creating reject reason note - " + e.getMessage());
}
}
protected String generateRejectDocumentDescription(ElectronicInvoice eInvoice,
ElectronicInvoiceOrder electronicInvoiceOrder){
String poID = StringUtils.isEmpty(electronicInvoiceOrder.getInvoicePurchaseOrderID()) ?
"UNKNOWN" :
electronicInvoiceOrder.getInvoicePurchaseOrderID();
String vendorName = StringUtils.isEmpty(eInvoice.getVendorName()) ?
"UNKNOWN" :
eInvoice.getVendorName();
String description = "PO: " + poID + " Vendor: " + vendorName;
return checkDescriptionLengthAndStripIfNeeded(description);
}
protected String generateRejectDocumentDescription(ElectronicInvoiceRejectDocument rejectDoc) {
String poID = StringUtils.isEmpty(rejectDoc.getInvoicePurchaseOrderNumber()) ?
"UNKNOWN" :
rejectDoc.getInvoicePurchaseOrderNumber();
String vendorName = "UNKNOWN";
if (rejectDoc.getVendorDetail() != null){
vendorName = rejectDoc.getVendorDetail().getVendorName();
}
String description = "PO: " + poID + " Vendor: " + vendorName;
return checkDescriptionLengthAndStripIfNeeded(description);
}
protected String checkDescriptionLengthAndStripIfNeeded(String description){
int noteTextMaxLength = SpringContext.getBean(DataDictionaryService.class).getAttributeMaxLength(DocumentHeader.class, KRADPropertyConstants.DOCUMENT_DESCRIPTION).intValue();
if (noteTextMaxLength < description.length()) {
description = description.substring(0, noteTextMaxLength);
}
return description;
}
@NonTransactional
public ElectronicInvoiceLoadSummary getOrCreateLoadSummary(ElectronicInvoiceLoad eInvoiceLoad,
String fileDunsNumber){
ElectronicInvoiceLoadSummary eInvoiceLoadSummary;
if (eInvoiceLoad.getInvoiceLoadSummaries().containsKey(fileDunsNumber)) {
eInvoiceLoadSummary = (ElectronicInvoiceLoadSummary) eInvoiceLoad.getInvoiceLoadSummaries().get(fileDunsNumber);
}
else {
eInvoiceLoadSummary = new ElectronicInvoiceLoadSummary(fileDunsNumber);
}
return eInvoiceLoadSummary;
}
@NonTransactional
public ElectronicInvoice loadElectronicInvoice(byte[] xmlAsBytes)
throws CxmlParseException {
if (LOG.isInfoEnabled()){
LOG.info("Loading Invoice File");
}
ElectronicInvoice electronicInvoice = null;
try {
electronicInvoice = (ElectronicInvoice) batchInputFileService.parse(electronicInvoiceInputFileType, xmlAsBytes);
}catch (ParseException e) {
throw new CxmlParseException(e.getMessage());
}
if (LOG.isInfoEnabled()){
LOG.info("Successfully loaded the Invoice File");
}
return electronicInvoice;
}
protected StringBuffer saveLoadSummary(ElectronicInvoiceLoad eInvoiceLoad) {
Map savedLoadSummariesMap = new HashMap();
StringBuffer summaryMessage = new StringBuffer();
for (Iterator iter = eInvoiceLoad.getInvoiceLoadSummaries().keySet().iterator(); iter.hasNext();) {
String dunsNumber = (String) iter.next();
ElectronicInvoiceLoadSummary eInvoiceLoadSummary = (ElectronicInvoiceLoadSummary) eInvoiceLoad.getInvoiceLoadSummaries().get(dunsNumber);
if (!eInvoiceLoadSummary.isEmpty().booleanValue()){
LOG.info("Saving Load Summary for DUNS '" + dunsNumber + "'");
ElectronicInvoiceLoadSummary currentLoadSummary = saveElectronicInvoiceLoadSummary(eInvoiceLoadSummary);
summaryMessage.append("DUNS Number - " + eInvoiceLoadSummary.getVendorDescriptor() + ":\n");
summaryMessage.append(" " + eInvoiceLoadSummary.getInvoiceLoadSuccessCount() + " successfully processed invoices for a total of $ " + eInvoiceLoadSummary.getInvoiceLoadSuccessAmount().doubleValue() + "\n");
summaryMessage.append(" " + eInvoiceLoadSummary.getInvoiceLoadFailCount() + " rejected invoices for an approximate total of $ " + eInvoiceLoadSummary.getInvoiceLoadFailAmount().doubleValue() + "\n");
summaryMessage.append("\n\n");
savedLoadSummariesMap.put(currentLoadSummary.getVendorDunsNumber(), eInvoiceLoadSummary);
} else {
LOG.info("Not saving Load Summary for DUNS '" + dunsNumber + "' because empty indicator is '" + eInvoiceLoadSummary.isEmpty().booleanValue() + "'");
}
}
summaryMessage.append("\n\n");
for (Iterator rejectIter = eInvoiceLoad.getRejectDocuments().iterator(); rejectIter.hasNext();) {
ElectronicInvoiceRejectDocument rejectDoc = (ElectronicInvoiceRejectDocument) rejectIter.next();
routeRejectDocument(rejectDoc,savedLoadSummariesMap);
}
/**
* Even if there is an exception in the reject doc routing, all the files marked as reject will
* be moved to the reject dir
*/
moveFileList(eInvoiceLoad.getRejectFilesToMove());
return summaryMessage;
}
protected void routeRejectDocument(ElectronicInvoiceRejectDocument rejectDoc,
Map savedLoadSummariesMap){
LOG.info("Saving Invoice Reject for DUNS '" + rejectDoc.getVendorDunsNumber() + "'");
if (savedLoadSummariesMap.containsKey(rejectDoc.getVendorDunsNumber())) {
rejectDoc.setInvoiceLoadSummary((ElectronicInvoiceLoadSummary) savedLoadSummariesMap.get(rejectDoc.getVendorDunsNumber()));
}
else {
rejectDoc.setInvoiceLoadSummary((ElectronicInvoiceLoadSummary) savedLoadSummariesMap.get(UNKNOWN_DUNS_IDENTIFIER));
}
try{
SpringContext.getBean(DocumentService.class).routeDocument(rejectDoc, "Routed by electronic invoice batch job", null);
}
catch (WorkflowException e) {
e.printStackTrace();
}
}
protected void sendSummary(StringBuffer message) {
String fromMailId = SpringContext.getBean(ParameterService.class).getParameterValueAsString(ElectronicInvoiceStep.class, PurapParameterConstants.ElectronicInvoiceParameters.DAILY_SUMMARY_REPORT_FROM_EMAIL_ADDRESS);
List<String> toMailIds = new ArrayList<String>( SpringContext.getBean(ParameterService.class).getParameterValuesAsString(ElectronicInvoiceStep.class, PurapParameterConstants.ElectronicInvoiceParameters.DAILY_SUMMARY_REPORT_TO_EMAIL_ADDRESSES) );
LOG.info("From email address parameter value:"+fromMailId);
LOG.info("To email address parameter value:"+toMailIds);
if (StringUtils.isBlank(fromMailId) || toMailIds.isEmpty()){
LOG.error("From/To mail addresses are empty. Unable to send the message");
}else{
MailMessage mailMessage = new MailMessage();
mailMessage.setFromAddress(fromMailId);
setMessageToAddressesAndSubject(mailMessage,toMailIds);
mailMessage.setMessage(message.toString());
try {
mailService.sendMessage(mailMessage);
}catch (Exception e) {
LOG.error("Invalid email address. Message not sent", e);
}
}
}
protected MailMessage setMessageToAddressesAndSubject(MailMessage message, List<String> toAddressList) {
if (!toAddressList.isEmpty()) {
for (int i = 0; i < toAddressList.size(); i++) {
if (StringUtils.isNotEmpty(toAddressList.get(i))) {
message.addToAddress(toAddressList.get(i).trim());
}
}
}
String mailTitle = "E-Invoice Load Results for " + ElectronicInvoiceUtils.getDateDisplayText(SpringContext.getBean(DateTimeService.class).getCurrentDate());
message.setSubject(mailTitle);
return message;
}
/**
* This method is responsible for the matching process for a reject document
*
* @return true if the matching process is succeed
*/
@Override
@NonTransactional
public boolean doMatchingProcess(ElectronicInvoiceRejectDocument rejectDocument){
/**
* This is needed here since if the user changes the DUNS number.
*/
validateVendorDetails(rejectDocument);
Map itemTypeMappings = getItemTypeMappings(rejectDocument.getVendorHeaderGeneratedIdentifier(),
rejectDocument.getVendorDetailAssignedIdentifier());
Map kualiItemTypes = getKualiItemTypes();
ElectronicInvoiceOrderHolder rejectDocHolder = new ElectronicInvoiceOrderHolder(rejectDocument,itemTypeMappings,kualiItemTypes);
matchingService.doMatchingProcess(rejectDocHolder);
/**
* Once we're through with the matching process, it's needed to check whether it's possible
* to create PREQ for the reject doc
*/
if (!rejectDocHolder.isInvoiceRejected()){
validateInvoiceOrderValidForPREQCreation(rejectDocHolder);
}
// determine which of the reject reasons we should suppress based on the parameter
List<String> ignoreRejectTypes = new ArrayList<String>( parameterService.getParameterValuesAsString(PurapConstants.PURAP_NAMESPACE, "ElectronicInvoiceReject", "SUPPRESS_REJECT_REASON_CODES_ON_EIRT_APPROVAL") );
List<ElectronicInvoiceRejectReason> rejectReasonsToDelete = new ArrayList<ElectronicInvoiceRejectReason>();
for (ElectronicInvoiceRejectReason rejectReason : rejectDocument.getInvoiceRejectReasons()) {
String rejectedReasonTypeCode = rejectReason.getInvoiceRejectReasonTypeCode();
if (StringUtils.isNotBlank(rejectedReasonTypeCode)) {
if (ignoreRejectTypes.contains(rejectedReasonTypeCode)) {
rejectReasonsToDelete.add(rejectReason);
}
}
}
// remove the flagged reject reasons
if (!rejectReasonsToDelete.isEmpty()) {
rejectDocument.getInvoiceRejectReasons().removeAll(rejectReasonsToDelete);
}
// if no reject reasons, then clear error messages
if (rejectDocument.getInvoiceRejectReasons().isEmpty()) {
GlobalVariables.getMessageMap().clearErrorMessages();
}
// this automatically returns false if there are no reject reasons
return !rejectDocHolder.isInvoiceRejected();
}
@Override
@NonTransactional
public boolean createPaymentRequest(ElectronicInvoiceRejectDocument rejectDocument){
if (rejectDocument.getInvoiceRejectReasons().size() > 0){
throw new RuntimeException("Not possible to create payment request since the reject document contains " + rejectDocument.getInvoiceRejectReasons().size() + " rejects");
}
Map itemTypeMappings = getItemTypeMappings(rejectDocument.getVendorHeaderGeneratedIdentifier(),
rejectDocument.getVendorDetailAssignedIdentifier());
Map kualiItemTypes = getKualiItemTypes();
ElectronicInvoiceOrderHolder rejectDocHolder = new ElectronicInvoiceOrderHolder(rejectDocument,itemTypeMappings,kualiItemTypes);
/**
* First, create a new payment request document. Once this document is created, then update the reject document's PREQ_ID field
* with the payment request document identifier. This identifier is used to associate the reject document with the payment request.
*/
PaymentRequestDocument preqDocument = createPaymentRequest(rejectDocHolder);
rejectDocument.setPaymentRequestIdentifier(preqDocument.getPurapDocumentIdentifier());
return !rejectDocHolder.isInvoiceRejected();
}
protected PaymentRequestDocument createPaymentRequest(ElectronicInvoiceOrderHolder orderHolder){
if (LOG.isInfoEnabled()){
LOG.info("Creating Payment Request document");
}
KNSGlobalVariables.getMessageList().clear();
validateInvoiceOrderValidForPREQCreation(orderHolder);
if (LOG.isInfoEnabled()){
if (orderHolder.isInvoiceRejected()){
LOG.info("Not possible to convert einvoice details into payment request");
}else{
LOG.info("Payment request document creation validation succeeded");
}
}
if (orderHolder.isInvoiceRejected()){
return null;
}
PaymentRequestDocument preqDoc = null;
try {
preqDoc = (PaymentRequestDocument) SpringContext.getBean(DocumentService.class).getNewDocument("PREQ");
}
catch (WorkflowException e) {
String extraDescription = "Error=" + e.getMessage();
ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.PREQ_WORKLOW_EXCEPTION,
extraDescription,
orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason);
LOG.error("Error creating Payment request document - " + e.getMessage());
return null;
}
PurchaseOrderDocument poDoc = orderHolder.getPurchaseOrderDocument();
if (poDoc == null){
throw new RuntimeException("Purchase Order document (POId=" + poDoc.getPurapDocumentIdentifier() + ") does not exist in the system");
}
preqDoc.getDocumentHeader().setDocumentDescription(generatePREQDocumentDescription(poDoc));
try {
preqDoc.updateAndSaveAppDocStatus(PurapConstants.PaymentRequestStatuses.APPDOC_IN_PROCESS);
} catch (WorkflowException we) {
throw new RuntimeException("Unable to save route status data for document: " + preqDoc.getDocumentNumber(), we);
}
preqDoc.setInvoiceDate(orderHolder.getInvoiceDate());
preqDoc.setInvoiceNumber(orderHolder.getInvoiceNumber());
preqDoc.setVendorInvoiceAmount(new KualiDecimal(orderHolder.getInvoiceNetAmount()));
preqDoc.setAccountsPayableProcessorIdentifier("E-Invoice");
preqDoc.setVendorCustomerNumber(orderHolder.getCustomerNumber());
preqDoc.setPaymentRequestElectronicInvoiceIndicator(true);
if (orderHolder.getAccountsPayablePurchasingDocumentLinkIdentifier() != null){
preqDoc.setAccountsPayablePurchasingDocumentLinkIdentifier(orderHolder.getAccountsPayablePurchasingDocumentLinkIdentifier());
}
//Copied from PaymentRequestServiceImpl.populatePaymentRequest()
//set bank code to default bank code in the system parameter
Bank defaultBank = SpringContext.getBean(BankService.class).getDefaultBankByDocType(preqDoc.getClass());
if (defaultBank != null) {
preqDoc.setBankCode(defaultBank.getBankCode());
preqDoc.setBank(defaultBank);
}
RequisitionDocument reqDoc = SpringContext.getBean(RequisitionService.class).getRequisitionById(poDoc.getRequisitionIdentifier());
String reqDocInitiator = reqDoc.getDocumentHeader().getWorkflowDocument().getInitiatorPrincipalId();
try {
Person user = KimApiServiceLocator.getPersonService().getPerson(reqDocInitiator);
setProcessingCampus(preqDoc, user.getCampusCode());
}catch(Exception e){
String extraDescription = "Error setting processing campus code - " + e.getMessage();
ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.PREQ_ROUTING_VALIDATION_ERROR, extraDescription, orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason);
return null;
}
HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccountList = SpringContext.getBean(AccountsPayableService.class).expiredOrClosedAccountsList(poDoc);
if (expiredOrClosedAccountList == null){
expiredOrClosedAccountList = new HashMap();
}
if (LOG.isInfoEnabled()){
LOG.info(expiredOrClosedAccountList.size() + " accounts has been found as Expired or Closed");
}
preqDoc.populatePaymentRequestFromPurchaseOrder(orderHolder.getPurchaseOrderDocument(),expiredOrClosedAccountList);
populateItemDetails(preqDoc,orderHolder);
/**
* Validate totals,paydate
*/
//PaymentRequestDocumentRule.processCalculateAccountsPayableBusinessRules
SpringContext.getBean(KualiRuleService.class).applyRules(new AttributedCalculateAccountsPayableEvent(preqDoc));
SpringContext.getBean(PaymentRequestService.class).calculatePaymentRequest(preqDoc,true);
processItemsForDiscount(preqDoc,orderHolder);
if (orderHolder.isInvoiceRejected()){
return null;
}
SpringContext.getBean(PaymentRequestService.class).calculatePaymentRequest(preqDoc,false);
/**
* PaymentRequestReview
*/
//PaymentRequestDocumentRule.processRouteDocumentBusinessRules
SpringContext.getBean(KualiRuleService.class).applyRules(new AttributedPaymentRequestForEInvoiceEvent(preqDoc));
if(GlobalVariables.getMessageMap().hasErrors()){
if (LOG.isInfoEnabled()){
LOG.info("***************Error in rules processing - " + GlobalVariables.getMessageMap());
}
Map<String, AutoPopulatingList<ErrorMessage>> errorMessages = GlobalVariables.getMessageMap().getErrorMessages();
String errors = errorMessages.toString();
ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.PREQ_ROUTING_VALIDATION_ERROR, errors, orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason);
return null;
}
if(KNSGlobalVariables.getMessageList().size() > 0){
if (LOG.isInfoEnabled()){
LOG.info("Payment request contains " + KNSGlobalVariables.getMessageList().size() + " warning message(s)");
for (int i = 0; i < KNSGlobalVariables.getMessageList().size(); i++) {
LOG.info("Warning " + i + " - " +KNSGlobalVariables.getMessageList().get(i));
}
}
}
addShipToNotes(preqDoc,orderHolder);
String routingAnnotation = null;
if (!orderHolder.isRejectDocumentHolder()){
routingAnnotation = "Routed by electronic invoice batch job";
}
try {
SpringContext.getBean(DocumentService.class).routeDocument(preqDoc,routingAnnotation, null);
}
catch (WorkflowException e) {
e.printStackTrace();
ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.PREQ_ROUTING_FAILURE, e.getMessage(), orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason);
return null;
}catch(ValidationException e){
String extraDescription = GlobalVariables.getMessageMap().toString();
ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.PREQ_ROUTING_VALIDATION_ERROR, extraDescription, orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason);
return null;
}
return preqDoc;
}
/**
*
* This method check OVERRIDE_PROCESSING_CAMPUS parameter to set processing campus code.
* If parameter value is populated, it set the processing campus to the value in parameter, otherwise use requisition initiator's campus code.
* @param preqDoc
* @param initiatorCampusCode
*/
protected void setProcessingCampus(PaymentRequestDocument preqDoc, String initiatorCampusCode) {
String campusCode = parameterService.getParameterValueAsString(ElectronicInvoiceStep.class, PurapParameterConstants.ElectronicInvoiceParameters.OVERRIDE_PROCESSING_CAMPUS);
if(!StringHelper.isNullOrEmpty(campusCode)) {
preqDoc.setProcessingCampusCode(campusCode);
}
else {
preqDoc.setProcessingCampusCode(initiatorCampusCode);
}
}
protected void addShipToNotes(PaymentRequestDocument preqDoc,
ElectronicInvoiceOrderHolder orderHolder){
String shipToAddress = orderHolder.getInvoiceShipToAddressAsString();
try {
Note noteObj = SpringContext.getBean(DocumentService.class).createNoteFromDocument(preqDoc, shipToAddress);
preqDoc.addNote(noteObj);
}catch (Exception e) {
LOG.error("Error creating ShipTo notes - " + e.getMessage());
}
}
protected void processItemsForDiscount(PaymentRequestDocument preqDocument,
ElectronicInvoiceOrderHolder orderHolder){
if (LOG.isInfoEnabled()){
LOG.info("Processing payment request items for discount");
}
if (!orderHolder.isItemTypeAvailableInItemMapping(ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DISCOUNT)){
if (LOG.isInfoEnabled()){
LOG.info("Skipping discount processing since there is no mapping of discount type for this vendor");
}
return;
}
if (orderHolder.getInvoiceDiscountAmount() == null ||
orderHolder.getInvoiceDiscountAmount() == BigDecimal.ZERO){
if (LOG.isInfoEnabled()){
LOG.info("Skipping discount processing since there is no discount amount found in the invoice file");
}
return;
}
KualiDecimal discountValueToUse = new KualiDecimal(orderHolder.getInvoiceDiscountAmount().negate());
List<PaymentRequestItem> preqItems = preqDocument.getItems();
boolean alreadyProcessedInvoiceDiscount = false;
boolean hasKualiPaymentTermsDiscountItem = false;
//if e-invoice amount is negative... it is a penalty and we must pay extra
for (int i = 0; i < preqItems.size(); i++) {
PaymentRequestItem preqItem = preqItems.get(i);
hasKualiPaymentTermsDiscountItem = hasKualiPaymentTermsDiscountItem || (StringUtils.equals(PurapConstants.ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE,preqItem.getItemTypeCode()));
if (isItemValidForUpdation(preqItem.getItemTypeCode(),ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DISCOUNT,orderHolder)){
alreadyProcessedInvoiceDiscount = true;
if (StringUtils.equals(preqItem.getItemTypeCode(),PurapConstants.ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE)){
//Item is kuali payment terms discount item... must perform calculation
// if discount item exists on PREQ and discount dollar amount exists... use greater amount
if (LOG.isInfoEnabled()){
LOG.info("Discount Check - E-Invoice matches PREQ item type '" + preqItem.getItemTypeCode() + "'... now checking for amount");
}
KualiDecimal preqExtendedPrice = preqItem.getExtendedPrice() == null ? KualiDecimal.ZERO : preqItem.getExtendedPrice();
if ( (discountValueToUse.compareTo(preqExtendedPrice)) < 0 ) {
if (LOG.isInfoEnabled()){
LOG.info("Discount Check - Using E-Invoice amount (" + discountValueToUse + ") as it is more discount than current payment terms amount " + preqExtendedPrice);
}
preqItem.setItemUnitPrice(discountValueToUse.bigDecimalValue());
preqItem.setExtendedPrice(discountValueToUse);
}
}else {
// item is not payment terms discount item... just add value
// if discount item exists on PREQ and discount dollar amount exists... use greater amount
if (LOG.isInfoEnabled()){
LOG.info("Discount Check - E-Invoice matches PREQ item type '" + preqItem.getItemTypeCode() + "'");
LOG.info("Discount Check - Using E-Invoice amount (" + discountValueToUse + ") as it is greater than payment terms amount");
}
preqItem.addToUnitPrice(discountValueToUse.bigDecimalValue());
preqItem.addToExtendedPrice(discountValueToUse);
}
}
}
/*
* If we have not already processed the discount amount then the mapping is pointed
* to an item that is not in the PREQ item list
*
* FYI - FILE DISCOUNT AMOUNT CURRENTLY HARD CODED TO GO INTO PAYMENT TERMS DISCOUNT ITEM ONLY... ALL OTHERS WILL FAIL
*/
if (!alreadyProcessedInvoiceDiscount) {
String itemTypeRequired = PurapConstants.ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE;
// if we already have a PMT TERMS DISC item but the e-invoice discount wasn't processed... error out
// if the item mapping for e-invoice discount item is not PMT TERMS DISC item and we haven't processed it... error out
if (hasKualiPaymentTermsDiscountItem ||
!orderHolder.isItemTypeAvailableInItemMapping(ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DISCOUNT)) {
ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.PREQ_DISCOUNT_ERROR, null, orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason);
return;
}
else {
PaymentRequestItem newItem = new PaymentRequestItem();
newItem.setItemUnitPrice(discountValueToUse.bigDecimalValue());
newItem.setItemTypeCode(PurapConstants.ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE);
newItem.setExtendedPrice(discountValueToUse);
newItem.setPurapDocument(preqDocument);
preqDocument.addItem(newItem);
}
}
if (LOG.isInfoEnabled()){
LOG.info("Completed processing payment request items for discount");
}
}
protected void populateItemDetails(PaymentRequestDocument preqDocument, ElectronicInvoiceOrderHolder orderHolder) {
if (LOG.isInfoEnabled()) {
LOG.info("Populating invoice order items into the payment request document");
}
List<PurApItem> preqItems = preqDocument.getItems();
//process all preq items and apply amounts from order holder
for (int i = 0; i < preqItems.size(); i++) {
PaymentRequestItem preqItem = (PaymentRequestItem) preqItems.get(i);
processInvoiceItem(preqItem, orderHolder);
/**
* This is not needed since if we have default desc from misc item, then preq rules are expecting the account details for this items
* AccountsPayableItemBase.isConsideredEntered() returns true if there is any item desc available.
*
*/
// setItemDefaultDescription(preqItem);
}
//Now we'll add any missing mapping items that did not have
// an existing payment request item
addMissingMappedItems(preqItems, orderHolder);
//as part of a clean up, remove any preq items that have zero or null unit/extended price
removeEmptyItems(preqItems);
if (LOG.isInfoEnabled()) {
LOG.info("Successfully populated the invoice order items");
}
}
/**
* Removes preq items from the list that have null or zero unit and extended price
*
* @param preqItems
*/
protected void removeEmptyItems(List<PurApItem> preqItems){
for(int i=preqItems.size()-1; i >= 0; i--){
PurApItem item = preqItems.get(i);
//if the unit and extended price have null or zero as a combo, remove item
if( isNullOrZero(item.getItemUnitPrice()) && isNullOrZero(item.getExtendedPrice()) ){
preqItems.remove(i);
}
}
}
/**
* Ensures that the mapped items, item type code, exist as a payment request item so they're
* process correctly within populateItemDetails
*
* @param preqItems
* @param orderHolder
*/
protected void addMissingMappedItems(List<PurApItem> preqItems, ElectronicInvoiceOrderHolder orderHolder){
PurchasingAccountsPayableDocument purapDoc = null;
Integer purapDocIdentifier = null;
//grab all the required item types that should be on the payment request
List requiredItemTypeCodeList = createInvoiceRequiresItemTypeCodeList(orderHolder);
if( ObjectUtils.isNotNull(requiredItemTypeCodeList) && !requiredItemTypeCodeList.isEmpty()) {
//loop through existing payment request items and remove ones we already have
for (int i=0; i < preqItems.size(); i++) {
//if the preq item exists in the list already, remove
if( requiredItemTypeCodeList.contains(preqItems.get(i).getItemTypeCode()) ){
requiredItemTypeCodeList.remove(preqItems.get(i).getItemTypeCode());
}
//utility grab the document identifier and document
purapDoc = preqItems.get(i).getPurapDocument();
purapDocIdentifier = preqItems.get(i).getPurapDocumentIdentifier();
}
if( ObjectUtils.isNotNull(requiredItemTypeCodeList) && !requiredItemTypeCodeList.isEmpty()) {
//if we have any left, it means they didn't exist on the payment request
// and we must add them.
for(int i=0; i < requiredItemTypeCodeList.size(); i++){
PaymentRequestItem preqItem = new PaymentRequestItem();
preqItem.resetAccount();
preqItem.setPurapDocumentIdentifier(purapDocIdentifier);
preqItem.setPurapDocument(purapDoc);
preqItem.setItemTypeCode((String)requiredItemTypeCodeList.get(i));
//process item
processInvoiceItem(preqItem, orderHolder);
//Add to preq Items if the value is not zero
if( (ObjectUtils.isNotNull(preqItem.getItemUnitPrice()) && preqItem.getItemUnitPrice() != BigDecimal.ZERO) &&
(ObjectUtils.isNotNull(preqItem.getExtendedPrice()) && preqItem.getExtendedPrice() != KualiDecimal.ZERO) ){
preqItems.add(preqItem);
}
}
}
}
}
/**
* Creates a list of item types the eInvoice requirs on
* the payment request due to valid amounts.
*/
protected List createInvoiceRequiresItemTypeCodeList(ElectronicInvoiceOrderHolder orderHolder){
List itemTypeCodeList = new ArrayList();
addToListIfExists(itemTypeCodeList, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_TAX, orderHolder);
addToListIfExists(itemTypeCodeList, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_SHIPPING, orderHolder);
addToListIfExists(itemTypeCodeList, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_SPECIAL_HANDLING, orderHolder);
addToListIfExists(itemTypeCodeList, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DEPOSIT, orderHolder);
addToListIfExists(itemTypeCodeList, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DUE, orderHolder);
addToListIfExists(itemTypeCodeList, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DISCOUNT, orderHolder);
return itemTypeCodeList;
}
/**
* Utility method to add a kuali item type code to a list from a invoice item type code
*
* @param itemTypeCodeList
* @param invoiceItemTypeCode
* @param orderHolder
*/
protected void addToListIfExists(List itemTypeCodeList, String invoiceItemTypeCode, ElectronicInvoiceOrderHolder orderHolder){
String itemTypeCode = orderHolder.getKualiItemTypeCodeFromMappings(invoiceItemTypeCode);
if( ObjectUtils.isNotNull(itemTypeCode) ){
itemTypeCodeList.add(itemTypeCode);
}
}
/**
* Finds the mapped item type code to invoice item type code and applies the appropriate values
* to the correct payment request item.
*
* @param preqItem
* @param orderHolder
*/
protected void processInvoiceItem(PaymentRequestItem preqItem, ElectronicInvoiceOrderHolder orderHolder){
if (isItemValidForUpdation(preqItem.getItemTypeCode(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_ITEM, orderHolder)) {
processAboveTheLineItem(preqItem, orderHolder);
}else if (isItemValidForUpdation(preqItem.getItemTypeCode(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_TAX, orderHolder)) {
processTaxItem(preqItem, orderHolder);
} else if (isItemValidForUpdation(preqItem.getItemTypeCode(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_SHIPPING, orderHolder)) {
processShippingItem(preqItem, orderHolder);
} else if (isItemValidForUpdation(preqItem.getItemTypeCode(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_SPECIAL_HANDLING, orderHolder)) {
processSpecialHandlingItem(preqItem, orderHolder);
} else if (isItemValidForUpdation(preqItem.getItemTypeCode(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DEPOSIT, orderHolder)) {
processDepositItem(preqItem, orderHolder);
} else if (isItemValidForUpdation(preqItem.getItemTypeCode(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DUE, orderHolder)) {
processDueItem(preqItem, orderHolder);
} else if (isItemValidForUpdation(preqItem.getItemTypeCode(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DISCOUNT, orderHolder)) {
processDiscountItem(preqItem, orderHolder);
}else if (isItemValidForUpdation(preqItem.getItemTypeCode(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_EXMT, orderHolder)) {
processAboveTheLineItem(preqItem, orderHolder);
}
}
protected void processAboveTheLineItem(PaymentRequestItem purapItem,
ElectronicInvoiceOrderHolder orderHolder){
if (LOG.isInfoEnabled()){
LOG.info("Processing above the line item");
}
ElectronicInvoiceItemHolder itemHolder = orderHolder.getItemByLineNumber(purapItem.getItemLineNumber().intValue());
if (itemHolder == null){
LOG.info("Electronic Invoice does not have item with Ref Item Line number " + purapItem.getItemLineNumber());
return;
}
purapItem.setItemUnitPrice(itemHolder.getInvoiceItemUnitPrice());
purapItem.setItemQuantity(new KualiDecimal(itemHolder.getInvoiceItemQuantity()));
purapItem.setItemTaxAmount(new KualiDecimal(itemHolder.getTaxAmount()));
purapItem.setItemCatalogNumber(itemHolder.getInvoiceItemCatalogNumber());
purapItem.setItemDescription(itemHolder.getInvoiceItemDescription());
if (itemHolder.getSubTotalAmount() != null &&
itemHolder.getSubTotalAmount().compareTo(KualiDecimal.ZERO) != 0){
purapItem.setExtendedPrice(itemHolder.getSubTotalAmount());
}else{
if (purapItem.getItemQuantity() != null) {
if (LOG.isInfoEnabled()){
LOG.info("Item number " + purapItem.getItemLineNumber() + " needs calculation of extended " +
"price from quantity " + purapItem.getItemQuantity() + " and unit cost " + purapItem.getItemUnitPrice());
}
purapItem.setExtendedPrice(purapItem.getItemQuantity().multiply(new KualiDecimal(purapItem.getItemUnitPrice())));
} else {
if (LOG.isInfoEnabled()){
LOG.info("Item number " + purapItem.getItemLineNumber() + " has no quantity so extended price " +
"equals unit price of " + purapItem.getItemUnitPrice());
}
purapItem.setExtendedPrice(new KualiDecimal(purapItem.getItemUnitPrice()));
}
}
}
protected void processSpecialHandlingItem(PaymentRequestItem purapItem,
ElectronicInvoiceOrderHolder orderHolder){
if (LOG.isInfoEnabled()){
LOG.info("Processing special handling item");
}
purapItem.addToUnitPrice(orderHolder.getInvoiceSpecialHandlingAmount());
purapItem.addToExtendedPrice(new KualiDecimal(orderHolder.getInvoiceSpecialHandlingAmount()));
String invoiceSpecialHandlingDescription = orderHolder.getInvoiceSpecialHandlingDescription();
if(invoiceSpecialHandlingDescription == null && (orderHolder.getInvoiceSpecialHandlingAmount() != null && BigDecimal.ZERO.compareTo(orderHolder.getInvoiceSpecialHandlingAmount())!= 0) ){
invoiceSpecialHandlingDescription = PurapConstants.ElectronicInvoice.DEFAULT_SPECIAL_HANDLING_DESCRIPTION;
}
if (StringUtils.isNotEmpty(invoiceSpecialHandlingDescription)) {
if (StringUtils.isEmpty(purapItem.getItemDescription())) {
purapItem.setItemDescription(invoiceSpecialHandlingDescription);
}
else {
purapItem.setItemDescription(purapItem.getItemDescription() + " - " + invoiceSpecialHandlingDescription);
}
}
}
protected void processTaxItem (PaymentRequestItem preqItem,
ElectronicInvoiceOrderHolder orderHolder){
if (LOG.isInfoEnabled()){
LOG.info("Processing Tax Item");
}
preqItem.addToUnitPrice(orderHolder.getTaxAmount());
preqItem.addToExtendedPrice(new KualiDecimal(orderHolder.getTaxAmount()));
if (StringUtils.isNotEmpty(orderHolder.getTaxDescription())) {
if (StringUtils.isEmpty(preqItem.getItemDescription())) {
preqItem.setItemDescription(orderHolder.getTaxDescription());
} else {
preqItem.setItemDescription(preqItem.getItemDescription() + " - " + orderHolder.getTaxDescription());
}
}
}
protected void processShippingItem(PaymentRequestItem preqItem,
ElectronicInvoiceOrderHolder orderHolder){
if (LOG.isInfoEnabled()){
LOG.info("Processing Shipping Item");
}
preqItem.addToUnitPrice(orderHolder.getInvoiceShippingAmount());
preqItem.addToExtendedPrice(new KualiDecimal(orderHolder.getInvoiceShippingAmount()));
if (StringUtils.isNotEmpty(orderHolder.getInvoiceShippingDescription())) {
if (StringUtils.isEmpty(preqItem.getItemDescription())) {
preqItem.setItemDescription(orderHolder.getInvoiceShippingDescription());
} else {
preqItem.setItemDescription(preqItem.getItemDescription() + " - " + orderHolder.getInvoiceShippingDescription());
}
}
}
protected void processDiscountItem(PaymentRequestItem preqItem,
ElectronicInvoiceOrderHolder orderHolder){
if (LOG.isInfoEnabled()){
LOG.info("Processing Discount Item");
}
preqItem.addToUnitPrice(orderHolder.getInvoiceDiscountAmount());
preqItem.addToExtendedPrice(new KualiDecimal(orderHolder.getInvoiceDiscountAmount()));
}
protected void processDepositItem(PaymentRequestItem preqItem,
ElectronicInvoiceOrderHolder orderHolder){
LOG.info("Processing Deposit Item");
preqItem.addToUnitPrice(orderHolder.getInvoiceDepositAmount());
preqItem.addToExtendedPrice(new KualiDecimal(orderHolder.getInvoiceDepositAmount()));
}
protected void processDueItem(PaymentRequestItem preqItem,
ElectronicInvoiceOrderHolder orderHolder){
LOG.info("Processing Deposit Item");
preqItem.addToUnitPrice(orderHolder.getInvoiceDueAmount());
preqItem.addToExtendedPrice(new KualiDecimal(orderHolder.getInvoiceDueAmount()));
}
protected boolean isNullOrZero(BigDecimal value){
if(ObjectUtils.isNull(value) || value.compareTo(BigDecimal.ZERO) == 0 ){
return true;
}else{
return false;
}
}
protected boolean isNullOrZero(KualiDecimal value){
if(ObjectUtils.isNull(value) || value.isZero()){
return true;
}else{
return false;
}
}
protected void setItemDefaultDescription(PaymentRequestItem preqItem){
//If description is empty and item is not type "ITEM"... use default description
if (StringUtils.isEmpty(preqItem.getItemDescription()) &&
!StringUtils.equals(PurapConstants.ItemTypeCodes.ITEM_TYPE_ITEM_CODE, preqItem.getItemTypeCode())){
if (ArrayUtils.contains(PurapConstants.ElectronicInvoice.ITEM_TYPES_REQUIRES_DESCRIPTION, preqItem.getItemTypeCode())){
preqItem.setItemDescription(PurapConstants.ElectronicInvoice.DEFAULT_BELOW_LINE_ITEM_DESCRIPTION);
}
}
}
protected boolean isItemValidForUpdation(String itemTypeCode,
String invoiceItemTypeCode,
ElectronicInvoiceOrderHolder orderHolder){
boolean isItemTypeAvailableInItemMapping = orderHolder.isItemTypeAvailableInItemMapping(invoiceItemTypeCode);
String itemTypeCodeFromMappings = orderHolder.getKualiItemTypeCodeFromMappings(invoiceItemTypeCode);
return isItemTypeAvailableInItemMapping && StringUtils.equals(itemTypeCodeFromMappings, itemTypeCode);
}
protected String generatePREQDocumentDescription(PurchaseOrderDocument poDocument) {
String description = "PO: " + poDocument.getPurapDocumentIdentifier() + " Vendor: " + poDocument.getVendorName() + " Electronic Invoice";
return checkDescriptionLengthAndStripIfNeeded(description);
}
/**
* This validates an electronic invoice and makes sure it can be turned into a Payment Request
*
*/
@NonTransactional
public void validateInvoiceOrderValidForPREQCreation(ElectronicInvoiceOrderHolder orderHolder){
if (LOG.isInfoEnabled()){
LOG.info("Validiting ElectronicInvoice Order to make sure that it can be turned into a Payment Request document");
}
PurchaseOrderDocument poDoc = orderHolder.getPurchaseOrderDocument();
if ( poDoc == null){
throw new RuntimeException("PurchaseOrder not available");
}
if (!orderHolder.isInvoiceNumberAcceptIndicatorEnabled()){
List preqs = paymentRequestService.getPaymentRequestsByVendorNumberInvoiceNumber(poDoc.getVendorHeaderGeneratedIdentifier(),
poDoc.getVendorDetailAssignedIdentifier(),
orderHolder.getInvoiceNumber());
if (preqs != null && preqs.size() > 0){
ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_ORDER_DUPLICATE,null,orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason,PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_FILE_NUMBER,PurapKeyConstants.ERROR_REJECT_INVOICE_DUPLICATE);
return;
}
}
if (orderHolder.getInvoiceDate() == null){
ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_DATE_INVALID,null,orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason,PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_FILE_DATE,PurapKeyConstants.ERROR_REJECT_INVOICE_DATE_INVALID);
return;
}else if (orderHolder.getInvoiceDate().after(dateTimeService.getCurrentDate())) {
ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_DATE_GREATER,null,orderHolder.getFileName());
orderHolder.addInvoiceOrderRejectReason(rejectReason,PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_FILE_DATE,PurapKeyConstants.ERROR_REJECT_INVOICE_DATE_GREATER);
return;
}
}
protected void moveFileList(Map filesToMove) {
for (Iterator iter = filesToMove.keySet().iterator(); iter.hasNext();) {
File fileToMove = (File) iter.next();
boolean success = this.moveFile(fileToMove, (String) filesToMove.get(fileToMove));
if (!success) {
String errorMessage = "File with name '" + fileToMove.getName() + "' could not be moved";
throw new PurError(errorMessage);
}
}
}
protected boolean moveFile(File fileForMove, String location) {
File moveDir = new File(location);
boolean success = fileForMove.renameTo(new File(moveDir, fileForMove.getName()));
return success;
}
protected void deleteDoneFile(File invoiceFile) {
File doneFile = new File(invoiceFile.getAbsolutePath().replace(electronicInvoiceInputFileType.getFileExtension(), ".done"));
if (doneFile.exists()) {
doneFile.delete();
}
}
/**
* returns a list of all error messages as a string
*
* @param errorMap
* @return
*/
protected String getErrorMessages(Map<String, ArrayList> errorMap){
ArrayList errorMessages = null;
ErrorMessage errorMessage = null;
StringBuffer errorList = new StringBuffer("");
String errorText = null;
for (Map.Entry<String, ArrayList> errorEntry : errorMap.entrySet()) {
errorMessages = errorEntry.getValue();
for (int i = 0; i < errorMessages.size(); i++) {
errorMessage = (ErrorMessage) errorMessages.get(i);
// get error text
errorText = kualiConfigurationService
.getPropertyValueAsString(errorMessage.getErrorKey());
// apply parameters
errorText = MessageFormat.format(errorText,
(Object[]) errorMessage.getMessageParameters());
// add key and error message together
errorList.append(errorText + "\n");
}
}
return errorList.toString();
}
protected String getBaseDirName(){
return electronicInvoiceInputFileType.getDirectoryPath() + File.separator;
}
@NonTransactional
public String getRejectDirName(){
return getBaseDirName() + "reject" + File.separator;
}
@NonTransactional
public String getAcceptDirName(){
return getBaseDirName() + "accept" + File.separator;
}
protected File getInvoiceFile(String fileName){
return new File(getBaseDirName() + fileName);
}
protected ElectronicInvoiceLoadSummary saveElectronicInvoiceLoadSummary(ElectronicInvoiceLoadSummary eils) {
SpringContext.getBean(BusinessObjectService.class).save(eils);
eils.refreshNonUpdateableReferences();
return eils;
}
@NonTransactional
public void setElectronicInvoiceInputFileType(ElectronicInvoiceInputFileType electronicInvoiceInputFileType) {
this.electronicInvoiceInputFileType = electronicInvoiceInputFileType;
}
@NonTransactional
public void setMailService(MailService mailService) {
this.mailService = mailService;
}
@NonTransactional
public void setElectronicInvoicingDao(ElectronicInvoicingDao electronicInvoicingDao) {
this.electronicInvoicingDao = electronicInvoicingDao;
}
@NonTransactional
public void setBatchInputFileService(BatchInputFileService batchInputFileService) {
this.batchInputFileService = batchInputFileService;
}
@NonTransactional
public void setElectronicInvoiceMatchingService(ElectronicInvoiceMatchingService matchingService) {
this.matchingService = matchingService;
}
@NonTransactional
public void setVendorService(VendorService vendorService) {
this.vendorService = vendorService;
}
@NonTransactional
public void setPurchaseOrderService(PurchaseOrderService purchaseOrderService) {
this.purchaseOrderService = purchaseOrderService;
}
@NonTransactional
public void setPaymentRequestService(PaymentRequestService paymentRequestService) {
this.paymentRequestService = paymentRequestService;
}
@NonTransactional
public void setConfigurationService(ConfigurationService kualiConfigurationService) {
this.kualiConfigurationService = kualiConfigurationService;
}
@NonTransactional
public void setDateTimeService(DateTimeService dateTimeService) {
this.dateTimeService = dateTimeService;
}
@NonTransactional
public void setParameterService(ParameterService parameterService) {
this.parameterService = parameterService;
}
/**
* @see org.kuali.kfs.sys.batch.service.impl.InitiateDirectoryImpl#getRequiredDirectoryNames()
*/
@Override
@NonTransactional
public List<String> getRequiredDirectoryNames() {
return new ArrayList<String>() {{add(getBaseDirName()); add(getAcceptDirName()); add(getRejectDirName()); }};
}
}