xml invoice import

This commit is contained in:
Ralph Soika 2024-05-28 13:17:17 +02:00
parent f9169a184b
commit 2203a41d3b
3 changed files with 190 additions and 69 deletions

View file

@ -8,12 +8,9 @@ import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject; import javax.inject.Inject;
@ -33,6 +30,7 @@ import org.imixs.workflow.exceptions.QueryException;
import com.alexanderlogistics.InvoiceUtil; import com.alexanderlogistics.InvoiceUtil;
import com.alexanderlogistics.KreditorDebitorService; import com.alexanderlogistics.KreditorDebitorService;
import com.alexanderlogistics.mahnlauf.MahnlaufService;
/** /**
* Der DatevCargosoftImportAdapter liest eine von Cargosoft bereitgestellte * Der DatevCargosoftImportAdapter liest eine von Cargosoft bereitgestellte
@ -126,6 +124,9 @@ public class DatevCargosoftImportAdapter implements SignalAdapter {
@Inject @Inject
TeamService teamService; TeamService teamService;
@Inject
MahnlaufService mahnlaufService;
private String INITIAL_MODEL_VERSION_INVOICE; private String INITIAL_MODEL_VERSION_INVOICE;
private int INITIAL_TASK_ID_INVOICE; private int INITIAL_TASK_ID_INVOICE;
private int INITIAL_EVENT_ID_INVOICE; private int INITIAL_EVENT_ID_INVOICE;
@ -206,7 +207,7 @@ public class DatevCargosoftImportAdapter implements SignalAdapter {
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMdd"); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMdd");
// load spaces Pos Expressions // load spaces Pos Expressions
Map<String, List<String>> spacePosMappings = loadSpacePosMappings(); Map<String, List<String>> spacePosMappings = mahnlaufService.loadSpacePosMappings();
ItemCollection invoiceWorkitem = null; ItemCollection invoiceWorkitem = null;
List<ItemCollection> splitBuchungen = new ArrayList<ItemCollection>(); List<ItemCollection> splitBuchungen = new ArrayList<ItemCollection>();
@ -385,27 +386,6 @@ public class DatevCargosoftImportAdapter implements SignalAdapter {
} }
/**
* Helper method that loads all expressions from the spaces
*
* @return List of list of expressions
*/
private Map<String, List<String>> loadSpacePosMappings() {
Map<String, List<String>> result = new HashMap<>();
// als erstes lesen wir die pos.mappings aus den Spaces ein, auf die dann eine
// Rechnung emapped werden kann.
List<ItemCollection> spaces_subs = teamService.getSpaces();
for (ItemCollection stub : spaces_subs) {
// load full workitem
ItemCollection space = documentService.load(stub.getUniqueID());
if (space != null) {
result.put(space.getUniqueID(), space.getItemValueList("pos.mapping", String.class));
}
}
return result;
}
/** /**
* This method returns a new empty invoice ItemCollection. * This method returns a new empty invoice ItemCollection.
* <p> * <p>
@ -525,27 +505,29 @@ public class DatevCargosoftImportAdapter implements SignalAdapter {
} }
// Jezt noch das Space mapping // Jezt noch das Space mapping
// dazu kucken wir in alle spaces den config wert 'pos.mapping' mahnlaufService.mapInvoiceTextToSpace(datevWorkitem, spacePosMappings);
String text = datevWorkitem.getItemValueString("invoice.text"); // // dazu kucken wir in alle spaces den config wert 'pos.mapping'
if (!text.isEmpty()) { // String text = datevWorkitem.getItemValueString("invoice.text");
boolean found = false; // if (!text.isEmpty()) {
for (Map.Entry<String, List<String>> entry : spacePosMappings.entrySet()) { // boolean found = false;
List<String> expressionsList = entry.getValue(); // for (Map.Entry<String, List<String>> entry : spacePosMappings.entrySet()) {
for (String expr : expressionsList) { // List<String> expressionsList = entry.getValue();
Pattern p = Pattern.compile(expr); // for (String expr : expressionsList) {
Matcher m = p.matcher(text); // Pattern p = Pattern.compile(expr);
if (m.find()) { // Matcher m = p.matcher(text);
// we have a match // if (m.find()) {
datevWorkitem.setItemValue("space.ref", entry.getKey()); // // we have a match
found = true; // datevWorkitem.setItemValue("space.ref", entry.getKey());
break; // found = true;
} // break;
} // }
if (found) { // }
break; // if (found) {
} // break;
} // }
} // }
// }
// Processe die Invoice // Processe die Invoice
datevWorkitem.model(INITIAL_MODEL_VERSION_INVOICE).task(INITIAL_TASK_ID_INVOICE) datevWorkitem.model(INITIAL_MODEL_VERSION_INVOICE).task(INITIAL_TASK_ID_INVOICE)
.event(INITIAL_EVENT_ID_INVOICE); .event(INITIAL_EVENT_ID_INVOICE);

View file

@ -27,13 +27,21 @@
package com.alexanderlogistics.mahnlauf; package com.alexanderlogistics.mahnlauf;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.security.DeclareRoles; import javax.annotation.security.DeclareRoles;
import javax.annotation.security.RolesAllowed; import javax.annotation.security.RolesAllowed;
import javax.annotation.security.RunAs; import javax.annotation.security.RunAs;
import javax.ejb.Singleton; import javax.ejb.Singleton;
import javax.inject.Inject; import javax.inject.Inject;
import org.imixs.marty.team.TeamService;
import org.imixs.workflow.ItemCollection; import org.imixs.workflow.ItemCollection;
import org.imixs.workflow.engine.DocumentService;
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;
@ -58,22 +66,24 @@ import org.imixs.workflow.exceptions.ProcessingErrorException;
@RunAs("org.imixs.ACCESSLEVEL.MANAGERACCESS") @RunAs("org.imixs.ACCESSLEVEL.MANAGERACCESS")
public class MahnlaufService { public class MahnlaufService {
public static final int TASK_MAHNLAUF_START = 5100;
public static final int TASK_MAHNLAUF_START = 5100; public static final int TASK_MAHNLAUF_END = 5299;
public static final int TASK_MAHNLAUF_END = 5299; public static final int EVENT_MAHNLAUF_EXECUTE = 300;
public static final int EVENT_MAHNLAUF_EXECUTE = 300;
public static final String ITEM_DBTR_NUMBER = "dbtr.number";
public static final String ITEM_DBTR_NUMBER = "dbtr.number"; public static final String ITEM_DBTR_NAME = "dbtr.name";
public static final String ITEM_DBTR_NAME = "dbtr.name"; public static final String ITEM_DBTR_EMAIL = "dbtr.mail";
public static final String ITEM_DBTR_EMAIL = "dbtr.mail"; public static final String ERROR_CONFIG = "CONFIG_ERROR";
public static final String ERROR_CONFIG = "CONFIG_ERROR"; public static final String ERROR_WORKFLOW = "ERROR_WORKFLOW";
public static final String ERROR_WORKFLOW = "ERROR_WORKFLOW";
@Inject @Inject
WorkflowService workflowService; WorkflowService workflowService;
@Inject
TeamService teamService;
@Inject
DocumentService documentService;
/** /**
* * Diese Method processed eine Invoice mit Manager Rechnten * * Diese Method processed eine Invoice mit Manager Rechnten
@ -92,4 +102,55 @@ public class MahnlaufService {
} }
} }
/**
* Helper method that loads all expressions from the spaces
*
* @return List of list of expressions
*/
public Map<String, List<String>> loadSpacePosMappings() {
Map<String, List<String>> result = new HashMap<>();
// als erstes lesen wir die pos.mappings aus den Spaces ein, auf die dann eine
// Rechnung emapped werden kann.
List<ItemCollection> spaces_subs = teamService.getSpaces();
for (ItemCollection stub : spaces_subs) {
// load full workitem
ItemCollection space = documentService.load(stub.getUniqueID());
if (space != null) {
result.put(space.getUniqueID(), space.getItemValueList("pos.mapping", String.class));
}
}
return result;
}
/**
* Hilfsmethode zum errechnen des richtigen Spaces zu einer invoice.text Positon
*
* @param workitem
* @param spacePosMappings
*/
public void mapInvoiceTextToSpace(ItemCollection workitem, Map<String, List<String>> spacePosMappings) {
// Jezt noch das Space mapping
// dazu kucken wir in alle spaces den config wert 'pos.mapping'
String text = workitem.getItemValueString("invoice.text");
if (!text.isEmpty()) {
boolean found = false;
for (Map.Entry<String, List<String>> entry : spacePosMappings.entrySet()) {
List<String> expressionsList = entry.getValue();
for (String expr : expressionsList) {
Pattern p = Pattern.compile(expr);
Matcher m = p.matcher(text);
if (m.find()) {
// we have a match
workitem.setItemValue("space.ref", entry.getKey());
found = true;
break;
}
}
if (found) {
break;
}
}
}
}
} }

View file

@ -37,6 +37,7 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map;
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;
@ -44,6 +45,7 @@ import java.util.logging.Logger;
import javax.ejb.EJB; import javax.ejb.EJB;
import javax.ejb.Stateless; import javax.ejb.Stateless;
import javax.enterprise.event.Observes; import javax.enterprise.event.Observes;
import javax.inject.Inject;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
@ -76,6 +78,8 @@ import org.xml.sax.InputSource;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import com.alexanderlogistics.InvoiceUtil; import com.alexanderlogistics.InvoiceUtil;
import com.alexanderlogistics.KreditorDebitorService;
import com.alexanderlogistics.mahnlauf.MahnlaufService;
/** /**
* Der CargosoftXMLInvoiceImportImportService erweitert den FTPImportService und * Der CargosoftXMLInvoiceImportImportService erweitert den FTPImportService und
@ -110,12 +114,19 @@ public class CargosoftXMLInvoiceImportImportService {
@EJB @EJB
DocumentService documentService; DocumentService documentService;
@EJB @EJB
ModelService modelService; ModelService modelService;
@EJB
KreditorDebitorService kreditorDebitorService;
@EJB @EJB
DocumentImportService documentImportService; DocumentImportService documentImportService;
@Inject
MahnlaufService mahnlaufService;
/** /**
* This method reacts on a CDI ImportEvent and reads documents form a ftp * This method reacts on a CDI ImportEvent and reads documents form a ftp
* server. * server.
@ -226,6 +237,10 @@ public class CargosoftXMLInvoiceImportImportService {
private void scannFTPDirectory(FTPClient ftpClient, String ftpPath, String mandantID, DocumentImportEvent event) private void scannFTPDirectory(FTPClient ftpClient, String ftpPath, String mandantID, DocumentImportEvent event)
throws PluginException { throws PluginException {
try { try {
// load spaces Pos Expressions
Map<String, List<String>> spacePosMappings = mahnlaufService.loadSpacePosMappings();
logger.finest("......read directory " + ftpPath); logger.finest("......read directory " + ftpPath);
// try to enter the working directory and read all files... // try to enter the working directory and read all files...
boolean bWorkingDir = ftpClient.changeWorkingDirectory(ftpPath); boolean bWorkingDir = ftpClient.changeWorkingDirectory(ftpPath);
@ -256,7 +271,7 @@ public class CargosoftXMLInvoiceImportImportService {
logger.finest("......file '" + file.getName() + "' successful read - bytes size = " logger.finest("......file '" + file.getName() + "' successful read - bytes size = "
+ rawData.length); + rawData.length);
// create new workitem // create new workitem
invoice = createWorkitem(event.getSource(), file.getName(), rawData); invoice = createWorkitem(event.getSource(), file.getName(), rawData, spacePosMappings);
if (invoice == null) { if (invoice == null) {
// no new data found // no new data found
continue; continue;
@ -319,7 +334,8 @@ public class CargosoftXMLInvoiceImportImportService {
* @throws ProcessingErrorException * @throws ProcessingErrorException
* @throws AccessDeniedException * @throws AccessDeniedException
*/ */
public ItemCollection createWorkitem(ItemCollection source, String fileName, byte[] rawData) public ItemCollection createWorkitem(ItemCollection source, String fileName, byte[] rawData,
Map<String, List<String>> spacePosMappings)
throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException { throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException {
ItemCollection workitem = new ItemCollection(); ItemCollection workitem = new ItemCollection();
workitem.model(source.getItemValueString(DocumentImportService.SOURCE_ITEM_MODELVERSION)); workitem.model(source.getItemValueString(DocumentImportService.SOURCE_ITEM_MODELVERSION));
@ -361,8 +377,20 @@ public class CargosoftXMLInvoiceImportImportService {
return null; return null;
} }
readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAmount/NetAmount/Amount/Value", readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceType/Codes/Code",
workitem, "invoice.total", String.class); workitem, "invoice.type", String.class);
// Gutschrift oder Rechnung?
if ("G".equals(workitem.getItemValueString("invoice.type"))) {
readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAmount/NetAmount/Amount/Value",
workitem, "invoice.total", Double.class);
workitem.setItemValue("invoice.saldo", -workitem.getItemValueDouble("invoice.total"));
workitem.setItemValue("invoice.total", -workitem.getItemValueDouble("invoice.total"));
} else {
readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAmount/NetAmount/Amount/Value",
workitem, "invoice.total", Double.class);
workitem.setItemValue("invoice.saldo", workitem.getItemValueDouble("invoice.total"));
}
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);
@ -370,6 +398,10 @@ public class CargosoftXMLInvoiceImportImportService {
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/PaymentCondition/Codes/Code[@Type='cs']",
workitem, "payment.term", String.class);
readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/PaymentConditions/DueDate", readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/PaymentConditions/DueDate",
workitem, "invoice.duedate", Date.class); workitem, "invoice.duedate", Date.class);
@ -379,10 +411,21 @@ public class CargosoftXMLInvoiceImportImportService {
if (dbtrNumber.startsWith("K") || dbtrNumber.startsWith("D")) { if (dbtrNumber.startsWith("K") || dbtrNumber.startsWith("D")) {
workitem.setItemValue("dbtr.number", dbtrNumber.substring(1)); 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"));
}
} catch (PluginException e) {
// e.printStackTrace();
}
// Row Positions lesen // Row Positions lesen
// InvoiceRows/InvoiceRow // InvoiceRows/InvoiceRow
readXMLRows(doc, workitem); readXMLRows(doc, workitem);
// Jetzt noch das Space mapping
mahnlaufService.mapInvoiceTextToSpace(workitem, spacePosMappings);
// read filename // read filename
readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/Attachments/Attachment/Filename", readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/Attachments/Attachment/Filename",
@ -404,9 +447,6 @@ public class CargosoftXMLInvoiceImportImportService {
return workitem; return workitem;
} }
public <T> void getMist(String itemName, Class<T> itemType) {
}
/** /**
* Reads a tag value from the xml tree and set the value into the given * Reads a tag value from the xml tree and set the value into the given
* workitem. * workitem.
@ -447,11 +487,11 @@ public class CargosoftXMLInvoiceImportImportService {
} }
return; return;
} }
if (itemType == Double.class) { if (itemType == Double.class && value != null && !value.isEmpty()) {
workitem.setItemValue(itemName, Double.parseDouble(value)); workitem.setItemValue(itemName, Double.parseDouble(value));
return; return;
} }
if (itemType == Integer.class) { if (itemType == Integer.class && value != null && !value.isEmpty()) {
workitem.setItemValue(itemName, Integer.parseInt(value)); workitem.setItemValue(itemName, Integer.parseInt(value));
return; return;
} }
@ -495,6 +535,11 @@ public class CargosoftXMLInvoiceImportImportService {
XPathExpression netAmountCurrencyExpr = xPath XPathExpression netAmountCurrencyExpr = xPath
.compile("InvoiceAmount/NetAmount/Amount/Currency/Codes/Code[@Type='cs']/text()"); .compile("InvoiceAmount/NetAmount/Amount/Currency/Codes/Code[@Type='cs']/text()");
XPathExpression netAmountValueExpr = xPath.compile("InvoiceAmount/NetAmount/Amount/Value/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"); NodeList rowList = doc.getElementsByTagName("InvoiceRow");
for (int i = 0; i < rowList.getLength(); i++) { for (int i = 0; i < rowList.getLength(); i++) {
@ -505,10 +550,41 @@ public class CargosoftXMLInvoiceImportImportService {
// Get the FileNumber value // Get the FileNumber value
String fileNumber = (String) fileNumberExpr.evaluate(rowNode, XPathConstants.STRING); String fileNumber = (String) fileNumberExpr.evaluate(rowNode, XPathConstants.STRING);
childItemCol.setItemValue("datev.text", fileNumber); 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); String currency = (String) netAmountCurrencyExpr.evaluate(rowNode, XPathConstants.STRING);
childItemCol.setItemValue("datev.wkz", currency); childItemCol.setItemValue("datev.wkz", currency);
// Umsatz
String umsatz = (String) netAmountValueExpr.evaluate(rowNode, XPathConstants.STRING); String umsatz = (String) netAmountValueExpr.evaluate(rowNode, XPathConstants.STRING);
childItemCol.setItemValue("datev.umsatz", Double.parseDouble(umsatz)); double dUmsatz = Double.parseDouble(umsatz);
childItemCol.setItemValue("datev.umsatz", dUmsatz);
// Kurs
String kurs = (String) netAmountExchangeRateExpr.evaluate(rowNode, XPathConstants.STRING);
if (kurs != null && !kurs.isEmpty()) {
Double dKurs = Double.parseDouble(kurs);
childItemCol.setItemValue("datev.kurs", dKurs);
// Basisumsatz
if (dKurs != 0) {
double basisUmsatz = dUmsatz / dKurs;
double gerundeterBasisUmsatz = Math.round(basisUmsatz * 100) / 100.0;
childItemCol.setItemValue("datev.basisumsatz", gerundeterBasisUmsatz);
}
}
String activityType = (String) netActivityTypeExpr.evaluate(rowNode, XPathConstants.STRING);
childItemCol.setItemValue("category", activityType);
// Invoice Type S/H
if ("G".equals(workitem.getItemValueString("invoice.type"))) {
// Gutschrift
childItemCol.setItemValue("datev.shzeichen", "S");
} else {
// Rechnung
childItemCol.setItemValue("datev.shzeichen", "H");
}
childItems.add(childItemCol); childItems.add(childItemCol);
} }
@ -521,6 +597,8 @@ public class CargosoftXMLInvoiceImportImportService {
// Set child items // Set child items
InvoiceUtil.implodeChildList(workitem, childItems); InvoiceUtil.implodeChildList(workitem, childItems);
// invoice.text ist erste position
workitem.setItemValue("invoice.text", workitem.getItemValueString("invoice.positions"));
} }