/******************************************************************************* * Imixs Workflow * Copyright (C) 2001, 2011 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: * http://www.imixs.org * http://java.net/projects/imixs-workflow * * Contributors: * Imixs Software Solutions GmbH - initial API and implementation * Ralph Soika - Software Developer *******************************************************************************/ package com.alexanderlogistics.api; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.TimeZone; import java.util.logging.Logger; import javax.ejb.Stateless; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; 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.imixs.workflow.FileData; import org.imixs.workflow.ItemCollection; import org.imixs.workflow.engine.DocumentService; 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.xml.sax.InputSource; import org.xml.sax.SAXException; import com.alexanderlogistics.InvoiceUtil; import com.alexanderlogistics.xml.CargosoftXMLInvoiceImportService; /** ** Dieser Service korrigiert falsch importierte Cargosoft daten. * Ausgangsrechnungen die ein falsches Jahr haben. * * Oder DAten die über die XML Schnittstelle falsch reinkamen... * * @author rsoika * @version 1.1 */ @Stateless @Produces({ MediaType.TEXT_HTML, MediaType.APPLICATION_XHTML_XML, MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, MediaType.TEXT_XML }) @Path("/cargosoft") public class CargosoftMigrationRestService implements Serializable { private static final long serialVersionUID = 1L; String log = ""; int errors = 0; int count = 0; public static String _QUERY = "($modelversion:rechnungsausgang-de-1.0)\n" + "AND $created:[20240604 TO 20240701]"; // public static String _QUERY = "($modelversion:rechnungsausgang-dwc-1.0)\n" + // // // "AND $created:[20240604 TO 20240619]"; @Inject DocumentService documentService; @Inject WorkflowService workflowService; private static Logger logger = Logger.getLogger(CargosoftMigrationRestService.class.getName()); public CargosoftMigrationRestService() { super(); } /** * This method loads taxonomy data for a workflow group within a given process * and builds a ChartJS data structure in JSON format * * @param workflowgroup * @param task * @return * @throws QueryException */ @GET @Path("/test") @Produces({ MediaType.TEXT_PLAIN }) public String testeFehlerhafteCargosoftDaten() throws QueryException { int falsch = 0; logger.info("query=" + _QUERY); log = "Query\n" + _QUERY; List result = documentService.find(_QUERY, 9999, 0); log = log + "\n\nCount=" + result.size(); if (result.size() > 9999) { log = log + "\n\nACHTUNG ES GIBT MEHR ALS 10000 RECHNUNGEN !"; } for (ItemCollection workitem : result) { String type = workitem.getItemValueString("invoice.type"); if ("G".equals(type) || "SR".equals(type)) { double total = workitem.getItemValueDouble("invoice.saldo"); if (total >= 0) { logger.info("---Falsch: " + workitem.getUniqueID()); falsch++; workitem.setItemValue("invoice.saldo", -total); // documentService.save(workitem); } } } log = log + "\n\n"; log = log + "" + result.size() + " rechnungen geprüft"; log = log + "" + falsch + " davon falsch"; log = log + "\n\n"; return log; } /** * This method fixes the wrong amount of a imported xml invoice. * * @param workflowgroup * @param task * @return * @throws QueryException * @throws ModelException * @throws PluginException * @throws ProcessingErrorException * @throws AccessDeniedException */ public String fixMissingDueDate() throws QueryException, AccessDeniedException, ProcessingErrorException, PluginException, ModelException { logger.info("query=" + _QUERY); log = "Query\n\n" + _QUERY; List result = documentService.find(_QUERY, 9999, 0); log = log + "\n\nSelection Count=" + result.size(); log = log + "\n\nFixes\n\n"; for (ItemCollection workitem : result) { if (!workitem.hasItem("invoice.reminder")) { Date dueDate = workitem.getItemValueDate("invoice.duedate"); workitem.setItemValue("invoice.reminder", dueDate); count++; // save only documentService.save(workitem); } } log = log + "\n\n=== Completed! " + count + " fixes! ===\n\n"; return log; } @GET @Path("/fix") @Produces({ MediaType.TEXT_PLAIN }) public String fixMissingRate() throws QueryException, AccessDeniedException, ProcessingErrorException, PluginException, ModelException { logger.info("query=" + _QUERY); log = "Query\n\n" + _QUERY; List result = documentService.find(_QUERY, 9999, 0); log = log + "\n\nSelection Count=" + result.size(); log = log + "\n\nFixes\n\n"; for (ItemCollection workitem : result) { if (!workitem.hasItem("invoice.rate")) { // Fetch frist datev.kurs von _childitems List childs = InvoiceUtil.explodeChildList(workitem); if (childs != null && childs.size() > 0) { ItemCollection child = childs.get(0); double rate = child.getItemValueDouble("datev.kurs"); logger.info("..fix missing rate = " + rate); workitem.setItemValue("invoice.rate", rate); // save only documentService.save(workitem); } } } log = log + "\n\n=== Completed! " + count + " fixes! ===\n\n"; return log; } public String fixFehlerhafteCargosoftDatenMitSnapshot() throws QueryException, AccessDeniedException, ProcessingErrorException, PluginException, ModelException { logger.info("query=" + _QUERY); log = "Query\n\n" + _QUERY; List result = documentService.find(_QUERY, 9999, 0); log = log + "\n\nSelection Count=" + result.size(); log = log + "\n\nFixes\n\n"; for (ItemCollection workitem : result) { ItemCollection snapshot = null; // load snapshot String snapshotID = workitem.getItemValueString("$snapshotid"); if (!snapshotID.isEmpty()) { snapshot = documentService.load(snapshotID); } if (snapshot == null) { logger.warning("Unable to load snapshot for document " + workitem.getUniqueID()); log = log + "Unable to load snapshot for document " + workitem.getUniqueID() + " \n"; errors++; continue; } // Get raw data.... // find the XML Attachment String xmlFileName = null; List fileNames = snapshot.getFileNames(); for (String fileName : fileNames) { if (fileName.endsWith(".xml")) { xmlFileName = fileName; break; } } if (xmlFileName == null) { logger.warning("Unable to load XML from document " + workitem.getUniqueID()); log = log + "Unable to load XML from document " + workitem.getUniqueID() + " \n"; errors++; continue; } FileData xmlFileData = snapshot.getFileData(xmlFileName); Document xmlDoc = createXMLTree(xmlFileData.getContent()); migrateDocument(workitem, xmlDoc); } log = log + "\n\n=== Completed! " + count + " fixes! ===\n\n"; return log; } /** * Diese Methode korrigiert den Saldo um die Netto summe.... * * @param workitem * @param xmlDoc * @throws ModelException * @throws PluginException * @throws ProcessingErrorException * @throws AccessDeniedException */ private void migrateDocument(ItemCollection workitem, Document doc) throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException { // Haben wir schon das neue Feld 'invoice.total.net' mit dem Netto Betrag? if (workitem.hasItem("invoice.total.tax")) { // no migration needed return; } // Net und Tax ermitteln 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); // Der Total entspricht nun dem Net Amount. // Wenn wir eine Tax haben, dann müssen wir den Total sowie den Saldo um // diese Tax erhöhen. // Damit ist dann die Migration abgeschlossen. double tax = workitem.getItemValueDouble("invoice.total.tax"); if (tax > 0) { log = log + "...Migriere " + workitem.getItemValueString("invoice.number") + " Korrektur Betrag " + tax + "\n"; double total = workitem.getItemValueDouble("invoice.total"); double saldo = workitem.getItemValueDouble("invoice.saldo"); // Gutschrift? if (total < 0) { total = total - tax; saldo = saldo - tax; } else { // Rechnung total = total + tax; saldo = saldo + tax; } workitem.setItemValue("invoice.total", total); workitem.setItemValue("invoice.saldo", saldo); workitem.setItemValue("cargosoft.fix.xmlerror", tax); log = log + "....... total neu=" + total + " saldo neu=" + saldo + "\n"; if (5900 == workitem.getTaskID()) { workitem.setEventID(942); workflowService.processWorkItem(workitem); } else { // save only documentService.save(workitem); } count++; } else { log = log + "...Keine Migration notwendig " + workitem.getItemValueString("invoice.number") + " tax=" + tax + "\n"; } } /** * Erzeugt einen XML Baum aus dem XML Raw Daten * * @param snapshot * @return */ private Document createXMLTree(byte[] rawData) { Document doc = null; InputStream inputStream = new ByteArrayInputStream(rawData); InputSource inputSource = new InputSource(inputStream); DocumentBuilder documentBuilder; try { documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); doc = documentBuilder.parse(inputSource); } catch (ParserConfigurationException | SAXException | IOException e) { log = log + "Unable to load XML tree: " + e.getMessage() + " \n"; return null; } return doc; } /** * 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(CargosoftXMLInvoiceImportService.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()); } } }