/*  
 *  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.
 * <p>
 * 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.
 * <p>
 * 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<String, List<String>> 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<String, List<String>> 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:
     * <InvoiceDate>2024-05-13T00:00:00+02:00</InvoiceDate>
     * 
     * @param doc        - xml doc
     * @param expression - xpath expression
     * @param workitem
     * @param itemName
     * @param itemType
     */
    private <T> void readXMLValue(Document doc, String expression, ItemCollection workitem, String itemName,
            Class<T> 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<ItemCollection> 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<ItemCollection> 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?
                        if (val.startsWith("ATC")) {
                            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<ItemCollection> 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;
    }
}
