modified: office-alexander-logistics-app/src/main/java/com/alexanderlogistics/datev/DatevCargosoftImportAdapter.java

modified:   office-alexander-logistics-app/src/main/java/com/alexanderlogistics/xml/CargosoftXMLInvoiceImportImportService.java
	modified:   reports/sepa/result_sepa01.xml
	modified:   reports/sepa/result_sepa02.xml
	deleted:    MAIL_OAUTH2.md
	deleted:    OP_MODULE.md
	modified:   README.md
This commit is contained in:
Ralph Soika 2024-05-27 21:19:10 +02:00
parent 7d5cf15aa1
commit 219ee170ff
4 changed files with 159 additions and 33 deletions

View file

@ -436,7 +436,7 @@ public class DatevCargosoftImportAdapter implements SignalAdapter {
// find the textblock... // find the textblock...
List<ItemCollection> result = documentService.find(sQuery, 1, 0); List<ItemCollection> result = documentService.find(sQuery, 1, 0);
if (result.size() > 0) { if (result.size() > 0) {
throw new AdapterException(DatevCargosoftImportAdapter.class.getName(), "DUPPLICATE", throw new AdapterException(DatevCargosoftImportAdapter.class.getName(), "DUPLICATE",
"Invoice already imported"); "Invoice already imported");
} }
} catch (QueryException e) { } catch (QueryException e) {

View file

@ -34,7 +34,9 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -59,17 +61,22 @@ import org.imixs.archive.importer.DocumentImportEvent;
import org.imixs.archive.importer.DocumentImportService; import org.imixs.archive.importer.DocumentImportService;
import org.imixs.workflow.FileData; import org.imixs.workflow.FileData;
import org.imixs.workflow.ItemCollection; import org.imixs.workflow.ItemCollection;
import org.imixs.workflow.engine.DocumentService;
import org.imixs.workflow.engine.ModelService; import org.imixs.workflow.engine.ModelService;
import org.imixs.workflow.engine.WorkflowService; import org.imixs.workflow.engine.WorkflowService;
import org.imixs.workflow.exceptions.AccessDeniedException; import org.imixs.workflow.exceptions.AccessDeniedException;
import org.imixs.workflow.exceptions.ModelException; import org.imixs.workflow.exceptions.ModelException;
import org.imixs.workflow.exceptions.PluginException; import org.imixs.workflow.exceptions.PluginException;
import org.imixs.workflow.exceptions.ProcessingErrorException; import org.imixs.workflow.exceptions.ProcessingErrorException;
import org.imixs.workflow.exceptions.QueryException;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource; import org.xml.sax.InputSource;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import com.alexanderlogistics.InvoiceUtil;
/** /**
* Der CargosoftXMLInvoiceImportImportService erweitert den FTPImportService und * Der CargosoftXMLInvoiceImportImportService erweitert den FTPImportService und
* kann XML Invoice Dokumente von Cargosoft importieren. * kann XML Invoice Dokumente von Cargosoft importieren.
@ -96,10 +103,13 @@ public class CargosoftXMLInvoiceImportImportService {
private static Logger logger = Logger.getLogger(CargosoftXMLInvoiceImportImportService.class.getName()); private static Logger logger = Logger.getLogger(CargosoftXMLInvoiceImportImportService.class.getName());
public static final String OPTION_MANDANT_ID = "mandant.id"; public static final String OPTION_MANDANT_ID = "mandant.id";
public static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
@EJB @EJB
WorkflowService workflowService; WorkflowService workflowService;
@EJB
DocumentService documentService;
@EJB @EJB
ModelService modelService; ModelService modelService;
@ -247,23 +257,25 @@ public class CargosoftXMLInvoiceImportImportService {
+ rawData.length); + rawData.length);
// create new workitem // create new workitem
invoice = createWorkitem(event.getSource(), file.getName(), rawData); invoice = createWorkitem(event.getSource(), file.getName(), rawData);
if (invoice == null) {
// persist workitem only if not exists and with a matching mandant.id // no new data found
if (!invoice.getItemValueString("mandant.id").equals(mandantID)) { continue;
throw new PluginException(CargosoftXMLInvoiceImportImportService.class.getName(),
"FTP_ERROR", "Invoice does not match mandantID " + mandantID);
} }
count++;
} else { } else {
logger.warning("...Warning - invalid file content '" logger.warning("...Warning - invalid file content '"
+ file.getName() + "' - file will be deleted!"); + file.getName() + "' - file will be deleted!");
} }
if (invoice != null) { 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(CargosoftXMLInvoiceImportImportService.class.getName(),
"FTP_ERROR", "Invoice does not match mandantID " + mandantID);
}
count++;
workflowService.processWorkItem(invoice); workflowService.processWorkItem(invoice);
} }
// finally delete the file.... // finally move the file....
// ftpClient.deleteFile(file.getName()); // ftpClient.deleteFile(file.getName());
} catch (AccessDeniedException | ProcessingErrorException | PluginException } catch (AccessDeniedException | ProcessingErrorException | PluginException
| ModelException e) { | ModelException e) {
@ -314,6 +326,7 @@ public class CargosoftXMLInvoiceImportImportService {
workitem.task(source.getItemValueInteger(DocumentImportService.SOURCE_ITEM_TASK)); workitem.task(source.getItemValueInteger(DocumentImportService.SOURCE_ITEM_TASK));
workitem.event(source.getItemValueInteger(DocumentImportService.SOURCE_ITEM_EVENT)); workitem.event(source.getItemValueInteger(DocumentImportService.SOURCE_ITEM_EVENT));
workitem.setWorkflowGroup(source.getItemValueString("workflowgroup")); workitem.setWorkflowGroup(source.getItemValueString("workflowgroup"));
workitem.setItemValue("cargosoft.import.filename", fileName);
// Now we parse the rawData.... // Now we parse the rawData....
InputStream inputStream = new ByteArrayInputStream(rawData); InputStream inputStream = new ByteArrayInputStream(rawData);
@ -338,16 +351,28 @@ public class CargosoftXMLInvoiceImportImportService {
readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/Client/Codes/Code", workitem, readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/Client/Codes/Code", workitem,
"mandant.id", String.class); "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/InvoiceAmount/NetAmount/Amount/Value", readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAmount/NetAmount/Amount/Value",
workitem, "invoice.total", String.class); workitem, "invoice.total", String.class);
readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceCurrency/Codes/Code", readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceCurrency/Codes/Code",
workitem, "invoice.currency", String.class); workitem, "invoice.currency", String.class);
// invoice.date <InvoiceDate>2024-05-13T00:00:00+02:00</InvoiceDate>
readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceDate", readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceDate",
workitem, "invoice.date", Date.class); workitem, "invoice.date", Date.class);
readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/PaymentConditions/DueDate",
workitem, "invoice.duedate", Date.class);
readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAddress/Codes/Code", readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAddress/Codes/Code",
workitem, "dbtr.number", String.class); workitem, "dbtr.number", String.class);
String dbtrNumber = workitem.getItemValueString("dbtr.number"); String dbtrNumber = workitem.getItemValueString("dbtr.number");
@ -355,6 +380,10 @@ public class CargosoftXMLInvoiceImportImportService {
workitem.setItemValue("dbtr.number", dbtrNumber.substring(1)); workitem.setItemValue("dbtr.number", dbtrNumber.substring(1));
} }
// Row Positions lesen
// InvoiceRows/InvoiceRow
readXMLRows(doc, workitem);
// read filename // read filename
readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/Attachments/Attachment/Filename", readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/Attachments/Attachment/Filename",
workitem, "import.filename", String.class); workitem, "import.filename", String.class);
@ -368,7 +397,7 @@ public class CargosoftXMLInvoiceImportImportService {
workitem.removeItem("import.filename"); workitem.removeItem("import.filename");
workitem.removeItem("import.filedata"); workitem.removeItem("import.filedata");
} catch (ParserConfigurationException | SAXException | IOException | XPathExpressionException e) { } catch (ParserConfigurationException | SAXException | IOException e) {
throw new PluginException(CargosoftXMLInvoiceImportImportService.class.getName(), throw new PluginException(CargosoftXMLInvoiceImportImportService.class.getName(),
"XML_ERROR", e.getMessage()); "XML_ERROR", e.getMessage());
} }
@ -384,39 +413,136 @@ public class CargosoftXMLInvoiceImportImportService {
* *
* /Invoices/Invoice/InvoiceHeader/Client/Code * /Invoices/Invoice/InvoiceHeader/Client/Code
* *
* Beispiel Datum:
* <InvoiceDate>2024-05-13T00:00:00+02:00</InvoiceDate>
*
* @param doc - xml doc * @param doc - xml doc
* @param expression - xpath expression * @param expression - xpath expression
* @param workitem * @param workitem
* @param itemName * @param itemName
* @param itemType * @param itemType
* @throws XPathExpressionException
* @throws PluginException
*/ */
private <T> void readXMLValue(Document doc, String expression, ItemCollection workitem, String itemName, private <T> void readXMLValue(Document doc, String expression, ItemCollection workitem, String itemName,
Class<T> itemType) throws XPathExpressionException { Class<T> itemType) {
// create XPath... // create XPath...
XPathFactory xpathFactory = XPathFactory.newInstance(); XPathFactory xpathFactory = XPathFactory.newInstance();
XPath xpath = xpathFactory.newXPath(); XPath xpath = xpathFactory.newXPath();
XPathExpression xPathExpression = xpath.compile(expression); XPathExpression xPathExpression;
// extract node value try {
Node valueNode = (Node) xPathExpression.evaluate(doc, XPathConstants.NODE); xPathExpression = xpath.compile(expression);
if (valueNode != null) {
String value = valueNode.getTextContent();
if (itemType == Date.class) { // extract node value
// 2024-05-13T00:00:00+02:00 Node valueNode = (Node) xPathExpression.evaluate(doc, XPathConstants.NODE);
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); if (valueNode != null) {
formatter.setTimeZone(TimeZone.getTimeZone("CET")); String value = valueNode.getTextContent();
try {
workitem.setItemValue(itemName, formatter.parse(value)); if (itemType == Date.class) {
} catch (ParseException e) { // 2024-05-13T00:00:00+02:00
logger.warning("Invalid Date Format"); 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) {
} else { workitem.setItemValue(itemName, Double.parseDouble(value));
return;
}
if (itemType == Integer.class) {
workitem.setItemValue(itemName, Integer.parseInt(value));
return;
}
// Default String format
workitem.setItemValue(itemName, value); 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<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()");
NodeList rowList = doc.getElementsByTagName("InvoiceRow");
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);
String currency = (String) netAmountCurrencyExpr.evaluate(rowNode, XPathConstants.STRING);
childItemCol.setItemValue("datev.wkz", currency);
String umsatz = (String) netAmountValueExpr.evaluate(rowNode, XPathConstants.STRING);
childItemCol.setItemValue("datev.umsatz", Double.parseDouble(umsatz));
childItems.add(childItemCol);
}
}
} catch (XPathExpressionException e) {
logger.warning("Unable to read row : " + e.getMessage());
}
// Set child items
InvoiceUtil.implodeChildList(workitem, childItems);
}
/**
* 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: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;
}
} }

View file

@ -6,7 +6,7 @@
<CstmrCdtTrfInitn> <CstmrCdtTrfInitn>
<GrpHdr> <GrpHdr>
<MsgId>bf073e72asdf512c4d60af7740872036</MsgId> <MsgId>bf073e72asdf512c4d60af7740872036</MsgId>
<CreDtTm>2024-04-12T09:10:53</CreDtTm> <CreDtTm>2024-04-26T22:29:58</CreDtTm>
<NbOfTxs>3</NbOfTxs> <NbOfTxs>3</NbOfTxs>
<CtrlSum>655.90</CtrlSum> <CtrlSum>655.90</CtrlSum>
<InitgPty> <InitgPty>
@ -23,7 +23,7 @@
<Cd>SEPA</Cd> <Cd>SEPA</Cd>
</SvcLvl> </SvcLvl>
</PmtTpInf> </PmtTpInf>
<ReqdExctnDt>2024-04-12</ReqdExctnDt> <ReqdExctnDt>2024-04-26</ReqdExctnDt>
<Dbtr> <Dbtr>
<Nm>Targo Bank</Nm> <Nm>Targo Bank</Nm>
</Dbtr> </Dbtr>

View file

@ -6,7 +6,7 @@
<CstmrCdtTrfInitn> <CstmrCdtTrfInitn>
<GrpHdr> <GrpHdr>
<MsgId>bf073e72asdf512c4d60af7740872036</MsgId> <MsgId>bf073e72asdf512c4d60af7740872036</MsgId>
<CreDtTm>2024-04-12T09:10:53</CreDtTm> <CreDtTm>2024-04-26T22:29:58</CreDtTm>
<NbOfTxs>3</NbOfTxs> <NbOfTxs>3</NbOfTxs>
<CtrlSum>655.90</CtrlSum> <CtrlSum>655.90</CtrlSum>
<InitgPty> <InitgPty>
@ -23,7 +23,7 @@
<Cd>SEPA</Cd> <Cd>SEPA</Cd>
</SvcLvl> </SvcLvl>
</PmtTpInf> </PmtTpInf>
<ReqdExctnDt>2024-04-12</ReqdExctnDt> <ReqdExctnDt>2024-04-26</ReqdExctnDt>
<Dbtr> <Dbtr>
<Nm>Targo Bank</Nm> <Nm>Targo Bank</Nm>
</Dbtr> </Dbtr>