diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/xml/CargosoftXMLEAkteImportService.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/xml/CargosoftXMLEAkteImportService.java
new file mode 100644
index 0000000..e224f86
--- /dev/null
+++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/xml/CargosoftXMLEAkteImportService.java
@@ -0,0 +1,728 @@
+/*
+ * 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 CargosoftXMLEAkteImportService erweitert den FTPImportService und
+ * kann XML EAkten (Steuerbescheide) 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 Steuerbescheid 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 CargosoftXMLEAkteImportService {
+
+ private static Logger logger = Logger.getLogger(CargosoftXMLEAkteImportService.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_EAKTE_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_EAKTE_XML Import failed - missing mandant.id in options!",
+ event);
+ event.setResult(DocumentImportEvent.PROCESSING_ERROR);
+ return;
+ }
+
+ documentImportService.logMessage("...CARGOSOFT_EAKTE_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(CargosoftXMLEAkteImportService.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(CargosoftXMLEAkteImportService.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(CargosoftXMLEAkteImportService.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(CargosoftXMLEAkteImportService.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(CargosoftXMLEAkteImportService.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(CargosoftXMLEAkteImportService.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(CargosoftXMLEAkteImportService.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, "/CargoSoftEFile/EFile/References/Reference[@type='client']", workitem,
+ "mandant.id", String.class);
+
+ readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='cs_voucher_number']",
+ workitem, "invoice.number", String.class);
+ readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='currency']",
+ workitem, "invoice.currency", String.class);
+ readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='total_net_amount']",
+ workitem, "invoice.total.net", String.class);
+ readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='total_tax_amount']",
+ workitem, "invoice.total.tax", String.class);
+ readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='reference']",
+ workitem, "invoice.atc.number", String.class);
+ readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='booking_period']",
+ workitem, "invoice.booking_period", String.class);
+
+ readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='booking_date']",
+ workitem, "invoice.booking_date", String.class);
+ readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='booking_text']",
+ workitem, "invoice.booking_text", String.class);
+
+ // Read rows
+ readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='row_1_cs_filenumber']",
+ workitem, "invoice.text", 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;
+ }
+
+ // 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"));
+ // }
+
+ String cdtrNumber = workitem.getItemValueString("cdtr.number");
+ if (cdtrNumber.startsWith("K") || cdtrNumber.startsWith("D")) {
+ workitem.setItemValue("cdtr.number", cdtrNumber.substring(1));
+ }
+ // lookup debitor and set name
+ if (cdtrNumber != null && !cdtrNumber.isEmpty())
+ try {
+ ItemCollection cdtr = kreditorDebitorService.findCreditor(cdtrNumber);
+ if (cdtr != null) {
+ workitem.setItemValue("cdtr.name", cdtr.getItemValueString("_vendor_name"));
+ // Anhand der Creditoren Stammdaten errrechnen wir die Sprache für diese
+ // Rechnung
+ String country = cdtr.getItemValueString("_VENDOR_COUNTRY");
+ workitem.setItemValue("cdtr.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);
+
+ // 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(CargosoftXMLEAkteImportService.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());
+
+ }
+ }
+
+ /**
+ * 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("InvoiceAmount/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;
+
+ _baseAmount = _baseAmount + gerundeterBasisUmsatz;
+
+ childItemCol.setItemValue("datev.basisumsatz", gerundeterBasisUmsatz);
+ }
+ }
+
+ String activityType = (String) netActivityTypeExpr.evaluate(rowNode, XPathConstants.STRING);
+ childItemCol.setItemValue("category", activityType);
+
+ childItemCol.setItemValue("datev.shzeichen", "H");
+
+ 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"));
+
+ }
+
+ /**
+ * 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, "/CargoSoftEFile/EFile/Attachments/Attachment/Filename",
+ workitem, "import.filename", String.class);
+ readXMLValue(doc, "/CargoSoftEFile/EFile/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("/CargoSoftEFile/EFile/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
+ */
+ private boolean alreadyImported(String belegNummer) {
+
+ String sQuery = "((type:workitem OR type:workitemarchive) AND $modelversion:steuerbescheid* 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;
+ }
+}
diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/xml/CargosoftXMLInvoiceImportService.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/xml/CargosoftXMLInvoiceImportService.java
index f3c9130..16d3c16 100644
--- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/xml/CargosoftXMLInvoiceImportService.java
+++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/xml/CargosoftXMLInvoiceImportService.java
@@ -472,6 +472,9 @@ public class CargosoftXMLInvoiceImportService {
// Row Positions lesen
readXMLRows(doc, workitem);
+ // Suche die ATC Nummer....
+ resolveATCNumber(doc, workitem);
+
// Jetzt noch das Space mapping
mahnlaufService.mapInvoiceTextToSpace(workitem, spacePosMappings);
@@ -562,6 +565,36 @@ public class CargosoftXMLInvoiceImportService {
}
}
+ /**
+ * Hilfsmethode die die ATC Nummer sucht.
+ * Wir gehen davon aus, das diese pro Rechnung nur einmal existieren kann.
+ *
+ * @param doc
+ * @param workitem
+ */
+ private void resolveATCNumber(Document doc, ItemCollection workitem) {
+ String atcNumber = null;
+ // Liste der BillingText-Elemente erhalten
+ NodeList billingTextList = doc.getElementsByTagName("BillingText");
+ if (billingTextList == null) {
+ return;
+ }
+ // Über die Liste iterieren und nach einem BillingText suchen, der mit "ATC"
+ // beginnt
+ for (int i = 0; i < billingTextList.getLength(); i++) {
+ Node billingTextNode = billingTextList.item(i);
+ if (billingTextNode.getNodeType() == Node.ELEMENT_NODE) {
+ String billingText = billingTextNode.getTextContent().trim();
+ if (billingText.startsWith("ATC")) {
+ atcNumber = billingText;
+ }
+ }
+ }
+ if (atcNumber != null && !atcNumber.isEmpty()) {
+ workitem.setItemValue("invoice.atc.number", atcNumber);
+ }
+ }
+
/**
* Reads the positions rows
*
@@ -596,7 +629,7 @@ public class CargosoftXMLInvoiceImportService {
.compile("InvoiceAmount/NetAmount/Amount/ExchangeRate/text()");
XPathExpression netActivityTypeExpr = xPath
- .compile("InvoiceAmount/ActivityType/Codes/Code[@Type='cs']/text()");
+ .compile("ActivityType/Codes/Code[@Type='cs']/text()");
NodeList rowList = doc.getElementsByTagName("InvoiceRow");
double _kurs = 0.0;
diff --git a/office-alexander-logistics-app/src/main/webapp/pages/admin/document_import.xhtml b/office-alexander-logistics-app/src/main/webapp/pages/admin/document_import.xhtml
index 075a4d7..c7b462e 100644
--- a/office-alexander-logistics-app/src/main/webapp/pages/admin/document_import.xhtml
+++ b/office-alexander-logistics-app/src/main/webapp/pages/admin/document_import.xhtml
@@ -184,13 +184,15 @@
- Type
-
-
+
diff --git a/workflow/zollanmeldung-de-1.0.0.bpmn b/workflow/steuerbescheid-de-1.0.0.bpmn
similarity index 93%
rename from workflow/zollanmeldung-de-1.0.0.bpmn
rename to workflow/steuerbescheid-de-1.0.0.bpmn
index caa18fb..dac3b2f 100644
--- a/workflow/zollanmeldung-de-1.0.0.bpmn
+++ b/workflow/steuerbescheid-de-1.0.0.bpmn
@@ -2,7 +2,7 @@
-
+
@@ -72,15 +72,15 @@ th { font-weight: bold;}