diff --git a/office-alexander-logistics-app/pom.xml b/office-alexander-logistics-app/pom.xml
index c053516..ccee67d 100644
--- a/office-alexander-logistics-app/pom.xml
+++ b/office-alexander-logistics-app/pom.xml
@@ -315,6 +315,13 @@
compile
+
+
+ org.imixs.util
+ imixs-e-invoice
+ ${org.imixs.e-invoice.version}
+
+
net.sf.saxon
diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/einvoice/EInvoiceConverterAdapter.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/einvoice/EInvoiceConverterAdapter.java
index 4b2431d..13fe39a 100644
--- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/einvoice/EInvoiceConverterAdapter.java
+++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/einvoice/EInvoiceConverterAdapter.java
@@ -6,17 +6,15 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.text.NumberFormat;
-import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
-import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Logger;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+
+import javax.xml.transform.TransformerException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
@@ -39,6 +37,7 @@ import org.imixs.workflow.exceptions.PluginException;
import org.imixs.workflow.exceptions.QueryException;
import org.imixs.workflow.util.XMLParser;
+import com.alexanderlogistics.BusinessPartnerService;
import com.alexanderlogistics.InvoiceUtil;
import jakarta.inject.Inject;
@@ -100,6 +99,9 @@ public class EInvoiceConverterAdapter implements SignalAdapter {
@Inject
SnapshotService snapshotService;
+ @Inject
+ BusinessPartnerService businessPartnerService;
+
/**
* This method
*
@@ -126,12 +128,21 @@ public class EInvoiceConverterAdapter implements SignalAdapter {
logger.info("│ ├── Source File: " + pdfFileData.getName());
+ updateEInvoice(xmlFileData, workitem);
+
+ // append document
+ logger.info("│ ├── append new e-invoice");
+ workitem.addFileData(xmlFileData);
+
+ // Embedd XML.....
+ logger.info("│ ├── embed e-invoice into PDF");
+ FileData eInvoice = embeddXML(pdfFileData, xmlFileData);
+ workitem.removeFile(pdfFileData.getName());
+ // set new pdf file...
+ workitem.addFileData(eInvoice);
+
} catch (PluginException e) {
throw new AdapterException(e);
- // throw new AdapterException(
- // EInvoiceConverterAdapter.class.getSimpleName(), e.getErrorCode(),
- // e.getMessage());
-
}
return workitem;
@@ -241,7 +252,6 @@ public class EInvoiceConverterAdapter implements SignalAdapter {
* @param workitem
* @throws PluginException
*/
- @SuppressWarnings("unchecked")
private void updateEInvoice(FileData fileDataXMLTemplate, ItemCollection workitem) throws PluginException {
try {
@@ -250,51 +260,46 @@ public class EInvoiceConverterAdapter implements SignalAdapter {
model.setId(workitem.getItemValueString("invoice.number"));
// Update Rechnungssummen...
- if (workitem.getItemValueDouble("invoice.vat.rate") > 0) {
+ if (workitem.getItemValueDouble("invoice.total.tax") > 0) {
// wir haben eine Steuer!
- model.setTaxTotalAmount(InvoiceUtil.round(workitem.getItemValueDouble("invoice.amount.total")
- - workitem.getItemValueDouble("invoice.amount.net")));
- model.setTaxRate(workitem.getItemValueDouble("invoice.vat.rate"));
+ model.setTaxTotalAmount(InvoiceUtil.round(workitem.getItemValueDouble("invoice.total")
+ - workitem.getItemValueDouble("invoice.total.net")));
+ model.setTaxRate(workitem.getItemValueDouble("invoice.total.tax"));
}
- // // Update Invoice Items
- // List invoiceItems = explodeChildList(workitem);
- // double lineTotalAmount = 0.00;
- // for (ItemCollection invoiceItem : invoiceItems) {
- // TradeLineItem tradeLineItem = buildTradeLineItem(invoiceItem);
- // model.setTradeLineItem(tradeLineItem);
- // lineTotalAmount = lineTotalAmount + tradeLineItem.getTotal();
- // }
+ // Update Invoice Items
+ List invoiceItems = InvoiceUtil.explodeChildList(workitem, "_childitems");
+ double lineTotalAmount = 0.00;
+ for (ItemCollection invoiceItem : invoiceItems) {
+ TradeLineItem tradeLineItem = buildTradeLineItem(invoiceItem);
+ model.setTradeLineItem(tradeLineItem);
+ lineTotalAmount = lineTotalAmount + tradeLineItem.getTotal();
+ }
// // Summenbildung
- // model.setNetTotalAmount(workitem.getItemValueDouble("invoice.amount.net"));
- // model.setGrandTotalAmount(workitem.getItemValueDouble("invoice.amount.total"));
- // model.setIssueDateTime(workitem.getItemValueLocalDate("invoice.date"));
+ model.setNetTotalAmount(workitem.getItemValueDouble("invoice.total.net"));
+ model.setGrandTotalAmount(workitem.getItemValueDouble("invoice.total"));
+ model.setIssueDateTime(workitem.getItemValueLocalDate("invoice.date"));
- // // Update Addresses
- // TradeParty billingAddress =
- // parseAddress(workitem.getItemValue("dbtr.address"), "buyer");
- // model.setTradeParty(billingAddress);
+ // Update Addresses
+ TradeParty billingAddress = buildAddress(workitem.getItemValueString("partner.id"), "buyer");
+ model.setTradeParty(billingAddress);
- // TradeParty shippingAddress =
- // parseAddress(workitem.getItemValue("dbtr.address.shipping"), "ship_to");
- // model.setTradeParty(shippingAddress);
+ TradeParty shippingAddress = buildAddress(workitem.getItemValueString("partner.id"), "ship_to");
+ model.setTradeParty(shippingAddress);
+ // References>AB230321
// model.setOrderReferenceId(workitem.getItemValueString("order.number"));
- // // finally update the template file
- // fileDataXMLTemplate.setContent(model.getContent());
+ // finally update the template file
+ fileDataXMLTemplate.setContent(model.getContent());
- } catch (FileNotFoundException | EInvoiceFormatException e) {
+ } catch (FileNotFoundException | EInvoiceFormatException | TransformerException e) {
throw new PluginException(this.getClass().getName(), DOCUMENT_ERROR, e.getMessage(), e);
}
}
- /**
- * Parses a list of address lines into a TradeParty object using best-guess
- * approach
- */
/**
* Erstellt ein TradeParty Objekt aus einer Liste von Adresszeilen.
*
@@ -302,98 +307,28 @@ public class EInvoiceConverterAdapter implements SignalAdapter {
* @param type Der Typ der TradeParty
* @return TradeParty
*/
- public static TradeParty parseAddress(List addressLines, String type) {
- if (addressLines == null || addressLines.isEmpty()) {
- throw new IllegalArgumentException("Address lines cannot be null or empty");
+ public TradeParty buildAddress(String partnerID, String type) throws PluginException {
+ if (partnerID == null || partnerID.isEmpty()) {
+ throw new PluginException(this.getClass().getName(), DOCUMENT_ERROR, "Missing partnerID");
}
+ ItemCollection businessPartner = businessPartnerService.getBusinessPartnerByID(partnerID);
+ if (businessPartner == null) {
+ throw new PluginException(this.getClass().getName(), DOCUMENT_ERROR,
+ "Business Partner ID '" + partnerID + "' does not exist");
+ }
TradeParty tradeParty = new TradeParty(type);
// Name ist immer die erste Zeile
- tradeParty.setName(addressLines.get(0).trim());
-
- // Letzte Zeile analysieren
- String lastLine = addressLines.get(addressLines.size() - 1).trim();
-
- // Prüfen ob die letzte Zeile ein PLZ + Stadt Format hat
- if (hasPostcodeAndCity(lastLine)) {
- tradeParty.setCountryId("DE");
- parsePostcodeAndCity(lastLine, tradeParty);
-
- // Straßenadresse aus den Zeilen zwischen Name und PLZ/Stadt
- buildStreetAddress(addressLines.subList(1, addressLines.size() - 1), tradeParty);
- } else {
- // Wenn die letzte Zeile kein PLZ + Stadt Format hat, dann ist es ein Land
- String countryCode = resolveISOCountryCodes(lastLine.toUpperCase());
- tradeParty.setCountryId(countryCode);
- // Stadt und PLZ sind in der vorletzten Zeile
- if (addressLines.size() > 2) {
- parsePostcodeAndCity(addressLines.get(addressLines.size() - 2), tradeParty);
- // Straßenadresse aus den Zeilen zwischen Name und PLZ/Stadt/Land
- buildStreetAddress(addressLines.subList(1, addressLines.size() - 2), tradeParty);
- }
- }
+ tradeParty.setName(businessPartner.getItemValueString("partner.name"));
+ tradeParty.setCountryId(businessPartner.getItemValueString("partner.country"));
+ tradeParty.setCityName(businessPartner.getItemValueString("partner.city"));
+ tradeParty.setPostcodeCode(businessPartner.getItemValueString("partner.zip"));
+ tradeParty.setStreetAddress(businessPartner.getItemValueString("partner.address"));
return tradeParty;
}
- /**
- * Hilfemethode zum übersetzen von ISO Country codes
- *
- * @param country
- * @return
- */
- private static String resolveISOCountryCodes(String country) {
- String countryCode = country.toUpperCase().substring(0, 2);
-
- // Bulgarien -> BG
- countryCode = countryCode.replace("BU", "BG");
- return countryCode;
- }
-
- /**
- * Baut die Straßenadresse aus den gegebenen Zeilen
- */
- private static void buildStreetAddress(List streetLines, TradeParty tradeParty) {
- if (streetLines.isEmpty())
- return;
-
- StringBuilder streetAddress = new StringBuilder();
- for (int i = 0; i < streetLines.size(); i++) {
- if (i > 0) {
- streetAddress.append(", ");
- }
- streetAddress.append(streetLines.get(i).trim());
- }
- tradeParty.setStreetAddress(streetAddress.toString());
- }
-
- /**
- * Prüft ob eine Zeile PLZ und Stadt enthält.
- * Eine PLZ wird als 4-5 stellige Zahl am Anfang der Zeile erkannt.
- */
- private static boolean hasPostcodeAndCity(String line) {
- String[] parts = line.trim().split("\\s+", 2);
- if (parts.length < 2)
- return false;
-
- String potentialPostcode = parts[0];
- return potentialPostcode.matches("\\d{4,5}");
- }
-
- /**
- * Analysiert die PLZ und Stadt aus einer Zeile.
- */
- private static void parsePostcodeAndCity(String line, TradeParty tradeParty) {
- String[] parts = line.trim().split("\\s+", 2);
- if (parts.length >= 2) {
- tradeParty.setPostcodeCode(parts[0].trim());
- tradeParty.setCityName(parts[1].trim());
- } else {
- tradeParty.setCityName(line.trim());
- }
- }
-
/**
* Parses the data list of an invoice line and returns a TradeLineItem object.
* The order of the list items must be exactly!
@@ -403,80 +338,17 @@ public class EInvoiceConverterAdapter implements SignalAdapter {
return null;
}
- TradeLineItem tradeLineItem = new TradeLineItem(orderItem.getItemValueString("id"));
+ TradeLineItem tradeLineItem = new TradeLineItem(orderItem.getItemValueString("numpos"));
- tradeLineItem.setName(orderItem.getItemValueString("name"));
- tradeLineItem.setQuantity(orderItem.getItemValueDouble("quantity"));
- tradeLineItem.setNetPrice(orderItem.getItemValueDouble("price"));
- tradeLineItem.setTotal(orderItem.getItemValueDouble("total"));
- tradeLineItem.setTaxRate(orderItem.getItemValueDouble("vat"));
+ tradeLineItem.setName(orderItem.getItemValueString("datev.text"));
+ tradeLineItem.setQuantity(1);
+ tradeLineItem.setNetPrice(orderItem.getItemValueDouble("datev.umsatz"));
+ tradeLineItem.setTotal(orderItem.getItemValueDouble("datev.umsatz"));
+ // tradeLineItem.setTaxRate(orderItem.getItemValueDouble("vat"));
- // if (orderItem.hasItem("orderid")) {
- // tradeLineItem.setOrderReferenceId(orderItem.getItemValueString("orderid"));
- // }
return tradeLineItem;
}
- /**
- * Generic method to set a item value by a regex
- *
- * @param text
- * @param regex
- * @return
- */
- @SuppressWarnings("rawtypes")
- private void setItemValueByRegex(ItemCollection workitem, String itemName, String content, String regex,
- Class itemType) {
-
- String value = getValueByRegex(content, regex);
- logger.info(itemName + "=" + value);
- if (value.isEmpty()) {
- return; // no value found
- }
-
- // Parse Double?
- if (itemType == Double.class) {
- if (value.contains(",")) {
- value = value.replace(".", "");
- }
- value = value.replace(",", ".");
- workitem.setItemValue(itemName, Double.parseDouble(value));
- } else
- // Parse Integer
- if (itemType == Integer.class) {
- workitem.setItemValue(itemName, Integer.parseInt(value));
- } else
- // Parse Date
- if (itemType == Date.class) {
- try {
- workitem.setItemValue(itemName, dateFormatter.parse(value));
- } catch (ParseException e) {
- logger.warning("Date value " + value + " not parsable: " + e.getMessage());
- workitem.setItemValue(itemName, value);
- }
- } else {
- workitem.setItemValue(itemName, value);
- }
-
- }
-
- /**
- * Generic method to extract a text fragment by regex
- *
- * @param text
- * @param regex
- * @return
- */
- private String getValueByRegex(String text, String regex) {
- String result = "";
- Pattern datumPattern = Pattern.compile(regex);
- Matcher datumMatcher = datumPattern.matcher(text);
- if (datumMatcher.find()) {
- result = datumMatcher.group(1);
- }
- return result;
- }
-
/**
* This method loads a text-block for a specified ref and appends the named
* fileData object of this document.
diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/einvoice/KSeFAdapter.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/einvoice/KSeFAdapter.java
index 4e04a87..fc7e1bc 100644
--- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/einvoice/KSeFAdapter.java
+++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/einvoice/KSeFAdapter.java
@@ -1,28 +1,16 @@
package com.alexanderlogistics.einvoice;
import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
import java.text.NumberFormat;
-import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
-import java.util.Date;
-import java.util.HashMap;
import java.util.List;
import java.util.Locale;
-import java.util.Map;
import java.util.logging.Logger;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import org.apache.pdfbox.pdmodel.PDDocument;
-import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
-import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
-import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
-import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
+import javax.xml.transform.TransformerException;
+
import org.imixs.archive.core.SnapshotService;
import org.imixs.einvoice.EInvoiceFormatException;
import org.imixs.einvoice.EInvoiceModel;
@@ -39,6 +27,7 @@ import org.imixs.workflow.exceptions.PluginException;
import org.imixs.workflow.exceptions.QueryException;
import org.imixs.workflow.util.XMLParser;
+import com.alexanderlogistics.BusinessPartnerService;
import com.alexanderlogistics.InvoiceUtil;
import jakarta.inject.Inject;
@@ -89,6 +78,9 @@ public class KSeFAdapter implements SignalAdapter {
@Inject
SnapshotService snapshotService;
+ @Inject
+ BusinessPartnerService businessPartnerService;
+
/**
* This method
*
@@ -112,116 +104,20 @@ public class KSeFAdapter implements SignalAdapter {
// Load the e-invoice template....
FileData xmlFileData = loadXMLTemplate(workitem, ksefDefinition);
- FileData pdfFileData = loadPDF(workitem);
- logger.info("│ ├── Source File: " + pdfFileData.getName());
+ updateEInvoice(xmlFileData, workitem);
+
+ // append XML document
+ logger.info("│ ├── export KSeF e-invoice");
+ workitem.addFileData(xmlFileData);
- if (true)
- throw new AdapterException(KSeFAdapter.class.getSimpleName(), CONFIG_ERROR, "NOT YET IMPLEMENTED");
} catch (PluginException e) {
throw new AdapterException(e);
- // throw new AdapterException(
- // EInvoiceConverterAdapter.class.getSimpleName(), e.getErrorCode(),
- // e.getMessage());
-
}
return workitem;
}
- /**
- * This helper method tries to find the pdf file matching the item
- * 'cargosoft.import.filename'. If not found the method returns the first .pdf
- * file form the list of attachments. The method returns null if no pdf file
- * exits
- *
- * @param workitem
- * @return the pdf FileData object
- * @throws PluginException if not pdf file was found
- */
- private FileData loadPDF(ItemCollection workitem) throws PluginException {
- FileData pdfFileData = null;
- String cargosoftFileName = workitem.getItemValueString("cargosoft.import.filename");
- if (!cargosoftFileName.isEmpty()) {
- cargosoftFileName = cargosoftFileName.replace(".xml", ".pdf");
-
- // try to load filedata....
- pdfFileData = snapshotService.getWorkItemFile(workitem.getUniqueID(), cargosoftFileName);
-
- }
-
- if (pdfFileData == null) {
- // take first pdf file available
- List fileNames = workitem.getFileNames();
- for (String filename : fileNames) {
-
- if (filename.toLowerCase().endsWith(".pdf")) {
- pdfFileData = snapshotService.getWorkItemFile(workitem.getUniqueID(), filename);
- break;
- }
- }
- }
-
- if (pdfFileData == null) {
- throw new PluginException(EInvoiceConverterAdapter.class.getSimpleName(),
- DOCUMENT_ERROR,
- "DOCUMENT_ERROR: no pdf file found!");
- }
- return pdfFileData;
-
- }
-
- private FileData embeddXML(FileData pdfFileData, FileData xmlFileData) throws PluginException {
- try {
- PDDocument document = PDDocument.load(pdfFileData.getContent());
- byte[] xmlBytes = xmlFileData.getContent();
-
- // Erstelle einen InputStream aus dem XML Content
- InputStream inputStream = new ByteArrayInputStream(xmlBytes);
- // Erstelle eine Datei-Spezifikation für die XML
- PDComplexFileSpecification fileSpec = new PDComplexFileSpecification();
- fileSpec.setFile(xmlFileData.getName());
- // Erstelle einen PDEmbeddedFile mit dem InputStream
- PDEmbeddedFile embeddedFile = new PDEmbeddedFile(document, inputStream);
- embeddedFile.setSubtype("application/xml");
- embeddedFile.setSize(xmlBytes.length);
- // Schließe den InputStream
- inputStream.close();
- // Setze den PDEmbeddedFile
- fileSpec.setEmbeddedFile(embeddedFile);
-
- // Erstelle einen Map-Eintrag für die eingebettete Datei
- Map filesMap = new HashMap<>();
- filesMap.put(xmlFileData.getName(), fileSpec);
-
- // Erstelle einen Eingebettete-Dateien-Namensbaum und füge die Datei hinzu
- PDEmbeddedFilesNameTreeNode efTree = new PDEmbeddedFilesNameTreeNode();
- efTree.setNames(filesMap);
-
- // Erstelle ein Namens-Dictionary und füge den Baum hinzu
- PDDocumentNameDictionary names = new PDDocumentNameDictionary(document.getDocumentCatalog());
- names.setEmbeddedFiles(efTree);
- document.getDocumentCatalog().setNames(names);
-
- // Speichere das PDF-Dokument
-
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- document.save(baos);
- byte[] eInvoiceData = baos.toByteArray();
- document.close();
- baos.close();
-
- FileData eInvoiceFileData = new FileData(pdfFileData.getName(), eInvoiceData, pdfFileData.getContentType(),
- pdfFileData.getAttributes());
- return eInvoiceFileData;
-
- } catch (IOException e) {
- // TODO Auto-generated catch block
- throw new PluginException(EInvoiceConverterAdapter.class.getSimpleName(), CONFIG_ERROR,
- "Failed to generate e-invoice: " + e.getMessage());
- }
- }
-
/**
* THis method updates an e-invoice template with the data stored in the
* workitem.
@@ -233,7 +129,6 @@ public class KSeFAdapter implements SignalAdapter {
* @param workitem
* @throws PluginException
*/
- @SuppressWarnings("unchecked")
private void updateEInvoice(FileData fileDataXMLTemplate, ItemCollection workitem) throws PluginException {
try {
@@ -242,51 +137,46 @@ public class KSeFAdapter implements SignalAdapter {
model.setId(workitem.getItemValueString("invoice.number"));
// Update Rechnungssummen...
- if (workitem.getItemValueDouble("invoice.vat.rate") > 0) {
+ if (workitem.getItemValueDouble("invoice.total.tax") > 0) {
// wir haben eine Steuer!
- model.setTaxTotalAmount(InvoiceUtil.round(workitem.getItemValueDouble("invoice.amount.total")
- - workitem.getItemValueDouble("invoice.amount.net")));
- model.setTaxRate(workitem.getItemValueDouble("invoice.vat.rate"));
+ model.setTaxTotalAmount(InvoiceUtil.round(workitem.getItemValueDouble("invoice.total")
+ - workitem.getItemValueDouble("invoice.total.net")));
+ model.setTaxRate(workitem.getItemValueDouble("invoice.total.tax"));
}
- // // Update Invoice Items
- // List invoiceItems = explodeChildList(workitem);
- // double lineTotalAmount = 0.00;
- // for (ItemCollection invoiceItem : invoiceItems) {
- // TradeLineItem tradeLineItem = buildTradeLineItem(invoiceItem);
- // model.setTradeLineItem(tradeLineItem);
- // lineTotalAmount = lineTotalAmount + tradeLineItem.getTotal();
- // }
+ // Update Invoice Items
+ List invoiceItems = InvoiceUtil.explodeChildList(workitem, "_childitems");
+ double lineTotalAmount = 0.00;
+ for (ItemCollection invoiceItem : invoiceItems) {
+ TradeLineItem tradeLineItem = buildTradeLineItem(invoiceItem);
+ model.setTradeLineItem(tradeLineItem);
+ lineTotalAmount = lineTotalAmount + tradeLineItem.getTotal();
+ }
// // Summenbildung
- // model.setNetTotalAmount(workitem.getItemValueDouble("invoice.amount.net"));
- // model.setGrandTotalAmount(workitem.getItemValueDouble("invoice.amount.total"));
- // model.setIssueDateTime(workitem.getItemValueLocalDate("invoice.date"));
+ model.setNetTotalAmount(workitem.getItemValueDouble("invoice.total.net"));
+ model.setGrandTotalAmount(workitem.getItemValueDouble("invoice.total"));
+ model.setIssueDateTime(workitem.getItemValueLocalDate("invoice.date"));
- // // Update Addresses
- // TradeParty billingAddress =
- // parseAddress(workitem.getItemValue("dbtr.address"), "buyer");
- // model.setTradeParty(billingAddress);
+ // Update Addresses
+ TradeParty billingAddress = buildAddress(workitem.getItemValueString("partner.id"), "buyer");
+ model.setTradeParty(billingAddress);
- // TradeParty shippingAddress =
- // parseAddress(workitem.getItemValue("dbtr.address.shipping"), "ship_to");
- // model.setTradeParty(shippingAddress);
+ TradeParty shippingAddress = buildAddress(workitem.getItemValueString("partner.id"), "ship_to");
+ model.setTradeParty(shippingAddress);
+ // References>AB230321
// model.setOrderReferenceId(workitem.getItemValueString("order.number"));
- // // finally update the template file
- // fileDataXMLTemplate.setContent(model.getContent());
+ // finally update the template file
+ fileDataXMLTemplate.setContent(model.getContent());
- } catch (FileNotFoundException | EInvoiceFormatException e) {
+ } catch (FileNotFoundException | EInvoiceFormatException | TransformerException e) {
throw new PluginException(this.getClass().getName(), DOCUMENT_ERROR, e.getMessage(), e);
}
}
- /**
- * Parses a list of address lines into a TradeParty object using best-guess
- * approach
- */
/**
* Erstellt ein TradeParty Objekt aus einer Liste von Adresszeilen.
*
@@ -294,98 +184,28 @@ public class KSeFAdapter implements SignalAdapter {
* @param type Der Typ der TradeParty
* @return TradeParty
*/
- public static TradeParty parseAddress(List addressLines, String type) {
- if (addressLines == null || addressLines.isEmpty()) {
- throw new IllegalArgumentException("Address lines cannot be null or empty");
+ public TradeParty buildAddress(String partnerID, String type) throws PluginException {
+ if (partnerID == null || partnerID.isEmpty()) {
+ throw new PluginException(this.getClass().getName(), DOCUMENT_ERROR, "Missing partnerID");
}
+ ItemCollection businessPartner = businessPartnerService.getBusinessPartnerByID(partnerID);
+ if (businessPartner == null) {
+ throw new PluginException(this.getClass().getName(), DOCUMENT_ERROR,
+ "Business Partner ID '" + partnerID + "' does not exist");
+ }
TradeParty tradeParty = new TradeParty(type);
// Name ist immer die erste Zeile
- tradeParty.setName(addressLines.get(0).trim());
-
- // Letzte Zeile analysieren
- String lastLine = addressLines.get(addressLines.size() - 1).trim();
-
- // Prüfen ob die letzte Zeile ein PLZ + Stadt Format hat
- if (hasPostcodeAndCity(lastLine)) {
- tradeParty.setCountryId("DE");
- parsePostcodeAndCity(lastLine, tradeParty);
-
- // Straßenadresse aus den Zeilen zwischen Name und PLZ/Stadt
- buildStreetAddress(addressLines.subList(1, addressLines.size() - 1), tradeParty);
- } else {
- // Wenn die letzte Zeile kein PLZ + Stadt Format hat, dann ist es ein Land
- String countryCode = resolveISOCountryCodes(lastLine.toUpperCase());
- tradeParty.setCountryId(countryCode);
- // Stadt und PLZ sind in der vorletzten Zeile
- if (addressLines.size() > 2) {
- parsePostcodeAndCity(addressLines.get(addressLines.size() - 2), tradeParty);
- // Straßenadresse aus den Zeilen zwischen Name und PLZ/Stadt/Land
- buildStreetAddress(addressLines.subList(1, addressLines.size() - 2), tradeParty);
- }
- }
+ tradeParty.setName(businessPartner.getItemValueString("partner.name"));
+ tradeParty.setCountryId(businessPartner.getItemValueString("partner.country"));
+ tradeParty.setCityName(businessPartner.getItemValueString("partner.city"));
+ tradeParty.setPostcodeCode(businessPartner.getItemValueString("partner.zip"));
+ tradeParty.setStreetAddress(businessPartner.getItemValueString("partner.address"));
return tradeParty;
}
- /**
- * Hilfemethode zum übersetzen von ISO Country codes
- *
- * @param country
- * @return
- */
- private static String resolveISOCountryCodes(String country) {
- String countryCode = country.toUpperCase().substring(0, 2);
-
- // Bulgarien -> BG
- countryCode = countryCode.replace("BU", "BG");
- return countryCode;
- }
-
- /**
- * Baut die Straßenadresse aus den gegebenen Zeilen
- */
- private static void buildStreetAddress(List streetLines, TradeParty tradeParty) {
- if (streetLines.isEmpty())
- return;
-
- StringBuilder streetAddress = new StringBuilder();
- for (int i = 0; i < streetLines.size(); i++) {
- if (i > 0) {
- streetAddress.append(", ");
- }
- streetAddress.append(streetLines.get(i).trim());
- }
- tradeParty.setStreetAddress(streetAddress.toString());
- }
-
- /**
- * Prüft ob eine Zeile PLZ und Stadt enthält.
- * Eine PLZ wird als 4-5 stellige Zahl am Anfang der Zeile erkannt.
- */
- private static boolean hasPostcodeAndCity(String line) {
- String[] parts = line.trim().split("\\s+", 2);
- if (parts.length < 2)
- return false;
-
- String potentialPostcode = parts[0];
- return potentialPostcode.matches("\\d{4,5}");
- }
-
- /**
- * Analysiert die PLZ und Stadt aus einer Zeile.
- */
- private static void parsePostcodeAndCity(String line, TradeParty tradeParty) {
- String[] parts = line.trim().split("\\s+", 2);
- if (parts.length >= 2) {
- tradeParty.setPostcodeCode(parts[0].trim());
- tradeParty.setCityName(parts[1].trim());
- } else {
- tradeParty.setCityName(line.trim());
- }
- }
-
/**
* Parses the data list of an invoice line and returns a TradeLineItem object.
* The order of the list items must be exactly!
@@ -395,80 +215,17 @@ public class KSeFAdapter implements SignalAdapter {
return null;
}
- TradeLineItem tradeLineItem = new TradeLineItem(orderItem.getItemValueString("id"));
+ TradeLineItem tradeLineItem = new TradeLineItem(orderItem.getItemValueString("numpos"));
- tradeLineItem.setName(orderItem.getItemValueString("name"));
- tradeLineItem.setQuantity(orderItem.getItemValueDouble("quantity"));
- tradeLineItem.setNetPrice(orderItem.getItemValueDouble("price"));
- tradeLineItem.setTotal(orderItem.getItemValueDouble("total"));
- tradeLineItem.setTaxRate(orderItem.getItemValueDouble("vat"));
+ tradeLineItem.setName(orderItem.getItemValueString("datev.text"));
+ tradeLineItem.setQuantity(1);
+ tradeLineItem.setNetPrice(orderItem.getItemValueDouble("datev.umsatz"));
+ tradeLineItem.setTotal(orderItem.getItemValueDouble("datev.umsatz"));
+ // tradeLineItem.setTaxRate(orderItem.getItemValueDouble("vat"));
- // if (orderItem.hasItem("orderid")) {
- // tradeLineItem.setOrderReferenceId(orderItem.getItemValueString("orderid"));
- // }
return tradeLineItem;
}
- /**
- * Generic method to set a item value by a regex
- *
- * @param text
- * @param regex
- * @return
- */
- @SuppressWarnings("rawtypes")
- private void setItemValueByRegex(ItemCollection workitem, String itemName, String content, String regex,
- Class itemType) {
-
- String value = getValueByRegex(content, regex);
- logger.info(itemName + "=" + value);
- if (value.isEmpty()) {
- return; // no value found
- }
-
- // Parse Double?
- if (itemType == Double.class) {
- if (value.contains(",")) {
- value = value.replace(".", "");
- }
- value = value.replace(",", ".");
- workitem.setItemValue(itemName, Double.parseDouble(value));
- } else
- // Parse Integer
- if (itemType == Integer.class) {
- workitem.setItemValue(itemName, Integer.parseInt(value));
- } else
- // Parse Date
- if (itemType == Date.class) {
- try {
- workitem.setItemValue(itemName, dateFormatter.parse(value));
- } catch (ParseException e) {
- logger.warning("Date value " + value + " not parsable: " + e.getMessage());
- workitem.setItemValue(itemName, value);
- }
- } else {
- workitem.setItemValue(itemName, value);
- }
-
- }
-
- /**
- * Generic method to extract a text fragment by regex
- *
- * @param text
- * @param regex
- * @return
- */
- private String getValueByRegex(String text, String regex) {
- String result = "";
- Pattern datumPattern = Pattern.compile(regex);
- Matcher datumMatcher = datumPattern.matcher(text);
- if (datumMatcher.find()) {
- result = datumMatcher.group(1);
- }
- return result;
- }
-
/**
* This method loads a text-block for a specified ref and appends the named
* fileData object of this document.
@@ -488,7 +245,7 @@ public class KSeFAdapter implements SignalAdapter {
debug = Boolean.parseBoolean(config.getItemValueString("debug"));
} catch (Exception e) {
}
- String targetName = "factur-x.xml";
+ String targetName = "ksef.xml";
// adapt text....
sourceName = workflowService.adaptText(sourceName, workitem);
diff --git a/pom.xml b/pom.xml
index 198e75c..f04362c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,6 +33,7 @@
3.0.3
2.1.1-SNAPSHOT
1.0.2-SNAPSHOT
+ 1.0.3-SNAPSHOT
6.0
9.9.1-4
2.0.26
diff --git a/workflow/pl/e-invoice/examples/003_6362_251117_R.pdf b/workflow/pl/e-invoice/examples/003_6362_251117_R.pdf
new file mode 100644
index 0000000..3e032cc
Binary files /dev/null and b/workflow/pl/e-invoice/examples/003_6362_251117_R.pdf differ
diff --git a/workflow/pl/e-invoice/examples/003_6365_251117_R.pdf b/workflow/pl/e-invoice/examples/003_6365_251117_R.pdf
new file mode 100644
index 0000000..d0b6689
Binary files /dev/null and b/workflow/pl/e-invoice/examples/003_6365_251117_R.pdf differ
diff --git a/workflow/pl/e-invoice/examples/80002_EX-GCA-2405-068.pdf b/workflow/pl/e-invoice/examples/80002_EX-GCA-2405-068.pdf
new file mode 100644
index 0000000..9bfb99c
Binary files /dev/null and b/workflow/pl/e-invoice/examples/80002_EX-GCA-2405-068.pdf differ
diff --git a/workflow/pl/e-invoice/factur-x.xml b/workflow/pl/e-invoice/templates/factur-x.xml
similarity index 66%
rename from workflow/pl/e-invoice/factur-x.xml
rename to workflow/pl/e-invoice/templates/factur-x.xml
index 29f33c1..44859a4 100644
--- a/workflow/pl/e-invoice/factur-x.xml
+++ b/workflow/pl/e-invoice/templates/factur-x.xml
@@ -17,28 +17,20 @@
- Wir berechnen heute unter Zugrundelegung unserer allgemeinen
- Geschäftsbedingungen, die Ihnen bereits
- übersendet wurden oder die Sie unter www.mueller-ahlhorn.com nachlesen können, wie
- folgt:
+ Payment Instructions:
- Bitte zahlen Sie ab sofort alle Rechnungen auf unser Konto bei der
- Landessparkasse zu Oldenburg, BIC
- SLZODE22XXX, IBAN DE04 2805 0100 0092 2828 70. Etwaig anfallende Bankspesen gehen zu
- Ihren Lasten.
+ Please ensure that the payment reference includes the invoice number, BL or
+ AWB number, container or shipment number and place of loading and discharge.
+ The full invoice amount must be transferred without any deductions and all bank charges
+ must be covered by the sender.
+ Otherwise, the beneficiary’s bank will be unable to process the incoming payment.
- Dr. Dietrich Müller GmbH
+ Alexander Global Logistics
- Geschäftsführer: Dr. Michael Müller
-
-
- Amtsgericht Oldenburg HRB 209026
-
-
- info@mueller-ahlhorn.com
+ Numer NIP: PL9552521552
@@ -46,27 +38,27 @@
- Dr. Dietrich Müller GmbH
+ Alexander Global Logistics
- Müller, Michael
+
- +4944359710261
+
- info@mueller-ahlhorn.com
+
- 26197
- Zeppelinring 18
- Ahlhorn
- DE
+ 70-660
+ Gdanska 36
+ Szczecin
+ PL
- info@mueller-ahlhorn.com
+ info@alexander-logistics.com
- DE295969093
+ PL9552521552
@@ -80,10 +72,10 @@
30
- DE04 2805 0100 0092 2828 70
+ PL79116022020000000654306674
- SLZODE22XXX
+ BIGBPLPWXXX
@@ -94,9 +86,7 @@
0.00
- Wir berechnen heute unter Zugrundelegung unserer allgemeinen
- Geschäftsbedingungen, die Ihnen bereits
- übersendet wurden oder die Sie unter www.mueller-ahlhorn.com nachlesen können.
+
0.00
diff --git a/workflow/pl/e-invoice/templates/ksef.xml b/workflow/pl/e-invoice/templates/ksef.xml
new file mode 100644
index 0000000..88b49f1
--- /dev/null
+++ b/workflow/pl/e-invoice/templates/ksef.xml
@@ -0,0 +1,138 @@
+
+
+
+ FA
+ 3
+ 2026-06-05T02:45:33.422866Z
+ Generator Imixs
+
+
+
+ #nip#
+ Bogusz - Sienkiewicz
+
+
+ PL
+ al. Różycki 7594
+ 87-607 Środa Śląska
+
+
+ Lucja17@example.net
+ 86-588-03-53
+
+
+
+
+ 3861610227
+ Błaszczak - Lenart
+
+
+ PL
+ al. Szewczyk 7247
+ 84-642 Żelechów
+
+
+ Jakubina82@gmail.com
+ 75-461-31-12
+
+ KL-2936
+ 2
+ 2
+
+
+ PLN
+ #invoicing_date#
+ Lidzbark
+ FA/GRQMB-#invoice_number#/05/2025
+ 2025-07-11
+ 10652.38
+ 2450.05
+ 13102.43
+
+ 2
+ 2
+ 2
+ 2
+
+ 1
+
+
+ 1
+
+ 2
+
+ 1
+
+
+ VAT
+
+ 1
+ 66465935-48e0-b7d9-ea88-d9d7ff4c6023
+ Refined Concrete Pants
+ szt.
+ 8
+ 295.81
+ 2366.48
+ 23
+
+
+ 2
+ 16d35141-c43f-98fc-5249-27d7b2845cf7
+ Sleek Steel Pants
+ szt.
+ 10
+ 402.96
+ 4029.60
+ 23
+
+
+ 3
+ 8647a6b5-f12a-3817-869e-010e4353686a
+ Incredible Steel Salad
+ szt.
+ 8
+ 11.13
+ 89.04
+ 23
+
+
+ 4
+ 6a9b993d-33c1-eba2-f06c-7d8f22e7430b
+ Ergonomic Soft Fish
+ szt.
+ 8
+ 34.03
+ 272.24
+ 23
+
+
+ 5
+ 05e42590-9bfb-10c2-a946-fd1641f705e7
+ Intelligent Frozen Cheese
+ szt.
+ 3
+ 501.86
+ 1505.58
+ 23
+
+
+ 6
+ dbb7aed5-fedb-96d7-0afd-c3d0c23740a7
+ Ergonomic Metal Bike
+ szt.
+ 6
+ 398.24
+ 2389.44
+ 23
+
+
+ 1
+ 2025-07-17
+ 7
+
+
+
\ No newline at end of file
diff --git a/workflow/pl/rechnungsausgang-pl-1.1.0.bpmn b/workflow/pl/rechnungsausgang-pl-1.1.0.bpmn
index 3ffeffa..f6640bb 100644
--- a/workflow/pl/rechnungsausgang-pl-1.1.0.bpmn
+++ b/workflow/pl/rechnungsausgang-pl-1.1.0.bpmn
@@ -117,6 +117,11 @@ th { font-weight: bold;}
gateway_k00hrA
event_AujEAA
event_FYxkhQ
+ event_7H4Aaw
+ event_NT60FA
+ event_S9HJ1A
+ event_qIhyWA
+ gateway_utXBiw
@@ -1400,6 +1405,7 @@ Wiedervorlage wird um 7 bzw. 5 Tage erhöht
$workflowgroup - $workflowstatus]]>
sequenceFlow_4IITzA
sequenceFlow_sO35Dg
+ sequenceFlow_jpNRMg
sequenceFlow_2BSFNw
@@ -1779,7 +1785,7 @@ Wiedervorlage wird um 7 bzw. 5 Tage erhöht
-
+
false
@@ -1902,6 +1908,7 @@ Wiedervorlage wird um 7 bzw. 5 Tage erhöht
sequenceFlow_4IITzA
sequenceFlow_UOahyg
sequenceFlow_BaNbmQ
+ sequenceFlow_ATB00A
sequenceFlow_nrxqhg
sequenceFlow_6VjCnQ
@@ -1928,11 +1935,11 @@ Wiedervorlage wird um 7 bzw. 5 Tage erhöht
- sequenceFlow_6VjCnQ
+ sequenceFlow_0cMadw
-
+
@@ -2015,6 +2022,178 @@ Wiedervorlage wird um 7 bzw. 5 Tage erhöht
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+ sequenceFlow_ATB00A
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+ sequenceFlow_VI5qMw
+ sequenceFlow_jpNRMg
+
+
+ sequenceFlow_VI5qMw
+
+
+
+
+
+
+
+
+
+
+ sequenceFlow_sNNdbA
+
+
+
+
+
+ sequenceFlow_6VjCnQ
+ sequenceFlow_0cMadw
+ sequenceFlow_sNNdbA
+
+
+
+
+
+
+
@@ -2032,27 +2211,27 @@ Wiedervorlage wird um 7 bzw. 5 Tage erhöht
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -2062,7 +2241,7 @@ Wiedervorlage wird um 7 bzw. 5 Tage erhöht
-
+
@@ -2071,18 +2250,18 @@ Wiedervorlage wird um 7 bzw. 5 Tage erhöht
-
+
-
+
-
+
-
+
-
+
@@ -2095,15 +2274,15 @@ Wiedervorlage wird um 7 bzw. 5 Tage erhöht
-
+
-
+
-
+
-
+
@@ -2113,42 +2292,42 @@ Wiedervorlage wird um 7 bzw. 5 Tage erhöht
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -2206,24 +2385,24 @@ Wiedervorlage wird um 7 bzw. 5 Tage erhöht
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -2239,45 +2418,45 @@ Wiedervorlage wird um 7 bzw. 5 Tage erhöht
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -2287,61 +2466,91 @@ Wiedervorlage wird um 7 bzw. 5 Tage erhöht
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
@@ -2351,53 +2560,54 @@ Wiedervorlage wird um 7 bzw. 5 Tage erhöht
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
+
+
@@ -2427,39 +2637,39 @@ Wiedervorlage wird um 7 bzw. 5 Tage erhöht
-
-
-
+
+
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
@@ -2472,27 +2682,25 @@ Wiedervorlage wird um 7 bzw. 5 Tage erhöht
-
+
-
+
-
+
-
+
-
-
-
-
-
-
+
+
+
+
@@ -2500,24 +2708,24 @@ Wiedervorlage wird um 7 bzw. 5 Tage erhöht
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
+
@@ -2532,93 +2740,115 @@ Wiedervorlage wird um 7 bzw. 5 Tage erhöht
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+