/* * Imixs-Workflow * * Copyright (C) 2001-2020 Imixs Software Solutions GmbH, * http://www.imixs.com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * 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 * General Public License for more details. * * You can receive a copy of the GNU General Public * License at http://www.gnu.org/licenses/gpl.html * * Project: * https://www.imixs.org * https://github.com/imixs/imixs-workflow * * Contributors: * Imixs Software Solutions GmbH - Project Management * Ralph Soika - Software Developer */ package com.alexanderlogistics.xml; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.TimeZone; import java.util.logging.Logger; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPFile; import org.apache.commons.net.ftp.FTPSClient; import org.imixs.archive.importer.DocumentImportEvent; import org.imixs.archive.importer.DocumentImportService; import org.imixs.workflow.FileData; import org.imixs.workflow.ItemCollection; import org.imixs.workflow.engine.DocumentService; import org.imixs.workflow.engine.ModelService; import org.imixs.workflow.engine.WorkflowService; import org.imixs.workflow.exceptions.AccessDeniedException; import org.imixs.workflow.exceptions.ModelException; import org.imixs.workflow.exceptions.PluginException; import org.imixs.workflow.exceptions.ProcessingErrorException; import org.imixs.workflow.exceptions.QueryException; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import com.alexanderlogistics.InvoiceUtil; import com.alexanderlogistics.KreditorDebitorService; import com.alexanderlogistics.mahnlauf.MahnlaufService; import jakarta.ejb.EJB; import jakarta.ejb.Stateless; import jakarta.enterprise.event.Observes; import jakarta.inject.Inject; /** * Der CargosoftXMLInvoiceImportImportService erweitert den FTPImportService und * kann XML Invoice Dokumente von Cargosoft importieren. * Der Service reagiert auf DocumentImportEvents und kann über das Options Feld * konfiguriert werden. *

* Im Gegensatz zum FTPImportService werden die XML Dateien nach einem * speziellen Verfahren geparsed und dann als Ausgangsrechnungen importiert. * Der Service ersetzt den alten DATEV Import. Falls eine Rechnung schon * existiert (Rechnungsnummer) wird der Datensatz vom FTP Laufwerk gelöscht und * nicht importiert. *

* In den Optionen muss die option "mandant.id" hinterlegt sein. Stimmt * diese beim Import nicht mit der Mandanten Nummer in der XML Datei überein, * wird das XML Dokument gelöscht und nicht importiert. * * * * @author rsoika * */ @Stateless public class CargosoftXMLInvoiceImportService { private static Logger logger = Logger.getLogger(CargosoftXMLInvoiceImportService.class.getName()); public static final String OPTION_MANDANT_ID = "mandant.id"; public static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"; public static final String REGEX_IMPORTTEXTPATTERN = "^R[1-8] .*$"; @EJB WorkflowService workflowService; @EJB DocumentService documentService; @EJB ModelService modelService; @EJB KreditorDebitorService kreditorDebitorService; @EJB DocumentImportService documentImportService; @Inject MahnlaufService mahnlaufService; /** * This method reacts on a CDI ImportEvent and reads documents form a ftp * server. * * */ public void onEvent(@Observes DocumentImportEvent event) { // check if source is already completed if (event.getResult() == DocumentImportEvent.PROCESSING_COMPLETED) { logger.finest("...... import source already completed - no processing will be performed."); return; } if (!"CARGOSOFT_INVOICE_XML".equalsIgnoreCase(event.getSource().getItemValueString("type"))) { // ignore data source logger.finest("...... type '" + event.getSource().getItemValueString("type") + "' skipped."); return; } Properties sourceOptions = documentImportService.getOptionsProperties(event.getSource()); // Read Mandand ID form Option list String mandantID = sourceOptions.getProperty(OPTION_MANDANT_ID, ""); if (mandantID == null || mandantID.isEmpty()) { documentImportService.logMessage("...CARGOSOFT_INVOICE_XML Import failed - missing mandant.id in options!", event); event.setResult(DocumentImportEvent.PROCESSING_ERROR); return; } documentImportService.logMessage("...CARGOSOFT_INVOICE_XML Import started: mandant.id=" + mandantID, event); String ftpServer = event.getSource().getItemValueString(DocumentImportService.SOURCE_ITEM_SERVER); String ftpPort = event.getSource().getItemValueString(DocumentImportService.SOURCE_ITEM_PORT); String ftpUser = event.getSource().getItemValueString(DocumentImportService.SOURCE_ITEM_USER); String ftpPassword = event.getSource().getItemValueString(DocumentImportService.SOURCE_ITEM_PASSWORD); String ftpPath = event.getSource().getItemValueString(DocumentImportService.SOURCE_ITEM_SELECTOR); if (!ftpPath.startsWith("/") && !ftpPath.startsWith("./")) { ftpPath = "/" + ftpPath; } if (!ftpPath.endsWith("/")) { ftpPath = ftpPath + "/"; } // if no server is given we exit if (ftpServer.isEmpty()) { logger.warning("...... no server specified!"); return; } if (ftpPort.isEmpty()) { // set default port ftpPort = "21"; } try { documentImportService.logMessage("...connecting to FTP server: " + ftpServer, event); documentImportService.logMessage("...working directory: " + ftpPath, event); FTPClient ftpClient = connectServer(ftpServer, Integer.parseInt(ftpPort), ftpUser, ftpPassword); scannFTPDirectory(ftpClient, ftpPath, mandantID, event); } catch (PluginException pe) { documentImportService.logMessage(pe.getMessage(), event); event.setResult(DocumentImportEvent.PROCESSING_ERROR); return; } // completed event.setResult(DocumentImportEvent.PROCESSING_COMPLETED); } /** * Connects to the FTP Server and returns a ftpClient instance. */ private FTPClient connectServer(String ftpServer, int ftpPort, String ftpUser, String ftpPassword) throws PluginException { FTPClient ftpClient = null; // TLS ftpClient = new FTPSClient("TLS", false); ftpClient.setControlEncoding("UTF-8"); try { ftpClient.connect(ftpServer, ftpPort); if (ftpClient.login(ftpUser, ftpPassword) == false) { throw new PluginException(CargosoftXMLInvoiceImportService.class.getName(), "FTP_ERROR", "FTP file transfer failed: login failed!"); } ftpClient.enterLocalPassiveMode(); logger.finest("...... FileType=" + FTP.BINARY_FILE_TYPE); ftpClient.setFileType(FTP.BINARY_FILE_TYPE); ftpClient.setControlEncoding("UTF-8"); } catch (IOException e) { throw new PluginException(CargosoftXMLInvoiceImportService.class.getName(), "FTP_ERROR", e.getMessage()); } return ftpClient; } /** * Helper method reads the working FTP directory and starts importing the files. * * @param ftpClient * @param ftpPath * @throws PluginException */ private void scannFTPDirectory(FTPClient ftpClient, String ftpPath, String mandantID, DocumentImportEvent event) throws PluginException { try { // load spaces Pos Expressions Map> spacePosMappings = mahnlaufService.loadSpacePosMappings(); logger.finest("......read directory " + ftpPath); // try to enter the working directory and read all files... boolean bWorkingDir = ftpClient.changeWorkingDirectory(ftpPath); if (bWorkingDir == false) { throw new PluginException(CargosoftXMLInvoiceImportService.class.getName(), "FTP_ERROR", "...failed to change into working directory: "); } FTPFile[] allFiles = ftpClient.listFiles(ftpPath); // FTPFile[] allFiles = ftpClient.listFiles(); int count = 0; if (allFiles.length > 0) { documentImportService.logMessage("..." + allFiles.length + " files found ", event); for (FTPFile file : allFiles) { // if this is a directory or symlink then we do ignore this entry if (!file.isFile()) { logger.warning("...'" + file.getName() + "' is not a valid file, object will be ignored!"); continue; } ItemCollection invoice = null; logger.info("import file " + file.getName() + "..."); // String fullFileName = ftpPath + "/" + file.getName(); try (ByteArrayOutputStream is = new ByteArrayOutputStream();) { ftpClient.retrieveFile(file.getName(), is); byte[] rawData = is.toByteArray(); if (rawData != null && rawData.length > 0) { logger.finest("......file '" + file.getName() + "' successful read - bytes size = " + rawData.length); // create new workitem invoice = createWorkitem(event.getSource(), file.getName(), rawData, spacePosMappings); if (invoice != null) { // persist workitem only if not exists and with a matching mandant.id if (!invoice.getItemValueString("mandant.id").equals(mandantID)) { throw new PluginException(CargosoftXMLInvoiceImportService.class.getName(), "FTP_ERROR", "Invoice does not match mandantID " + mandantID); } count++; workflowService.processWorkItemByNewTransaction(invoice); // .processWorkItem(invoice); } } // finally delete teh file from the transfer folder ftpClient.deleteFile(ftpPath + file.getName()); // String sourceFilePath = ftpPath + file.getName(); // String destinationFilePath = ftpPath + "processed/" + file.getName(); // boolean success = ftpClient.rename(sourceFilePath, destinationFilePath); // if (!success) { // logger.warning("Failed to move file " + file.getName() + " to " + ftpPath + // "processed/"); // } } catch (AccessDeniedException | ProcessingErrorException | PluginException | ModelException | XPathExpressionException | TransformerException e) { throw new PluginException(CargosoftXMLInvoiceImportService.class.getName(), "FTP_ERROR", e.getMessage()); } } documentImportService.logMessage("..." + count + " new files imported.", event); } } catch (IOException e) { logger.severe("FTP I/O Error: " + e.getMessage()); int r = ftpClient.getReplyCode(); logger.severe("FTP ReplyCode=" + r); throw new PluginException(CargosoftXMLInvoiceImportService.class.getName(), "FTP_ERROR", e.getMessage()); } finally { // do logout.... try { ftpClient.logout(); ftpClient.disconnect(); } catch (IOException e) { documentImportService.logMessage("...FTP file transfer failed: " + e.getMessage(), event); throw new PluginException(CargosoftXMLInvoiceImportService.class.getName(), "FTP_ERROR", e.getMessage()); } } } /** * Creates and processes a new workitem with a given xml information from the * fileData * * * @return * @throws ModelException * @throws PluginException * @throws ProcessingErrorException * @throws AccessDeniedException * @throws TransformerException * @throws XPathExpressionException */ public ItemCollection createWorkitem(ItemCollection source, String fileName, byte[] rawData, Map> spacePosMappings) throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException, XPathExpressionException, TransformerException { ItemCollection workitem = new ItemCollection(); workitem.model(source.getItemValueString(DocumentImportService.SOURCE_ITEM_MODELVERSION)); workitem.task(source.getItemValueInteger(DocumentImportService.SOURCE_ITEM_TASK)); workitem.event(source.getItemValueInteger(DocumentImportService.SOURCE_ITEM_EVENT)); workitem.setWorkflowGroup(source.getItemValueString("workflowgroup")); workitem.setItemValue("cargosoft.import.filename", fileName); // Now we parse the rawData.... InputStream inputStream = new ByteArrayInputStream(rawData); InputSource inputSource = new InputSource(inputStream); DocumentBuilder documentBuilder; try { documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document doc = documentBuilder.parse(inputSource); // Now we parse the following item values // invoice.number // invoice.currency // invoice.text // invoice.date // dbtr.number // invoice.duedate // invoice.reminder // payment.term // invoice.rate // invoice.base.amount readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/Client/Codes/Code", workitem, "mandant.id", String.class); readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceNumber", workitem, "invoice.number", String.class); // verify if invoice is already imported. if (alreadyImported(workitem.getItemValueString("invoice.number"))) { logger.warning("Invoice " + workitem.getItemValueString("invoice.number") + " already imported"); return null; } readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceType/Codes/Code", workitem, "invoice.type", String.class); readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceType/Description", workitem, "invoice.type.description", String.class); // Rechnungssumme errechnen..... readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAmount/NetAmount/Amount/Value", workitem, "invoice.total.net", Double.class); readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAmount/VATInformation/VATAmount/Amount/Value", workitem, "invoice.total.tax", Double.class); if (!workitem.hasItem("invoice.total.tax")) { workitem.setItemValue("invoice.total.tax", 0.0); } // Total = net + tax double _total = workitem.getItemValueDouble("invoice.total.net"); _total = _total + workitem.getItemValueDouble("invoice.total.tax"); _total = Math.round(_total * 100) / 100.0; workitem.setItemValue("invoice.total", _total); workitem.setItemValue("invoice.saldo", _total); // Bei Gutschrift und Stornorechnung Wert negieren... if (isGutschrift(workitem)) { workitem.setItemValue("invoice.saldo", -workitem.getItemValueDouble("invoice.total")); workitem.setItemValue("invoice.total", -workitem.getItemValueDouble("invoice.total")); } // Steuer readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAmount/VATInformation/VAT/VATRate", workitem, "invoice.vatrate", Double.class); // Booking Period readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/BookingPeriod", workitem, "invoice.bookingperiod", String.class); readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceCurrency/Codes/Code", workitem, "invoice.currency", String.class); readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceDate", workitem, "invoice.date", Date.class); readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/PaymentConditions/PaymentCondition/Codes/Code[@Type='cs']", workitem, "payment.term", String.class); // kostenstelle readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/CostUnit/Description", workitem, "invoice.CostUnit", String.class); readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/PaymentConditions/DueDate", workitem, "invoice.duedate", Date.class); readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/PaymentConditions/DueDate", workitem, "invoice.reminder", Date.class); readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAddress/Codes/Code", workitem, "dbtr.number", String.class); String dbtrNumber = workitem.getItemValueString("dbtr.number"); if (dbtrNumber.startsWith("K") || dbtrNumber.startsWith("D")) { workitem.setItemValue("dbtr.number", dbtrNumber.substring(1)); } // lookup debitor and set name try { ItemCollection dbtr = kreditorDebitorService.findDebitor(dbtrNumber); if (dbtr != null) { workitem.setItemValue("dbtr.name", dbtr.getItemValueString("_vendor_name")); // Anhand der Creditoren Stammdaten errrechnen wir die Sprache für diese // Rechnung String country = dbtr.getItemValueString("_VENDOR_COUNTRY"); workitem.setItemValue("dbtr.country", country); if (country.equalsIgnoreCase("de") || country.equalsIgnoreCase("ch") || country.equalsIgnoreCase("at")) { workitem.setItemValue("invoice.language", "DE"); } else { workitem.setItemValue("invoice.language", "EN"); } } } catch (PluginException e) { // e.printStackTrace(); } // Row Positions lesen readXMLRows(doc, workitem); // Suche die ATC Nummern.... resolveATCNumbers(doc, workitem); // Jetzt noch das Space mapping mahnlaufService.mapInvoiceTextToSpace(workitem, spacePosMappings); // Rückstellungen ignorieren if (isRueckStellung(workitem)) { // Ignore invoice! return null; } // Finally we attache the pdf file and the XML content to the workitem. attacheFiles(doc, workitem); } catch (ParserConfigurationException | SAXException | IOException e) { throw new PluginException(CargosoftXMLInvoiceImportService.class.getName(), "XML_ERROR", e.getMessage()); } return workitem; } /** * Prüft ob die Rechnung eine Rückstellung ist also mit R1 bis R8 beginnt * * ^R[1-8] . * * @param workitem * @return */ private boolean isRueckStellung(ItemCollection workitem) { String text = workitem.getItemValueString("invoice.text"); return text.matches(REGEX_IMPORTTEXTPATTERN); } /** * Reads a tag value from the xml tree and set the value into the given * workitem. * * /Invoices/Invoice/InvoiceHeader/Client/Code * * Beispiel Datum: * 2024-05-13T00:00:00+02:00 * * @param doc - xml doc * @param expression - xpath expression * @param workitem * @param itemName * @param itemType */ private void readXMLValue(Document doc, String expression, ItemCollection workitem, String itemName, Class itemType) { // create XPath... XPathFactory xpathFactory = XPathFactory.newInstance(); XPath xpath = xpathFactory.newXPath(); XPathExpression xPathExpression; try { xPathExpression = xpath.compile(expression); // extract node value Node valueNode = (Node) xPathExpression.evaluate(doc, XPathConstants.NODE); if (valueNode != null) { String value = valueNode.getTextContent(); if (itemType == Date.class) { // 2024-05-13T00:00:00+02:00 SimpleDateFormat formatter = new SimpleDateFormat(DATE_FORMAT); formatter.setTimeZone(TimeZone.getTimeZone("CET")); try { workitem.setItemValue(itemName, formatter.parse(value)); } catch (ParseException e) { logger.warning("Invalid Date Format"); } return; } if (itemType == Double.class && value != null && !value.isEmpty()) { workitem.setItemValue(itemName, Double.parseDouble(value)); return; } if (itemType == Integer.class && value != null && !value.isEmpty()) { workitem.setItemValue(itemName, Integer.parseInt(value)); return; } // Default String format workitem.setItemValue(itemName, value); } } catch (XPathExpressionException e) { logger.warning("Unable to read data field '" + expression + "' : " + e.getMessage()); } } /** * Hilfsmethode die die ATC Nummer sucht und in das workitem feld * invoice.atc.number einträgt. * Wir gehen davon aus, das wir über die ChildItems diese aus dem billingtexts * schon ermittel haben. * Es können mehrere ATC nummern existieren. Diese werden in einer Liste * gespeichert * * @param doc * @param workitem */ private void resolveATCNumbers(Document doc, ItemCollection workitem) { String atcNumber = null; List childs = InvoiceUtil.explodeChildList(workitem); for (ItemCollection child : childs) { if (child.hasItem("atc.number")) { atcNumber = child.getItemValueString("atc.number"); workitem.appendItemValue("invoice.atc.number", atcNumber); // break; } } // if (atcNumber != null && !atcNumber.isEmpty()) { // workitem.setItemValue("invoice.atc.number", atcNumber); // } } /** * Reads the positions rows * * InvoiceRows/InvoiceRow * * Each row has the following items: * _childitems { * datev.shzeichen=[H], * datev.kurs=[0.0], * datev.basisumsatz=[0.0], * datev.text=[R LA-PAP-2405-051], * datev.konto=[3851], * datev.umsatz=[3307.81], * datev.wkz=[EUR]}} * * @param doc - xml doc * @param workitem */ private void readXMLRows(Document doc, ItemCollection workitem) { // create XPath... List childItems = new ArrayList<>(); XPathFactory xpathFactory = XPathFactory.newInstance(); XPath xPath = xpathFactory.newXPath(); try { // Compile the XPath expressions XPathExpression fileNumberExpr = xPath.compile("FileNumber/text()"); XPathExpression netAmountCurrencyExpr = xPath .compile("InvoiceAmount/NetAmount/Amount/Currency/Codes/Code[@Type='cs']/text()"); XPathExpression netAmountValueExpr = xPath.compile("InvoiceAmount/NetAmount/Amount/Value/text()"); XPathExpression netAmountExchangeRateExpr = xPath .compile("InvoiceAmount/NetAmount/Amount/ExchangeRate/text()"); XPathExpression netActivityTypeExpr = xPath .compile("ActivityType/Codes/Code[@Type='cs']/text()"); NodeList rowList = doc.getElementsByTagName("InvoiceRow"); double _kurs = 0.0; double _baseAmount = 0.0; for (int i = 0; i < rowList.getLength(); i++) { ItemCollection childItemCol = new ItemCollection(); Node rowNode = rowList.item(i); if (rowNode.getNodeType() == Node.ELEMENT_NODE) { // Get the FileNumber value String fileNumber = (String) fileNumberExpr.evaluate(rowNode, XPathConstants.STRING); childItemCol.setItemValue("datev.text", fileNumber); // speichere die Position in invoice.positions zur suche nach dem // Positionskennzeichen workitem.appendItemValue("invoice.positions", fileNumber); String currency = (String) netAmountCurrencyExpr.evaluate(rowNode, XPathConstants.STRING); childItemCol.setItemValue("datev.wkz", currency); // Umsatz String umsatz = (String) netAmountValueExpr.evaluate(rowNode, XPathConstants.STRING); double dUmsatz = Double.parseDouble(umsatz); childItemCol.setItemValue("datev.umsatz", dUmsatz); // Kurs String _kursByRow = (String) netAmountExchangeRateExpr.evaluate(rowNode, XPathConstants.STRING); if (_kursByRow != null && !_kursByRow.isEmpty()) { Double dKurs = Double.parseDouble(_kursByRow); _kurs = dKurs; childItemCol.setItemValue("datev.kurs", dKurs); // Basisumsatz if (dKurs != 0) { double basisUmsatz = dUmsatz / dKurs; double gerundeterBasisUmsatz = Math.round(basisUmsatz * 100) / 100.0; if (isGutschrift(workitem)) { _baseAmount = _baseAmount - gerundeterBasisUmsatz; } else { _baseAmount = _baseAmount + gerundeterBasisUmsatz; } childItemCol.setItemValue("datev.basisumsatz", gerundeterBasisUmsatz); } } String activityType = (String) netActivityTypeExpr.evaluate(rowNode, XPathConstants.STRING); childItemCol.setItemValue("category", activityType); /** * S/H Kennzeichen auflösen * * Bei G = Gutschrift oder SR = Stornorechnung müssen wir das vorzeichen ändern * Invoice Type S/H */ if (isGutschrift(workitem)) { // Gutschrift childItemCol.setItemValue("datev.shzeichen", "S"); } else { // Rechnung childItemCol.setItemValue("datev.shzeichen", "H"); } /** * Liste der BillingText-Elemente lesen.... * * und ggf. die ATC Nummer finden.... **/ // XPath-Ausdruck für den BillingCode XPathExpression billingCodeExpr = xPath.compile("BillingCode/Codes/Code"); String billingCode = (String) billingCodeExpr.evaluate(rowNode, XPathConstants.STRING); childItemCol.setItemValue("BillingCode", billingCode); // XPath-Ausdruck für die BillingTexts XPathExpression billingTextsExpr = xPath.compile("BillingTexts/BillingText"); NodeList billingTextNodes = (NodeList) billingTextsExpr.evaluate(rowNode, XPathConstants.NODESET); for (int bi = 0; bi < billingTextNodes.getLength(); bi++) { String val = billingTextNodes.item(bi).getTextContent(); childItemCol.appendItemValue("BillingText", val); // handelt es sich um eine ATC Nummer? // Änderung 22.10.2024 - jetzt auch auf 24DE prüfen if (val.startsWith("ATC") || val.startsWith("24DE") || val.startsWith("25DE") || val.startsWith("26DE")) { childItemCol.setItemValue("atc.number", val); } } childItems.add(childItemCol); } } // Update invoice.rate und invoice.base.amount workitem.setItemValue("invoice.rate", _kurs); _baseAmount = Math.round(_baseAmount * 100) / 100.0; workitem.setItemValue("invoice.base.amount", _baseAmount); } catch (XPathExpressionException e) { logger.warning("Unable to read row : " + e.getMessage()); } // Set child items InvoiceUtil.implodeChildList(workitem, childItems); // invoice.text ist erste position workitem.setItemValue("invoice.text", workitem.getItemValueString("invoice.positions")); } /** * G = Gutschrift * SR = Stornorechnung * GK = Sammelgutschrift * SS = Sammelrechnung Storno * * müssen wir das vorzeichen ändern * dies wird über den Invoice.Type geprüft * * @return */ private boolean isGutschrift(ItemCollection workitem) { String type = workitem.getItemValueString("invoice.type"); return ("G".equals(type) || "SR".equals(type) || "GK".equals(type) || "SS".equals(type)); } /** * This method attache the pdf file and the XML file to the workitem. * The method removes the file content first form the xml tree and attache the * XML without the pdf data. * * @throws XPathExpressionException * @throws TransformerException */ private void attacheFiles(Document doc, ItemCollection workitem) throws XPathExpressionException, TransformerException { // create XPath... XPathFactory xpathFactory = XPathFactory.newInstance(); XPath xpath = xpathFactory.newXPath(); // read filename and content.... readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/Attachments/Attachment/Filename", workitem, "import.filename", String.class); readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/Attachments/Attachment/Content", workitem, "import.filedata", String.class); // attache the PDF file content String fileDataString = workitem.getItemValueString("import.filedata"); byte[] decodedPDFData = java.util.Base64.getDecoder().decode(fileDataString); FileData fileData = new FileData(workitem.getItemValueString("import.filename"), decodedPDFData, "application/pdf", null); workitem.addFileData(fileData); // remove the temp items.. workitem.removeItem("import.filename"); workitem.removeItem("import.filedata"); // next remove the Attachments/AttachmentContent from the dom tree XPathExpression attachmentsExpr = xpath .compile("/Invoices/Invoice/InvoiceHeader/Attachments/Attachment/Content"); NodeList contentNodes = (NodeList) attachmentsExpr.evaluate(doc, XPathConstants.NODESET); // Remove the matching nodes for (int i = 0; i < contentNodes.getLength(); i++) { Node contentNode = contentNodes.item(i); Node parentNode = contentNode.getParentNode(); parentNode.removeChild(contentNode); } // Now attache the XML tree without the file... StringWriter stringWriter = new StringWriter(); TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer transformer = transformerFactory.newTransformer(); DOMSource source = new DOMSource(doc); StreamResult result = new StreamResult(stringWriter); transformer.transform(source, result); // byte[] decodedXMLData = // java.util.Base64.getDecoder().decode(stringWriter.toString().); FileData fileDataXML = new FileData(workitem.getItemValueString("cargosoft.import.filename"), stringWriter.toString().getBytes(), "application/xml", null); workitem.addFileData(fileDataXML); } /** * Prüft ob die Belegnummer schon existiert (importiert wurde) * * @param belegNummer * @return */ protected boolean alreadyImported(String belegNummer) { String sQuery = "((type:workitem OR type:workitemarchive) AND $modelversion:rechnungsausgang* AND invoice.number:\"" + belegNummer + "\")"; try { // find the textblock... List result = documentService.find(sQuery, 1, 0); if (result.size() > 0) { return true; } } catch (QueryException e) { logger.warning("failed to search invoices by query: " + e.getMessage()); } return false; } }