Merge branch 'master' into xrechnung_version_information

This commit is contained in:
stm
2024-10-22 16:53:06 +02:00
committed by GitHub
25 changed files with 1065 additions and 506 deletions

View File

@@ -1,3 +1,12 @@
2.15.0
=======
2024-
- 435 use invoiceimporter as common technical basis also for zugferdimporter
- also import delivery address
- 527
- make document charges and allowances serializable
2.14.2 2.14.2
======= =======
2024-10-14 2024-10-14
@@ -7,7 +16,6 @@
- #505 crash after invoking ZUGFeRD2PullProvider - #505 crash after invoking ZUGFeRD2PullProvider
- #506 Fix POM missing dependencies - #506 Fix POM missing dependencies
2.14.1 2.14.1
======= =======
2024-10-06 2024-10-06

View File

@@ -1,8 +1,6 @@
package org.mustangproject.commandline; package org.mustangproject.commandline;
import java.io.BufferedReader; import java.io.*;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@@ -10,10 +8,35 @@ import java.nio.file.Paths;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
public class CliIT { public class CliIT {
public static File getResourceAsFile(String resourcePath) {
try {
InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(resourcePath);
if (in == null) {
return null;
}
File tempFile = File.createTempFile(String.valueOf(in.hashCode()), ".tmp");
tempFile.deleteOnExit();
try (FileOutputStream out = new FileOutputStream(tempFile)) {
// copy stream
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
return tempFile;
} catch (IOException e) {
return null;
}
}
@Test @Test
public void testCii2Ubl() throws Exception { public void testCii2Ubl() throws Exception {
Path output = Paths.get("target/ubl.xml"); Path output = Paths.get("target/ubl.xml");
@@ -43,4 +66,17 @@ public class CliIT {
return builder.toString(); return builder.toString();
} }
@Test
public void testMetric() {
StatRun sr = new StatRun();
File tempFile = getResourceAsFile("corrupt-factur-x-waytoosmall.pdf");
FileChecker fc = new FileChecker(tempFile.getAbsolutePath(), sr);
fc.checkForZUGFeRD();
System.out.print(fc.getOutputLine());
}
} }

View File

@@ -0,0 +1,2 @@
%PDF-1.4
%ェォャュ

View File

@@ -75,7 +75,7 @@
<dependency> <dependency>
<groupId>org.apache.xmlgraphics</groupId> <groupId>org.apache.xmlgraphics</groupId>
<artifactId>fop</artifactId> <artifactId>fop</artifactId>
<version>2.9</version> <version>2.10</version>
<exclusions> <exclusions>
<exclusion> <exclusion>
<groupId>xml-apis</groupId> <groupId>xml-apis</groupId>

View File

@@ -1,5 +1,7 @@
package org.mustangproject; package org.mustangproject;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java.math.BigDecimal; import java.math.BigDecimal;
/*** /***
@@ -28,6 +30,7 @@ public class Allowance extends Charge {
* @return false since its not supposed to be calculated negatively * @return false since its not supposed to be calculated negatively
*/ */
@Override @Override
@JsonIgnore
public boolean isCharge() { public boolean isCharge() {
return false; return false;
} }

View File

@@ -0,0 +1,30 @@
// Copyright (c) 2023 Jochen Stärk, see LICENSE file
package org.mustangproject;
import org.mustangproject.ZUGFeRD.TransactionCalculator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.math.BigDecimal;
public class CalculatedInvoice extends Invoice implements Serializable {
protected BigDecimal grandTotal=null;
public void calculate() {
TransactionCalculator tc=new TransactionCalculator(this);
grandTotal=tc.getGrandTotal();
}
public BigDecimal getGrandTotal() {
if (grandTotal==null) {
calculate();
}
return grandTotal;
}
public CalculatedInvoice setGrandTotal(BigDecimal grand) {
grandTotal=grand;
return this;
}
}

View File

@@ -1,5 +1,6 @@
package org.mustangproject; package org.mustangproject;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.mustangproject.ZUGFeRD.IAbsoluteValueProvider; import org.mustangproject.ZUGFeRD.IAbsoluteValueProvider;
import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge; import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge;
@@ -151,6 +152,7 @@ public class Charge implements IZUGFeRDAllowanceCharge {
* @return true since it is supposed to be calculated negatively * @return true since it is supposed to be calculated negatively
*/ */
@Override @Override
@JsonIgnore
public boolean isCharge() { public boolean isCharge() {
return true; return true;
} }

View File

@@ -26,6 +26,7 @@ import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.mustangproject.ZUGFeRD.*; import org.mustangproject.ZUGFeRD.*;
import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants; import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants;
@@ -37,6 +38,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
* @see IExportableTransaction if you want to implement an interface instead * @see IExportableTransaction if you want to implement an interface instead
*/ */
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Invoice implements IExportableTransaction { public class Invoice implements IExportableTransaction {
protected String documentName = null, documentCode = null, number = null, ownOrganisationFullPlaintextInfo = null, referenceNumber = null, shipToOrganisationID = null, shipToOrganisationName = null, shipToStreet = null, shipToZIP = null, shipToLocation = null, shipToCountry = null, buyerOrderReferencedDocumentID = null, invoiceReferencedDocumentID = null, buyerOrderReferencedDocumentIssueDateTime = null, ownForeignOrganisationID = null, ownOrganisationName = null, currency = null, paymentTermDescription = null; protected String documentName = null, documentCode = null, number = null, ownOrganisationFullPlaintextInfo = null, referenceNumber = null, shipToOrganisationID = null, shipToOrganisationName = null, shipToStreet = null, shipToZIP = null, shipToLocation = null, shipToCountry = null, buyerOrderReferencedDocumentID = null, invoiceReferencedDocumentID = null, buyerOrderReferencedDocumentIssueDateTime = null, ownForeignOrganisationID = null, ownOrganisationName = null, currency = null, paymentTermDescription = null;
@@ -520,6 +522,20 @@ public class Invoice implements IExportableTransaction {
} }
} }
/***
* this is wrong and only used from jackson
* @param iza
* @return
*/
public Invoice setZFAllowances(Allowance[] iza) {
Allowances=new ArrayList<>();
for (IZUGFeRDAllowanceCharge cz:iza) {
Allowances.add(cz);
}
return this;
}
@Override @Override
public IZUGFeRDAllowanceCharge[] getZFCharges() { public IZUGFeRDAllowanceCharge[] getZFCharges() {
@@ -530,6 +546,18 @@ public class Invoice implements IExportableTransaction {
} }
} }
/***
* this is wrong and only used from jackson
* @param iza
* @return
*/
public Invoice setZFCharges(Charge[] iza) {
Charges=new ArrayList<>();
for (IZUGFeRDAllowanceCharge cz:iza) {
Charges.add(cz);
}
return this;
}
@Override @Override
public IZUGFeRDAllowanceCharge[] getZFLogisticsServiceCharges() { public IZUGFeRDAllowanceCharge[] getZFLogisticsServiceCharges() {

View File

@@ -1,6 +1,7 @@
package org.mustangproject; package org.mustangproject;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.mustangproject.ZUGFeRD.IReferencedDocument; import org.mustangproject.ZUGFeRD.IReferencedDocument;
import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge; import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge;
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableItem; import org.mustangproject.ZUGFeRD.IZUGFeRDExportableItem;
@@ -18,6 +19,7 @@ import java.util.Date;
*/ */
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Item implements IZUGFeRDExportableItem { public class Item implements IZUGFeRDExportableItem {
protected BigDecimal price = BigDecimal.ZERO; protected BigDecimal price = BigDecimal.ZERO;
protected BigDecimal quantity; protected BigDecimal quantity;

View File

@@ -1,6 +1,7 @@
package org.mustangproject; package org.mustangproject;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.mustangproject.ZUGFeRD.IDesignatedProductClassification; import org.mustangproject.ZUGFeRD.IDesignatedProductClassification;
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableProduct; import org.mustangproject.ZUGFeRD.IZUGFeRDExportableProduct;
import org.mustangproject.util.NodeMap; import org.mustangproject.util.NodeMap;
@@ -17,6 +18,8 @@ import java.util.Map;
* describes a product, good or service used in an invoice item line * describes a product, good or service used in an invoice item line
*/ */
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Product implements IZUGFeRDExportableProduct { public class Product implements IZUGFeRDExportableProduct {
protected String unit, name, sellerAssignedID, buyerAssignedID; protected String unit, name, sellerAssignedID, buyerAssignedID;
protected String description=""; protected String description="";

View File

@@ -5,6 +5,7 @@ import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableContact; import org.mustangproject.ZUGFeRD.IZUGFeRDExportableContact;
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableTradeParty; import org.mustangproject.ZUGFeRD.IZUGFeRDExportableTradeParty;
import org.mustangproject.ZUGFeRD.IZUGFeRDLegalOrganisation; import org.mustangproject.ZUGFeRD.IZUGFeRDLegalOrganisation;
@@ -19,6 +20,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
* A organisation, i.e. usually a company * A organisation, i.e. usually a company
*/ */
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class TradeParty implements IZUGFeRDExportableTradeParty { public class TradeParty implements IZUGFeRDExportableTradeParty {
protected String name, zip, street, location, country; protected String name, zip, street, location, country;
@@ -500,6 +502,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
return this; return this;
} }
/** /**
* (optional) * (optional)
* *
@@ -511,6 +514,14 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
return this; return this;
} }
/**
* primarily for invoiceimporter and JSON
* @return the list of sepa mandates
*/
public List<DirectDebit> getDebitDetails() {
return debitDetails;
}
@Override @Override
public IZUGFeRDLegalOrganisation getLegalOrganisation() { public IZUGFeRDLegalOrganisation getLegalOrganisation() {
return legalOrg; return legalOrg;
@@ -670,6 +681,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
return contact; return contact;
} }
@JsonIgnore
public IZUGFeRDTradeSettlement[] getAsTradeSettlement() { public IZUGFeRDTradeSettlement[] getAsTradeSettlement() {
if (bankDetails.isEmpty() && debitDetails.isEmpty()) { if (bankDetails.isEmpty() && debitDetails.isEmpty()) {
return null; return null;

View File

@@ -4,9 +4,13 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.dom4j.io.XMLWriter; import org.dom4j.io.XMLWriter;
import org.mustangproject.ZUGFeRD.ZUGFeRDDateFormat;
import org.w3c.dom.Node;
public class XMLTools extends XMLWriter { public class XMLTools extends XMLWriter {
@Override @Override
@@ -39,6 +43,52 @@ public class XMLTools extends XMLWriter {
} }
/**
* returns the value of an node
*
* @param node the Node to get the value from
* @return A String or empty String, if no value was found
*/
public static String getNodeValue(Node node) {
if (node != null && node.getFirstChild() != null) {
return node.getFirstChild().getNodeValue();
}
return "";
}
/**
* tries to convert a String to BigDecimal.
*
* @param nodeValue The value as String
* @return a BigDecimal with the value provides as String or a BigDecimal with value 0.00 if an error occurs
*/
public static BigDecimal tryBigDecimal(String nodeValue) {
try {
return new BigDecimal(nodeValue);
} catch (final Exception e) {
try {
return BigDecimal.valueOf(Float.valueOf(nodeValue));
} catch (final Exception ex) {
return new BigDecimal("0.00");
}
}
}
/**
* tries to convert a Node to a BigDecimal.
*
* @param node The value as String
* @return a BigDecimal with the value provides as String or a BigDecimal with value 0.00 if an error occurs
*/
public static BigDecimal tryBigDecimal(Node node) {
final String nodeValue = XMLTools.getNodeValue(node);
if (nodeValue.isEmpty()) {
return null;
}
return XMLTools.tryBigDecimal(nodeValue);
}
/*** /***
* formats a number so that at least minDecimals are displayed but at the maximum maxDecimals are there, i.e. * formats a number so that at least minDecimals are displayed but at the maximum maxDecimals are there, i.e.
* cuts potential 0s off the end until minDecimals * cuts potential 0s off the end until minDecimals
@@ -60,6 +110,38 @@ public class XMLTools extends XMLWriter {
} }
/***
* returns a util.Date from a 102 String yyyymmdd in a node
* @param node the node
* @return a util.Date, or null, if not parseable
*/
public static Date tryDate(Node node) {
final String nodeValue = XMLTools.getNodeValue(node);
if (nodeValue.isEmpty()) {
return null;
}
return tryDate(nodeValue);
}
/***
* returns a util.Date from a 102 String yyyymmdd
* @param toParse the string
* @return a util.Date, or null, if not parseable
*/
public static Date tryDate(String toParse) {
final SimpleDateFormat formatter = ZUGFeRDDateFormat.DATE.getFormatter();
try {
return formatter.parse(toParse);
} catch (final Exception e) {
return null;
}
}
/***
* relplaces some entities like < , > and & with their escaped pendant like &lt;
* @param s the string
* @return the "safe" string
*/
public static String encodeXML(CharSequence s) { public static String encodeXML(CharSequence s) {
if (s == null) { if (s == null) {
return ""; return "";

View File

@@ -34,9 +34,13 @@ public class LineCalculator {
} }
} }
BigDecimal vatPercent = currentItem.getProduct().getVATPercent(); BigDecimal vatPercent = null;
if (vatPercent == null) if (currentItem.getProduct()!=null) {
vatPercent = currentItem.getProduct().getVATPercent();
}
if (vatPercent == null) {
vatPercent = BigDecimal.ZERO; vatPercent = BigDecimal.ZERO;
}
BigDecimal multiplicator = vatPercent.divide(BigDecimal.valueOf(100)); BigDecimal multiplicator = vatPercent.divide(BigDecimal.valueOf(100));
priceGross = currentItem.getPrice(); // see https://github.com/ZUGFeRD/mustangproject/issues/159 priceGross = currentItem.getPrice(); // see https://github.com/ZUGFeRD/mustangproject/issues/159
price = priceGross.subtract(allowance).add(charge); price = priceGross.subtract(allowance).add(charge);

View File

@@ -20,6 +20,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.*; import java.util.*;
@@ -33,6 +34,7 @@ import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory; import javax.xml.xpath.XPathFactory;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.fop.util.XMLUtil;
import org.apache.pdfbox.Loader; import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary; import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
@@ -48,57 +50,19 @@ import org.w3c.dom.Node;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
public class ZUGFeRDImporter { public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter {
private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRDImporter.class); private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRDImporter.class);
/** public ZUGFeRDImporter() {
* if metadata has been found super();
*/
protected boolean containsMeta = false;
/**
* map filenames of additional XML files to their contents
*/
private final HashMap<String, byte[]> additionalXMLs = new HashMap<>();
/**
* map filenames of all embedded files in the respective PDF
*/
private final ArrayList<FileAttachment> PDFAttachments = new ArrayList<>();
/**
* Raw XML form of the extracted data - may be directly obtained.
*/
private byte[] rawXML = null;
/**
* XMP metadata
*/
private String xmpString = null; // XMP metadata
/**
* parsed Document
*/
private Document document;
private Integer version;
protected ZUGFeRDImporter() {
//constructor for extending classes
} }
public ZUGFeRDImporter(String pdfFilename) { public ZUGFeRDImporter(String filename) {
try (InputStream bis = Files.newInputStream(Paths.get(pdfFilename), StandardOpenOption.READ)) { super(filename);
extractLowLevel(bis);
} catch (final IOException e) {
LOGGER.error("Failed to extract ZUGFeRD data", e);
throw new ZUGFeRDExportException(e);
}
} }
public ZUGFeRDImporter(InputStream stream) {
public ZUGFeRDImporter(InputStream pdfStream) { super(stream);
try {
extractLowLevel(pdfStream);
} catch (final IOException e) {
LOGGER.error("Failed to extract ZUGFeRD data", e);
throw new ZUGFeRDExportException(e);
}
} }
@@ -112,145 +76,6 @@ public class ZUGFeRDImporter {
} }
/**
* Extracts a ZUGFeRD invoice from a PDF document represented by an input stream. Errors are reported via exception handling.
*
* @param inStream a inputstream of a pdf file
*/
private void extractLowLevel(InputStream inStream) throws IOException {
BufferedInputStream pdfStream = new BufferedInputStream(inStream);
byte[] pad = new byte[4];
pdfStream.mark(0);
pdfStream.read(pad);
pdfStream.reset();
byte[] pdfSignature = {'%', 'P', 'D', 'F'};
if (Arrays.equals(pad, pdfSignature)) { // we have a pdf
try (PDDocument doc = Loader.loadPDF(IOUtils.toByteArray(pdfStream))) {
// PDDocumentInformation info = doc.getDocumentInformation();
final PDDocumentNameDictionary names = new PDDocumentNameDictionary(doc.getDocumentCatalog());
//start
if (doc.getDocumentCatalog() == null || doc.getDocumentCatalog().getMetadata() == null) {
LOGGER.info("no-xmlpart");
return;
}
final InputStream XMP = doc.getDocumentCatalog().getMetadata().exportXMPMetadata();
xmpString = convertStreamToString(XMP);
final PDEmbeddedFilesNameTreeNode etn = names.getEmbeddedFiles();
if (etn == null) {
return;
}
final Map<String, PDComplexFileSpecification> efMap = etn.getNames();
// String filePath = "/tmp/";
if (efMap != null) {
extractFiles(efMap); // see
// https://memorynotfound.com/apache-pdfbox-extract-embedded-file-pdf-document/
} else {
final List<PDNameTreeNode<PDComplexFileSpecification>> kids = etn.getKids();
if (kids == null) {
return;
}
for (final PDNameTreeNode<PDComplexFileSpecification> node : kids) {
final Map<String, PDComplexFileSpecification> namesL = node.getNames();
extractFiles(namesL);
}
}
}
} else {
// no PDF probably XML
containsMeta = true;
setRawXML(XMLTools.getBytesFromStream(pdfStream));
}
}
private void extractFiles(Map<String, PDComplexFileSpecification> names) throws IOException {
for (final String alias : names.keySet()) {
final PDComplexFileSpecification fileSpec = names.get(alias);
final String filename = fileSpec.getFilename();
/**
* filenames for invoice data (ZUGFeRD v1 and v2, Factur-X)
*/
final PDEmbeddedFile embeddedFile = fileSpec.getEmbeddedFile();
if ((filename.equals("ZUGFeRD-invoice.xml") || (filename.equals("zugferd-invoice.xml")) || filename.equals("factur-x.xml")) || filename.equals("xrechnung.xml") || filename.equals("order-x.xml") || filename.equals("cida.xml")) {
containsMeta = true;
// String embeddedFilename = filePath + filename;
// File file = new File(filePath + filename);
// System.out.println("Writing " + embeddedFilename);
// ByteArrayOutputStream fileBytes=new
// ByteArrayOutputStream();
// FileOutputStream fos = new FileOutputStream(file);
setRawXML(embeddedFile.toByteArray());
// fos.write(embeddedFile.getByteArray());
// fos.close();
}
if (filename.startsWith("additional_data")) {
additionalXMLs.put(filename, embeddedFile.toByteArray());
}
PDFAttachments.add(new FileAttachment(filename, embeddedFile.getSubtype(), "Data", embeddedFile.toByteArray()));
}
}
protected Document getDocument() {
return document;
}
private void setDocument() throws ParserConfigurationException, IOException, SAXException {
final DocumentBuilderFactory xmlFact = DocumentBuilderFactory.newInstance();
xmlFact.setNamespaceAware(true);
final DocumentBuilder builder = xmlFact.newDocumentBuilder();
final ByteArrayInputStream is = new ByteArrayInputStream(rawXML);
/// is.skip(guessBOMSize(is));
document = builder.parse(is);
}
public void setRawXML(byte[] rawXML) throws IOException {
this.containsMeta = true;
this.rawXML = rawXML;
this.version = null;
try {
setDocument();
} catch (ParserConfigurationException | SAXException e) {
LOGGER.error("Failed to parse XML", e);
throw new ZUGFeRDExportException(e);
}
}
protected String extractString(String xpathStr) {
if (!containsMeta) {
throw new ZUGFeRDExportException("No suitable data/ZUGFeRD file could be found.");
}
final String result;
try {
final Document document = getDocument();
final XPathFactory xpathFact = XPathFactory.newInstance();
final XPath xpath = xpathFact.newXPath();
result = xpath.evaluate(xpathStr, document);
} catch (final XPathExpressionException e) {
LOGGER.error("Failed to evaluate XPath", e);
throw new ZUGFeRDExportException(e);
}
return result;
}
/*** /***
* Wrapper for protected method extractString * Wrapper for protected method extractString
* @param xpathStr the xpath expression to be evaluated * @param xpathStr the xpath expression to be evaluated
@@ -261,21 +86,21 @@ public class ZUGFeRDImporter {
} }
////////////////////////////////////
/** /**
* @return the reference (purpose) the sender specified for this invoice * @return the reference (purpose) the sender specified for this invoice
*/ */
public String getForeignReference() { public String getForeignReference() {
String result = extractString("//*[local-name() = 'ApplicableHeaderTradeSettlement']/*[local-name() = 'PaymentReference']");
if (result == null || result.isEmpty()) { return importedInvoice.getNumber();
result = extractString("//*[local-name() = 'ApplicableSupplyChainTradeSettlement']/*[local-name() = 'PaymentReference']");
}
return result;
} }
/** /**
* @return the ZUGFeRD Profile * @return the ZUGFeRD Profile
*/ */
public String getZUGFeRDProfil() { public String getZUGFeRDProfil() {
String guideline = extractString("//*[local-name() = 'GuidelineSpecifiedDocumentContextParameter']//*[local-name() = 'ID']"); String guideline = extractString("//*[local-name() = 'GuidelineSpecifiedDocumentContextParameter']//*[local-name() = 'ID']");
if (guideline.contains("xrechnung")) { if (guideline.contains("xrechnung")) {
return "XRECHNUNG"; return "XRECHNUNG";
@@ -299,21 +124,6 @@ public class ZUGFeRDImporter {
} }
} }
/**
* @return the Invoice Currency Code
*/
public String getInvoiceCurrencyCode() {
try {
if (getVersion() == 1) {
return extractString("//*[local-name() = 'ApplicableSupplyChainTradeSettlement']//*[local-name() = 'InvoiceCurrencyCode']");
} else {
return extractString("//*[local-name() = 'ApplicableHeaderTradeSettlement']//*[local-name() = 'InvoiceCurrencyCode']");
}
} catch (final Exception e) {
// Exception was already logged
return "";
}
}
/** /**
* @return the IssuerAssigned ID * @return the IssuerAssigned ID
@@ -336,57 +146,6 @@ public class ZUGFeRDImporter {
return extractIssuerAssignedID("ContractReferencedDocument"); return extractIssuerAssignedID("ContractReferencedDocument");
} }
private String extractIssuerAssignedID(String propertyName) {
try {
if (getVersion() == 1) {
return extractString("//*[local-name() = '" + propertyName + "']//*[local-name() = 'ID']");
} else {
return extractString("//*[local-name() = '" + propertyName + "']//*[local-name() = 'IssuerAssignedID']");
}
} catch (final Exception e) {
// Exception was already logged
return "";
}
}
/**
* @return the BuyerTradeParty ID
*/
public String getBuyerTradePartyID() {
return extractString("//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'ID']");
}
/**
* @return the Issue Date()
*/
public String getIssueDate() {
try {
if (getVersion() == 1) {
return extractString("//*[local-name() = 'HeaderExchangedDocument']//*[local-name() = 'IssueDateTime']//*[local-name() = 'DateTimeString']");
} else {
return extractString("//*[local-name() = 'ExchangedDocument']//*[local-name() = 'IssueDateTime']//*[local-name() = 'DateTimeString']");
}
} catch (final Exception e) {
// Exception was already logged
return "";
}
}
public Date getDetailedDeliveryPeriodFrom() {
final String toParse = extractString(
"//*[local-name() = 'ApplicableHeaderTradeSettlement']" +
"//*[local-name() = 'BillingSpecifiedPeriod']" +
"//*[local-name() = 'StartDateTime']//*[local-name() = 'DateTimeString']");
return tryDate(toParse);
}
public Date getDetailedDeliveryPeriodTo() {
final String toParse = extractString(
"//*[local-name() = 'ApplicableHeaderTradeSettlement']" +
"//*[local-name() = 'BillingSpecifiedPeriod']" +
"//*[local-name() = 'EndDateTime']//*[local-name() = 'DateTimeString']");
return tryDate(toParse);
}
/** /**
* @return the TaxBasisTotalAmount * @return the TaxBasisTotalAmount
@@ -470,7 +229,16 @@ public class ZUGFeRDImporter {
* @return the BuyerTradeParty SpecifiedTaxRegistration ID * @return the BuyerTradeParty SpecifiedTaxRegistration ID
*/ */
public String getBuyertradePartySpecifiedTaxRegistrationID() { public String getBuyertradePartySpecifiedTaxRegistrationID() {
return extractString("//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'SpecifiedTaxRegistration']//*[local-name() = 'ID']"); String id = null;
if ((importedInvoice.getRecipient()!=null) && (importedInvoice.getRecipient().getLegalOrganisation()!=null)) {
// this *should* be the official result
id = importedInvoice.getRecipient().getLegalOrganisation().getSchemedID().getID();
}
// but also provide some fallback
if (id == null) {
id = getBuyerTradePartyID();
}
return id;
} }
@@ -494,14 +262,14 @@ public class ZUGFeRDImporter {
* @return the BuyerTradeParty Name * @return the BuyerTradeParty Name
*/ */
public String getBuyerTradePartyName() { public String getBuyerTradePartyName() {
return extractString("//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'Name']"); return importedInvoice.getRecipient().getName();
} }
/** /**
* @return the BuyerTradeParty Name * @return the BuyerTradeParty Name
*/ */
public String getDeliveryTradePartyName() { public String getDeliveryTradePartyName() {
return extractString("//*[local-name() = 'ShipToTradeParty']//*[local-name() = 'Name']"); return importedInvoice.getDeliveryAddress().getName();
} }
@@ -548,16 +316,7 @@ public class ZUGFeRDImporter {
* @return the Invoice ID * @return the Invoice ID
*/ */
public String getInvoiceID() { public String getInvoiceID() {
try { return importedInvoice.getNumber();
if (getVersion() == 1) {
return extractString("//*[local-name() = 'HeaderExchangedDocument']//*[local-name() = 'ID']");
} else {
return extractString("//*[local-name() = 'ExchangedDocument']//*[local-name() = 'ID']");
}
} catch (final Exception e) {
// Exception was already logged
return "";
}
} }
@@ -615,11 +374,21 @@ public class ZUGFeRDImporter {
* @return the sender's account IBAN code * @return the sender's account IBAN code
*/ */
public String getIBAN() { public String getIBAN() {
return extractString("//*[local-name() = 'PayeePartyCreditorFinancialAccount']/*[local-name() = 'IBANID']"); for (IZUGFeRDTradeSettlement settlement : importedInvoice.getTradeSettlement()) {
if (settlement instanceof IZUGFeRDTradeSettlementDebit) {
return ((IZUGFeRDTradeSettlementDebit) settlement).getIBAN();
}
if (settlement instanceof IZUGFeRDTradeSettlementPayment) {
return ((IZUGFeRDTradeSettlementPayment) settlement).getOwnIBAN();
}
}
return null;
} }
public String getHolder() { public String getHolder() {
return extractString("//*[local-name() = 'SellerTradeParty']/*[local-name() = 'Name']"); return extractString("//*[local-name() = 'SellerTradeParty']/*[local-name() = 'Name']");
} }
@@ -628,14 +397,8 @@ public class ZUGFeRDImporter {
* @return the total payable amount * @return the total payable amount
*/ */
public String getAmount() { public String getAmount() {
String result = extractString("//*[local-name() = 'SpecifiedTradeSettlementHeaderMonetarySummation']/*[local-name() = 'DuePayableAmount']");
if (result == null || result.isEmpty()) {
/* fx/zf would be SpecifiedTradeSettlementMonetarySummation return importedInvoice.getGrandTotal().toPlainString();
* but ox is SpecifiedTradeSettlementHeaderMonetarySummation...*/
result = extractString("//*[local-name() = 'GrandTotalAmount']");
}
return result;
} }
@@ -643,7 +406,60 @@ public class ZUGFeRDImporter {
* @return when the payment is due * @return when the payment is due
*/ */
public String getDueDate() { public String getDueDate() {
return extractString("//*[local-name() = 'SpecifiedTradePaymentTerms']/*[local-name() = 'DueDateDateTime']/*[local-name() = 'DateTimeString']"); SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
return sdf.format(importedInvoice.getDueDate());
}
////////////////////
/**
* @return the Invoice Currency Code
*/
public String getInvoiceCurrencyCode() {
return importedInvoice.getCurrency();
}
private String extractIssuerAssignedID(String propertyName) {
try {
if (getVersion() == 1) {
return extractString("//*[local-name() = '" + propertyName + "']//*[local-name() = 'ID']");
} else {
return extractString("//*[local-name() = '" + propertyName + "']//*[local-name() = 'IssuerAssignedID']");
}
} catch (final Exception e) {
// Exception was already logged
return "";
}
}
/**
* @return the BuyerTradeParty ID
*/
public String getBuyerTradePartyID() {
String id = importedInvoice.getRecipient().getID();
if (id == null) {
// provide some fallback
id = importedInvoice.getRecipient().getVATID();
}
return id;
}
/**
* @return the Issue Date()
*/
public String getIssueDate() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
return sdf.format(importedInvoice.getIssueDate());
}
public Date getDetailedDeliveryPeriodFrom() {
return importedInvoice.getDetailedDeliveryPeriodFrom();
}
public Date getDetailedDeliveryPeriodTo() {
return importedInvoice.getDetailedDeliveryPeriodTo();
} }
@@ -691,28 +507,6 @@ public class ZUGFeRDImporter {
} }
public EStandard getStandard() throws Exception {
if (!containsMeta) {
throw new Exception("Not yet parsed");
}
final String head = getUTF8();
String rootNode = extractString("local-name(/*)");
if (rootNode.equals("CrossIndustryDocument")) {
return EStandard.zugferd;
} else if (rootNode.equals("Invoice")) {
return EStandard.ubl;
} else if (rootNode.equals("CrossIndustryInvoice")) {
return EStandard.facturx;
} else if (rootNode.equals("SCRDMCCBDACIDAMessageStructure")) {
return EStandard.despatchadvice;
} else if (head.contains("<rsm:SCRDMCCBDACIOMessageStructure")) {
return EStandard.orderx;
}
throw new Exception("ZUGFeRD version could not be determined");
}
public int getVersion() throws Exception { public int getVersion() throws Exception {
if (!containsMeta) { if (!containsMeta) {
throw new Exception("Not yet parsed"); throw new Exception("Not yet parsed");
@@ -736,35 +530,6 @@ public class ZUGFeRDImporter {
} }
/**
* @return return UTF8 XML (without BOM) of the invoice
*/
public String getUTF8() {
if (rawXML == null) {
return null;
}
if (rawXML.length < 3) {
return new String(rawXML);
}
final byte[] bomlessData;
if ((rawXML[0] == (byte) 0xEF)
&& (rawXML[1] == (byte) 0xBB)
&& (rawXML[2] == (byte) 0xBF)) {
// I don't like BOMs, lets remove it
bomlessData = new byte[rawXML.length - 3];
System.arraycopy(rawXML, 3, bomlessData, 0,
rawXML.length - 3);
} else {
bomlessData = rawXML;
}
return new String(bomlessData);
}
/** /**
* Returns the raw XML data as extracted from the ZUGFeRD PDF file. * Returns the raw XML data as extracted from the ZUGFeRD PDF file.
* *
@@ -790,14 +555,6 @@ public class ZUGFeRDImporter {
} }
static String convertStreamToString(java.io.InputStream is) {
try {
return IOUtils.toString(is, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
/** /**
* returns an instance of PostalTradeAddress for SellerTradeParty section * returns an instance of PostalTradeAddress for SellerTradeParty section
* *
@@ -809,7 +566,7 @@ public class ZUGFeRDImporter {
try { try {
if (getVersion() == 1) { if (getVersion() == 1) {
nl = getNodeListByPath("//*[local-name() = 'CrossIndustryDocument']//*[local-name() = 'SpecifiedSupplyChainTradeTransaction']/*[local-name() = 'ApplicableSupplyChainTradeAgreement']//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'PostalTradeAddress']"); nl = getNodeListByPath("//*[localname() = 'CrossIndustryDocument']//*[local-name() = 'SpecifiedSupplyChainTradeTransaction']/*[local-name() = 'ApplicableSupplyChainTradeAgreement']//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'PostalTradeAddress']");
} else { } else {
nl = getNodeListByPath("//*[local-name() = 'CrossIndustryInvoice']//*[local-name() = 'SupplyChainTradeTransaction']//*[local-name() = 'ApplicableHeaderTradeAgreement']//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'PostalTradeAddress']"); nl = getNodeListByPath("//*[local-name() = 'CrossIndustryInvoice']//*[local-name() = 'SupplyChainTradeTransaction']//*[local-name() = 'ApplicableHeaderTradeAgreement']//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'PostalTradeAddress']");
} }
@@ -953,7 +710,7 @@ public class ZUGFeRDImporter {
if (node != null) { if (node != null) {
final NodeList tradeAgreementChildren = node.getChildNodes(); final NodeList tradeAgreementChildren = node.getChildNodes();
node = getNodeByName(tradeAgreementChildren, "ChargeAmount"); node = getNodeByName(tradeAgreementChildren, "ChargeAmount");
lineItem.setPrice(tryBigDecimal(getNodeValue(node))); lineItem.setPrice(XMLTools.tryBigDecimal(node));
node = getNodeByName(tradeAgreementChildren, "BasisQuantity"); node = getNodeByName(tradeAgreementChildren, "BasisQuantity");
if (node != null && node.getAttributes() != null) { if (node != null && node.getAttributes() != null) {
final Node unitCodeAttribute = node.getAttributes().getNamedItem("unitCode"); final Node unitCodeAttribute = node.getAttributes().getNamedItem("unitCode");
@@ -966,48 +723,48 @@ public class ZUGFeRDImporter {
node = getNodeByName(nn.getChildNodes(), "GrossPriceProductTradePrice"); node = getNodeByName(nn.getChildNodes(), "GrossPriceProductTradePrice");
if (node != null) { if (node != null) {
node = getNodeByName(node.getChildNodes(), "ChargeAmount"); node = getNodeByName(node.getChildNodes(), "ChargeAmount");
lineItem.setGrossPrice(tryBigDecimal(getNodeValue(node))); lineItem.setGrossPrice(XMLTools.tryBigDecimal(node));
} }
break; break;
case "AssociatedDocumentLineDocument": case "AssociatedDocumentLineDocument":
node = getNodeByName(nn.getChildNodes(), "LineID"); node = getNodeByName(nn.getChildNodes(), "LineID");
lineItem.setId(getNodeValue(node)); lineItem.setId(XMLTools.getNodeValue(node));
break; break;
case "SpecifiedTradeProduct": case "SpecifiedTradeProduct":
node = getNodeByName(nn.getChildNodes(), "SellerAssignedID"); node = getNodeByName(nn.getChildNodes(), "SellerAssignedID");
lineItem.getProduct().setSellerAssignedID(getNodeValue(node)); lineItem.getProduct().setSellerAssignedID(XMLTools.getNodeValue(node));
node = getNodeByName(nn.getChildNodes(), "BuyerAssignedID"); node = getNodeByName(nn.getChildNodes(), "BuyerAssignedID");
lineItem.getProduct().setBuyerAssignedID(getNodeValue(node)); lineItem.getProduct().setBuyerAssignedID(XMLTools.getNodeValue(node));
node = getNodeByName(nn.getChildNodes(), "Name"); node = getNodeByName(nn.getChildNodes(), "Name");
lineItem.getProduct().setName(getNodeValue(node)); lineItem.getProduct().setName(XMLTools.getNodeValue(node));
node = getNodeByName(nn.getChildNodes(), "Description"); node = getNodeByName(nn.getChildNodes(), "Description");
lineItem.getProduct().setDescription(getNodeValue(node)); lineItem.getProduct().setDescription(XMLTools.getNodeValue(node));
break; break;
case "SpecifiedLineTradeDelivery": case "SpecifiedLineTradeDelivery":
case "SpecifiedSupplyChainTradeDelivery": case "SpecifiedSupplyChainTradeDelivery":
node = getNodeByName(nn.getChildNodes(), "BilledQuantity"); node = getNodeByName(nn.getChildNodes(), "BilledQuantity");
lineItem.setQuantity(tryBigDecimal(getNodeValue(node))); lineItem.setQuantity(XMLTools.tryBigDecimal(node));
break; break;
case "SpecifiedLineTradeSettlement": case "SpecifiedLineTradeSettlement":
node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax"); node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax");
if (node != null) { if (node != null) {
node = getNodeByName(node.getChildNodes(), "RateApplicablePercent"); node = getNodeByName(node.getChildNodes(), "RateApplicablePercent");
lineItem.getProduct().setVATPercent(tryBigDecimal(getNodeValue(node))); lineItem.getProduct().setVATPercent(XMLTools.tryBigDecimal(node));
} }
node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax"); node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax");
if (node != null) { if (node != null) {
node = getNodeByName(node.getChildNodes(), "CalculatedAmount"); node = getNodeByName(node.getChildNodes(), "CalculatedAmount");
lineItem.setTax(tryBigDecimal(getNodeValue(node))); lineItem.setTax(XMLTools.tryBigDecimal(node));
} }
node = getNodeByName(nn.getChildNodes(), "BillingSpecifiedPeriod"); node = getNodeByName(nn.getChildNodes(), "BillingSpecifiedPeriod");
if (node != null) { if (node != null) {
@@ -1021,13 +778,13 @@ public class ZUGFeRDImporter {
if (end != null) { if (end != null) {
dateTimeEnd = getNodeByName(end.getChildNodes(), "DateTimeString"); dateTimeEnd = getNodeByName(end.getChildNodes(), "DateTimeString");
} }
lineItem.setDetailedDeliveryPeriod(tryDate(dateTimeStart), tryDate(dateTimeEnd)); lineItem.setDetailedDeliveryPeriod(XMLTools.tryDate(dateTimeStart), XMLTools.tryDate(dateTimeEnd));
} }
node = getNodeByName(nn.getChildNodes(), "SpecifiedTradeSettlementLineMonetarySummation"); node = getNodeByName(nn.getChildNodes(), "SpecifiedTradeSettlementLineMonetarySummation");
if (node != null) { if (node != null) {
node = getNodeByName(node.getChildNodes(), "LineTotalAmount"); node = getNodeByName(node.getChildNodes(), "LineTotalAmount");
lineItem.setLineTotalAmount(tryBigDecimal(getNodeValue(node))); lineItem.setLineTotalAmount(XMLTools.tryBigDecimal(node));
} }
break; break;
case "SpecifiedSupplyChainTradeSettlement": case "SpecifiedSupplyChainTradeSettlement":
@@ -1036,19 +793,19 @@ public class ZUGFeRDImporter {
node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax"); node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax");
if (node != null) { if (node != null) {
node = getNodeByName(node.getChildNodes(), "ApplicablePercent"); node = getNodeByName(node.getChildNodes(), "ApplicablePercent");
lineItem.getProduct().setVATPercent(tryBigDecimal(getNodeValue(node))); lineItem.getProduct().setVATPercent(XMLTools.tryBigDecimal(node));
} }
node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax"); node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax");
if (node != null) { if (node != null) {
node = getNodeByName(node.getChildNodes(), "CalculatedAmount"); node = getNodeByName(node.getChildNodes(), "CalculatedAmount");
lineItem.setTax(tryBigDecimal(getNodeValue(node))); lineItem.setTax(XMLTools.tryBigDecimal(node));
} }
node = getNodeByName(nn.getChildNodes(), "SpecifiedTradeSettlementMonetarySummation"); node = getNodeByName(nn.getChildNodes(), "SpecifiedTradeSettlementMonetarySummation");
if (node != null) { if (node != null) {
node = getNodeByName(node.getChildNodes(), "LineTotalAmount"); node = getNodeByName(node.getChildNodes(), "LineTotalAmount");
lineItem.setLineTotalAmount(tryBigDecimal(getNodeValue(node))); lineItem.setLineTotalAmount(XMLTools.tryBigDecimal(node));
} }
break; break;
} }
@@ -1123,51 +880,4 @@ public class ZUGFeRDImporter {
} }
} }
/**
* returns the value of an node
*
* @param node the Node to get the value from
* @return A String or empty String, if no value was found
*/
private String getNodeValue(Node node) {
if (node != null && node.getFirstChild() != null) {
return node.getFirstChild().getNodeValue();
}
return "";
}
/**
* tries to convert an String to BigDecimal.
*
* @param nodeValue The value as String
* @return a BigDecimal with the value provides as String or a BigDecimal with value 0.00 if an error occurs
*/
private BigDecimal tryBigDecimal(String nodeValue) {
try {
return new BigDecimal(nodeValue);
} catch (final Exception e) {
try {
return BigDecimal.valueOf(Float.valueOf(nodeValue));
} catch (final Exception ex) {
return new BigDecimal("0.00");
}
}
}
private Date tryDate(Node node) {
final String nodeValue = getNodeValue(node);
if (nodeValue.isEmpty()) {
return null;
}
return tryDate(nodeValue);
}
private static Date tryDate(String toParse) {
final SimpleDateFormat formatter = ZUGFeRDDateFormat.DATE.getFormatter();
try {
return formatter.parse(toParse);
} catch (final Exception e) {
return null;
}
}
} }

View File

@@ -1,9 +1,11 @@
package org.mustangproject.ZUGFeRD; package org.mustangproject.ZUGFeRD;
import java.io.IOException; import java.io.*;
import java.io.InputStream;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
@@ -11,43 +13,276 @@ import java.util.Base64;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath; import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory; import javax.xml.xpath.XPathFactory;
import org.apache.commons.io.IOUtils;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
import org.mustangproject.*; import org.mustangproject.*;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter { public class ZUGFeRDInvoiceImporter {
private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRDInvoiceImporter.class.getCanonicalName()); // log private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRDInvoiceImporter.class.getCanonicalName()); // log
private boolean recalcPrice = false;
private boolean ignoreCalculationErrors = false; /**
private ArrayList<FileAttachment> fileAttachments=new ArrayList<>(); * if metadata has been found
*/
protected boolean containsMeta = false;
/**
* map filenames of additional XML files to their contents
*/
protected final HashMap<String, byte[]> additionalXMLs = new HashMap<>();
/**
* map filenames of all embedded files in the respective PDF
*/
protected final ArrayList<FileAttachment> PDFAttachments = new ArrayList<>();
/**
* Raw XML form of the extracted data - may be directly obtained.
*/
protected byte[] rawXML = null;
/**
* XMP metadata
*/
protected String xmpString = null; // XMP metadata
/**
* parsed Document
*/
protected Document document;
/***
* automatically parse into importedInvoice
*/
protected boolean parseAutomatically = true;
protected Integer version;
protected CalculatedInvoice importedInvoice = null;
protected boolean recalcPrice = false;
protected boolean ignoreCalculationErrors = false;
protected ArrayList<FileAttachment> fileAttachments = new ArrayList<>();
public ZUGFeRDInvoiceImporter() { public ZUGFeRDInvoiceImporter() {
super(); //constructor for extending classes
} }
public ZUGFeRDInvoiceImporter(String filename) { public ZUGFeRDInvoiceImporter(String pdfFilename) {
super(filename); setPDFFilename(pdfFilename);
} }
public ZUGFeRDInvoiceImporter(InputStream stream) { public ZUGFeRDInvoiceImporter(InputStream pdfStream) {
super(stream); setInputStream(pdfStream);
} }
public void fromXML(String XML) { public void setPDFFilename(String pdfFilename) {
try (InputStream bis = Files.newInputStream(Paths.get(pdfFilename), StandardOpenOption.READ)) {
extractLowLevel(bis);
} catch (final IOException e) {
LOGGER.error("Failed to extract ZUGFeRD data", e);
throw new ZUGFeRDExportException(e);
}
}
public void setInputStream(InputStream pdfStream) {
try { try {
extractLowLevel(pdfStream);
} catch (final IOException e) {
LOGGER.error("Failed to extract ZUGFeRD data", e);
throw new ZUGFeRDExportException(e);
}
}
/***
* return the file names of all files embedded into the PDF
* @see for XML embedded files please use ZUGFeRDInvoiceImporter.getFileAttachmentsXML
* @return a ArrayList of FileAttachments, empty if none
*/
public List<FileAttachment> getFileAttachmentsPDF() {
return PDFAttachments;
}
/**
* Extracts a ZUGFeRD invoice from a PDF document represented by an input stream. Errors are reported via exception handling.
*
* @param inStream a inputstream of a pdf file
*/
private void extractLowLevel(InputStream inStream) throws IOException {
BufferedInputStream pdfStream = new BufferedInputStream(inStream);
byte[] pad = new byte[4];
pdfStream.mark(0);
pdfStream.read(pad);
pdfStream.reset();
byte[] pdfSignature = {'%', 'P', 'D', 'F'};
if (Arrays.equals(pad, pdfSignature)) { // we have a pdf
try {
PDDocument doc = Loader.loadPDF(IOUtils.toByteArray(pdfStream));
// PDDocumentInformation info = doc.getDocumentInformation();
final PDDocumentNameDictionary names = new PDDocumentNameDictionary(doc.getDocumentCatalog());
//start
if (doc.getDocumentCatalog() == null || doc.getDocumentCatalog().getMetadata() == null) {
LOGGER.info("no-xmlpart");
return;
}
final InputStream XMP = doc.getDocumentCatalog().getMetadata().exportXMPMetadata();
xmpString = convertStreamToString(XMP);
final PDEmbeddedFilesNameTreeNode etn = names.getEmbeddedFiles();
if (etn == null) {
return;
}
final Map<String, PDComplexFileSpecification> efMap = etn.getNames();
// String filePath = "/tmp/";
if (efMap != null) {
extractFiles(efMap); // see
// https://memorynotfound.com/apache-pdfbox-extract-embedded-file-pdf-document/
} else {
final List<PDNameTreeNode<PDComplexFileSpecification>> kids = etn.getKids();
if (kids == null) {
return;
}
for (final PDNameTreeNode<PDComplexFileSpecification> node : kids) {
final Map<String, PDComplexFileSpecification> namesL = node.getNames();
extractFiles(namesL);
}
}
} catch (Exception e) {
LOGGER.error("Failed to parse PDF", e);
//ignore otherwise
}
} else {
// no PDF probably XML
containsMeta = true; containsMeta = true;
setRawXML(XML.getBytes(StandardCharsets.UTF_8)); setRawXML(XMLTools.getBytesFromStream(pdfStream));
} catch (IOException e) {
LOGGER.error(e.getMessage(), e); }
}
/***
* have the item prices be determined from the line total.
* That's a workaround for some invoices which just put 0 as item price
*/
public void doRecalculateItemPricesFromLineTotals() {
recalcPrice = true;
}
/***
* do not raise ParseExceptions even if the reproduced invoice total does not match the given value
*/
public void doIgnoreCalculationErrors() {
ignoreCalculationErrors = true;
}
/***
* sets th pdf attachments, and if a file is recognized (e.g. a factur-x.xml) triggers processing
* @param names the Hashmap of String, PDComplexFileSpecification
* @throws IOException
*/
private void extractFiles(Map<String, PDComplexFileSpecification> names) throws IOException {
for (final String alias : names.keySet()) {
final PDComplexFileSpecification fileSpec = names.get(alias);
final String filename = fileSpec.getFilename();
/**
* filenames for invoice data (ZUGFeRD v1 and v2, Factur-X)
*/
final PDEmbeddedFile embeddedFile = fileSpec.getEmbeddedFile();
if ((filename.equals("ZUGFeRD-invoice.xml") || (filename.equals("zugferd-invoice.xml")) || filename.equals("factur-x.xml")) || filename.equals("xrechnung.xml") || filename.equals("order-x.xml") || filename.equals("cida.xml")) {
containsMeta = true;
// String embeddedFilename = filePath + filename;
// File file = new File(filePath + filename);
// System.out.println("Writing " + embeddedFilename);
// ByteArrayOutputStream fileBytes=new
// ByteArrayOutputStream();
// FileOutputStream fos = new FileOutputStream(file);
setRawXML(embeddedFile.toByteArray());
// fos.write(embeddedFile.getByteArray());
// fos.close();
}
if (filename.startsWith("additional_data")) {
additionalXMLs.put(filename, embeddedFile.toByteArray());
}
PDFAttachments.add(new FileAttachment(filename, embeddedFile.getSubtype(), "Data", embeddedFile.toByteArray()));
}
}
/***
* set the xml of a CII invoice
* @param rawXML the xml string
* @param doParse automatically parse input for zugferdImporter (not ZUGFeRDInvoiceImporter)
* @throws IOException
*/
public void setRawXML(byte[] rawXML, boolean doParse) throws IOException {
this.containsMeta = true;
this.rawXML = rawXML;
this.version = null;
parseAutomatically = doParse;
try {
setDocument();
} catch (ParserConfigurationException | SAXException e) {
LOGGER.error("Failed to parse XML", e);
throw new ZUGFeRDExportException(e);
}
}
/***
* set the xml of a CII invoice, simple version
* @param rawXML the cii(?) as a string
* @throws IOException
*/
public void setRawXML(byte[] rawXML) throws IOException {
setRawXML(rawXML, true);
}
private void setDocument() throws ParserConfigurationException, IOException, SAXException {
final DocumentBuilderFactory xmlFact = DocumentBuilderFactory.newInstance();
xmlFact.setNamespaceAware(true);
final DocumentBuilder builder = xmlFact.newDocumentBuilder();
final ByteArrayInputStream is = new ByteArrayInputStream(rawXML);
/// is.skip(guessBOMSize(is));
document = builder.parse(is);
if (parseAutomatically) {
try {
importedInvoice = new CalculatedInvoice();
extractInto(importedInvoice);
} catch (XPathExpressionException e) {
throw new RuntimeException(e);
} catch (ParseException e) {
throw new RuntimeException(e);
}
} }
} }
@@ -63,6 +298,8 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
String number = ""; String number = "";
String typeCode = null; String typeCode = null;
String deliveryPeriodStart = null;
String deliveryPeriodEnd = null;
/* /*
* dummywerte sind derzeit noch setDueDate setIssueDate setDeliveryDate * dummywerte sind derzeit noch setDueDate setIssueDate setDeliveryDate
* setSender setRecipient setnumber bspw. due date * setSender setRecipient setnumber bspw. due date
@@ -72,6 +309,12 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
XPath xpath = xpathFact.newXPath(); XPath xpath = xpathFact.newXPath();
XPathExpression xpr = xpath.compile("//*[local-name()=\"SellerTradeParty\"]|//*[local-name()=\"AccountingSupplierParty\"]/*"); XPathExpression xpr = xpath.compile("//*[local-name()=\"SellerTradeParty\"]|//*[local-name()=\"AccountingSupplierParty\"]/*");
NodeList SellerNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); NodeList SellerNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
XPathExpression shipEx = xpath.compile("//*[local-name()=\"ShipToTradeParty\"]");
NodeList deliveryNodes = (NodeList) shipEx.evaluate(getDocument(), XPathConstants.NODESET);
if (deliveryNodes != null) {
zpp.setDeliveryAddress(new TradeParty(deliveryNodes));
}
xpr = xpath.compile("//*[local-name()=\"BuyerTradeParty\"]|//*[local-name()=\"AccountingCustomerParty\"]/*"); xpr = xpath.compile("//*[local-name()=\"BuyerTradeParty\"]|//*[local-name()=\"AccountingCustomerParty\"]/*");
NodeList BuyerNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); NodeList BuyerNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
@@ -87,6 +330,12 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
NodeList totalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); NodeList totalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
if (totalNodes.getLength() > 0) { if (totalNodes.getLength() > 0) {
expectedGrandTotal = new BigDecimal(totalNodes.item(0).getTextContent()); expectedGrandTotal = new BigDecimal(totalNodes.item(0).getTextContent());
if (zpp instanceof CalculatedInvoice) {
// usually we would re-calculate the invoice to get expectedGrandTotal
// however, for "minimal" invoices or other invoices without lines
// this will not work
((CalculatedInvoice) zpp).setGrandTotal(expectedGrandTotal);
}
} }
xpr = xpath.compile("//*[local-name()=\"PrepaidAmount\"]"); xpr = xpath.compile("//*[local-name()=\"PrepaidAmount\"]");
@@ -209,14 +458,19 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
} }
String currency = extractString("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]/*[local-name()=\"InvoiceCurrencyCode\"]|*[local-name()=\"DocumentCurrencyCode\"]");
zpp.setCurrency(currency);
xpr = xpath.compile("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]|//*[local-name()=\"ApplicableSupplyChainTradeSettlement\"]"); xpr = xpath.compile("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]|//*[local-name()=\"ApplicableSupplyChainTradeSettlement\"]");
NodeList headerTradeSettlementNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); NodeList headerTradeSettlementNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
List<BankDetails> bankDetails = new ArrayList<>(); List<BankDetails> bankDetails = new ArrayList<>();
String directDebitMandateID = null;
String IBAN = null, BIC = null;
for (int i = 0; i < headerTradeSettlementNodes.getLength(); i++) { for (int i = 0; i < headerTradeSettlementNodes.getLength(); i++) {
// nodes.item(i).getTextContent())) { // nodes.item(i).getTextContent())) {
Node headerTradeSettlementNode = headerTradeSettlementNodes.item(i); Node headerTradeSettlementNode = headerTradeSettlementNodes.item(i);
NodeList headerTradeSettlementChilds = headerTradeSettlementNode.getChildNodes(); NodeList headerTradeSettlementChilds = headerTradeSettlementNode.getChildNodes();
for (int settlementChildIndex = 0; settlementChildIndex < headerTradeSettlementChilds.getLength(); settlementChildIndex++) { for (int settlementChildIndex = 0; settlementChildIndex < headerTradeSettlementChilds.getLength(); settlementChildIndex++) {
if ((headerTradeSettlementChilds.item(settlementChildIndex).getLocalName() != null) if ((headerTradeSettlementChilds.item(settlementChildIndex).getLocalName() != null)
@@ -231,15 +485,20 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
} }
} }
} }
if ((paymentTermChilds.item(paymentTermChildIndex).getLocalName() != null) && (paymentTermChilds.item(paymentTermChildIndex).getLocalName().equals("DirectDebitMandateID"))) {
directDebitMandateID = paymentTermChilds.item(paymentTermChildIndex).getTextContent();
}
} }
} }
if ((headerTradeSettlementChilds.item(settlementChildIndex).getLocalName() != null) if ((headerTradeSettlementChilds.item(settlementChildIndex).getLocalName() != null)
&& (headerTradeSettlementChilds.item(settlementChildIndex).getLocalName().equals("SpecifiedTradeSettlementPaymentMeans"))) { && (headerTradeSettlementChilds.item(settlementChildIndex).getLocalName().equals("SpecifiedTradeSettlementPaymentMeans"))) {
NodeList paymentMeansChilds = headerTradeSettlementChilds.item(settlementChildIndex).getChildNodes(); NodeList paymentMeansChilds = headerTradeSettlementChilds.item(settlementChildIndex).getChildNodes();
String IBAN = null, BIC = null; IBAN = null;
BIC = null;
for (int paymentMeansChildIndex = 0; paymentMeansChildIndex < paymentMeansChilds.getLength(); paymentMeansChildIndex++) { for (int paymentMeansChildIndex = 0; paymentMeansChildIndex < paymentMeansChilds.getLength(); paymentMeansChildIndex++) {
if ((paymentMeansChilds.item(paymentMeansChildIndex).getLocalName() != null) && (paymentMeansChilds.item(paymentMeansChildIndex).getLocalName().equals("PayeePartyCreditorFinancialAccount"))) {
if ((paymentMeansChilds.item(paymentMeansChildIndex).getLocalName() != null) && (paymentMeansChilds.item(paymentMeansChildIndex).getLocalName().equals("PayeePartyCreditorFinancialAccount") || paymentMeansChilds.item(paymentMeansChildIndex).getLocalName().equals("PayerPartyDebtorFinancialAccount"))) {
NodeList accountChilds = paymentMeansChilds.item(paymentMeansChildIndex).getChildNodes(); NodeList accountChilds = paymentMeansChilds.item(paymentMeansChildIndex).getChildNodes();
for (int accountChildIndex = 0; accountChildIndex < accountChilds.getLength(); accountChildIndex++) { for (int accountChildIndex = 0; accountChildIndex < accountChilds.getLength(); accountChildIndex++) {
if ((accountChilds.item(accountChildIndex).getLocalName() != null) && (accountChilds.item(accountChildIndex).getLocalName().equals("IBANID"))) {//CII if ((accountChilds.item(accountChildIndex).getLocalName() != null) && (accountChilds.item(accountChildIndex).getLocalName().equals("IBANID"))) {//CII
@@ -265,9 +524,37 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
bankDetails.add(bd); bankDetails.add(bd);
} }
} }
if ((headerTradeSettlementChilds.item(settlementChildIndex).getLocalName() != null)
&& (headerTradeSettlementChilds.item(settlementChildIndex).getLocalName().equals("BillingSpecifiedPeriod"))) {
NodeList periodChilds = headerTradeSettlementChilds.item(settlementChildIndex).getChildNodes();
for (int periodChildIndex = 0; periodChildIndex < periodChilds.getLength(); periodChildIndex++) {
if ((periodChilds.item(periodChildIndex).getLocalName() != null) && (periodChilds.item(periodChildIndex).getLocalName().equals("StartDateTime"))) {
NodeList startPeriodChilds = periodChilds.item(periodChildIndex).getChildNodes();
for (int startPeriodIndex = 0; startPeriodIndex < startPeriodChilds.getLength(); startPeriodIndex++) {
if ((startPeriodChilds.item(startPeriodIndex).getLocalName() != null) && (startPeriodChilds.item(startPeriodIndex).getLocalName().equals("DateTimeString"))) {//CII
deliveryPeriodStart = startPeriodChilds.item(startPeriodIndex).getTextContent();
}
}
}
if ((periodChilds.item(periodChildIndex).getLocalName() != null) && (periodChilds.item(periodChildIndex).getLocalName().equals("EndDateTime"))) {
NodeList endPeriodChilds = periodChilds.item(periodChildIndex).getChildNodes();
for (int endPeriodIndex = 0; endPeriodIndex < endPeriodChilds.getLength(); endPeriodIndex++) {
if ((endPeriodChilds.item(endPeriodIndex).getLocalName() != null) && (endPeriodChilds.item(endPeriodIndex).getLocalName().equals("DateTimeString"))) {//CII
deliveryPeriodEnd = endPeriodChilds.item(endPeriodIndex).getTextContent();
}
}
}
}
}
} }
} }
if ((deliveryPeriodStart != null) && (deliveryPeriodEnd != null)) {
zpp.setDetailedDeliveryPeriod(XMLTools.tryDate(deliveryPeriodStart), XMLTools.tryDate(deliveryPeriodEnd));
} else if (deliveryPeriodStart != null) {
zpp.setDeliveryDate(XMLTools.tryDate(deliveryPeriodStart));
}
xpr = xpath.compile("//*[local-name()=\"PaymentMeans\"]"); //UBL only xpr = xpath.compile("//*[local-name()=\"PaymentMeans\"]"); //UBL only
NodeList paymentMeansNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); NodeList paymentMeansNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
@@ -282,7 +569,7 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
NodeList paymentTermChilds = paymentMeansChilds.item(meansChildIndex).getChildNodes(); NodeList paymentTermChilds = paymentMeansChilds.item(meansChildIndex).getChildNodes();
for (int paymentTermChildIndex = 0; paymentTermChildIndex < paymentTermChilds.getLength(); paymentTermChildIndex++) { for (int paymentTermChildIndex = 0; paymentTermChildIndex < paymentTermChilds.getLength(); paymentTermChildIndex++) {
if ((paymentTermChilds.item(paymentTermChildIndex).getLocalName() != null) && (paymentTermChilds.item(paymentTermChildIndex).getLocalName().equals("ID"))) { if ((paymentTermChilds.item(paymentTermChildIndex).getLocalName() != null) && (paymentTermChilds.item(paymentTermChildIndex).getLocalName().equals("ID"))) {
String IBAN = paymentTermChilds.item(paymentTermChildIndex).getTextContent(); IBAN = paymentTermChilds.item(paymentTermChildIndex).getTextContent();
if (IBAN != null) { if (IBAN != null) {
BankDetails bd = new BankDetails(IBAN); BankDetails bd = new BankDetails(IBAN);
bankDetails.add(bd); bankDetails.add(bd);
@@ -295,6 +582,11 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
zpp.setDueDate(dueDate).setDeliveryDate(deliveryDate).setIssueDate(issueDate).setSender(new TradeParty(SellerNodes)).setRecipient(new TradeParty(BuyerNodes)).setNumber(number).setDocumentCode(typeCode); zpp.setDueDate(dueDate).setDeliveryDate(deliveryDate).setIssueDate(issueDate).setSender(new TradeParty(SellerNodes)).setRecipient(new TradeParty(BuyerNodes)).setNumber(number).setDocumentCode(typeCode);
if ((directDebitMandateID != null) && (IBAN != null)) {
DirectDebit d = new DirectDebit(IBAN, directDebitMandateID);
zpp.getSender().addDebitDetails(d);
}
bankDetails.forEach(bankDetail -> zpp.getSender().addBankDetails(bankDetail)); bankDetails.forEach(bankDetail -> zpp.getSender().addBankDetails(bankDetail));
if (payeeNodes.getLength() > 0) { if (payeeNodes.getLength() > 0) {
@@ -339,7 +631,7 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
xpr = xpath.compile("//*[local-name()=\"AttachmentBinaryObject\"]|//*[local-name()=\"EmbeddedDocumentBinaryObject\"]"); xpr = xpath.compile("//*[local-name()=\"AttachmentBinaryObject\"]|//*[local-name()=\"EmbeddedDocumentBinaryObject\"]");
NodeList attachmentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); NodeList attachmentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
for (int i = 0; i < attachmentNodes.getLength(); i++) { for (int i = 0; i < attachmentNodes.getLength(); i++) {
FileAttachment fa=new FileAttachment(attachmentNodes.item(i).getAttributes().getNamedItem("filename").getNodeValue(),attachmentNodes.item(i).getAttributes().getNamedItem("mimeCode").getNodeValue(),"Data", Base64.getDecoder().decode(attachmentNodes.item(i).getTextContent())); FileAttachment fa = new FileAttachment(attachmentNodes.item(i).getAttributes().getNamedItem("filename").getNodeValue(), attachmentNodes.item(i).getAttributes().getNamedItem("mimeCode").getNodeValue(), "Data", Base64.getDecoder().decode(attachmentNodes.item(i).getTextContent()));
fileAttachments.add(fa); fileAttachments.add(fa);
// filename = "Aufmass.png" mimeCode = "image/png" // filename = "Aufmass.png" mimeCode = "image/png"
//EmbeddedDocumentBinaryObject cbc:EmbeddedDocumentBinaryObject mimeCode="image/png" filename="Aufmass.png" //EmbeddedDocumentBinaryObject cbc:EmbeddedDocumentBinaryObject mimeCode="image/png" filename="Aufmass.png"
@@ -431,13 +723,97 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
&& ((!expectedStringTotalGross.equals(XMLTools.nDigitFormat(expectedGrandTotal, 2))) && ((!expectedStringTotalGross.equals(XMLTools.nDigitFormat(expectedGrandTotal, 2)))
&& (!ignoreCalculationErrors))) { && (!ignoreCalculationErrors))) {
throw new ParseException( throw new ParseException(
"Could not reproduce the invoice, this could mean that it could not be read properly", 0); "Could not reproduce the invoice, this could mean that it could not be read properly exp "+expectedStringTotalGross+" is "+XMLTools.nDigitFormat(expectedGrandTotal, 2), 0);
} }
} }
return zpp; return zpp;
} }
protected Document getDocument() {
return document;
}
protected String extractString(String xpathStr) {
if (!containsMeta) {
throw new ZUGFeRDExportException("No suitable data/ZUGFeRD file could be found.");
}
final String result;
try {
final Document document = getDocument();
final XPathFactory xpathFact = XPathFactory.newInstance();
final XPath xpath = xpathFact.newXPath();
result = xpath.evaluate(xpathStr, document);
} catch (final XPathExpressionException e) {
LOGGER.error("Failed to evaluate XPath", e);
throw new ZUGFeRDExportException(e);
}
return result;
}
public EStandard getStandard() throws Exception {
if (!containsMeta) {
throw new Exception("Not yet parsed");
}
final String head = getUTF8();
String rootNode = extractString("local-name(/*)");
if (rootNode.equals("CrossIndustryDocument")) {
return EStandard.zugferd;
} else if (rootNode.equals("Invoice")) {
return EStandard.ubl;
} else if (rootNode.equals("CreditNote")) {
return EStandard.ubl;
} else if (rootNode.equals("CrossIndustryInvoice")) {
return EStandard.facturx;
} else if (rootNode.equals("SCRDMCCBDACIDAMessageStructure")) {
return EStandard.despatchadvice;
} else if (head.contains("<rsm:SCRDMCCBDACIOMessageStructure")) {
return EStandard.orderx;
}
throw new Exception("ZUGFeRD version could not be determined");
}
/**
* @return return UTF8 XML (without BOM) of the invoice
*/
public String getUTF8() {
if (rawXML == null) {
return null;
}
if (rawXML.length < 3) {
return new String(rawXML);
}
final byte[] bomlessData;
if ((rawXML[0] == (byte) 0xEF)
&& (rawXML[1] == (byte) 0xBB)
&& (rawXML[2] == (byte) 0xBF)) {
// I don't like BOMs, lets remove it
bomlessData = new byte[rawXML.length - 3];
System.arraycopy(rawXML, 3, bomlessData, 0,
rawXML.length - 3);
} else {
bomlessData = rawXML;
}
return new String(bomlessData);
}
static String convertStreamToString(java.io.InputStream is) {
try {
return IOUtils.toString(is, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
/*** /***
* *
* @return the file attachments embedded in XML (using base64) decoded as byte array, * @return the file attachments embedded in XML (using base64) decoded as byte array,
@@ -461,18 +837,18 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
} }
/***
* have the item prices be determined from the line total.
* That's a workaround for some invoices which just put 0 as item price
*/
public void doRecalculateItemPricesFromLineTotals() {
recalcPrice = true;
}
/*** /***
* do not raise ParseExceptions even if the reproduced invoice total does not match the given value * sets the XML for the importer to parse
* @param XML the UBL or CII
*/ */
public void doIgnoreCalculationErrors() { public void fromXML(String XML) {
ignoreCalculationErrors = true; try {
containsMeta = true;
setRawXML(XML.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
LOGGER.error(e.getMessage(), e);
} }
}
} }

View File

@@ -49,4 +49,86 @@ public class DeSerializationTest extends TestCase {
assertEquals("info@company.com", fromJSON.getSender().getUriUniversalCommunicationID()); assertEquals("info@company.com", fromJSON.getSender().getUriUniversalCommunicationID());
} }
public void testAllowanceRead() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
// [{"stringValue":"a","intValue":1,"booleanValue":true},
// {"stringValue":"bc","intValue":3,"booleanValue":false}]
Invoice fromJSON = mapper.readValue("{\n" +
" \"documentCode\": \"380\",\n" +
" \"number\": \"471102\",\n" +
" \"ownOrganisationName\": \"Lieferant GmbH\",\n" +
" \"currency\": \"EUR\",\n" +
" \"issueDate\": \"2018-03-03T23:00:00.000+00:00\",\n" +
" \"deliveryDate\": \"2018-03-03T23:00:00.000+00:00\",\n" +
" \"sender\": {\n" +
" \"name\": \"Lieferant GmbH\",\n" +
" \"zip\": \"80333\",\n" +
" \"street\": \"Lieferantenstraße 20\",\n" +
" \"location\": \"München\",\n" +
" \"country\": \"DE\",\n" +
" \"taxID\": \"201/113/40209\",\n" +
" \"vatID\": \"DE123456789\",\n" +
" \"vatid\": \"DE123456789\"\n" +
" },\n" +
" \"recipient\": {\n" +
" \"name\": \"Kunden AG Mitte\",\n" +
" \"zip\": \"69876\",\n" +
" \"street\": \"Kundenstraße 15\",\n" +
" \"location\": \"Frankfurt\",\n" +
" \"country\": \"DE\"\n" +
" },\n" +
" \"grandTotal\": 234.43,\n" +
" \"zfitems\": [\n" +
" {\n" +
" \"price\": 9.9,\n" +
" \"quantity\": 20,\n" +
" \"tax\": null,\n" +
" \"grossPrice\": null,\n" +
" \"lineTotalAmount\": null,\n" +
" \"basisQuantity\": 1,\n" +
" \"detailedDeliveryPeriodFrom\": null,\n" +
" \"detailedDeliveryPeriodTo\": null,\n" +
" \"id\": null,\n" +
" \"product\": {\n" +
" \"unit\": \"H87\",\n" +
" \"name\": \"Trennblätter A4\",\n" +
" \"taxCategoryCode\": \"S\",\n" +
" \"attributes\": null,\n" +
" \"vatpercent\": 19\n" +
" },\n" +
" \"value\": 9.9\n" +
" }\n" +
" ],\n" +
" \"tradeSettlement\": null,\n" +
" \"ownTaxID\": \"201/113/40209\",\n" +
" \"ownVATID\": \"DE123456789\",\n" +
" \"ownStreet\": \"Lieferantenstraße 20\",\n" +
" \"ownZIP\": \"80333\",\n" +
" \"ownLocation\": \"München\",\n" +
" \"ownCountry\": \"DE\",\n" +
" \"zfallowances\": [\n" +
" {\n" +
" \"totalAmount\": 1,\n" +
" \"taxPercent\": 19,\n" +
" \"reason\": \"Sondernachlass\",\n" +
" \"reasonCode\": null,\n" +
" \"categoryCode\": \"S\",\n" +
" \"charge\": false\n" +
" }\n" +
" ]\n" +
"}", Invoice.class);
TransactionCalculator tc=new TransactionCalculator(fromJSON);
assertEquals(tc.getGrandTotal(),new BigDecimal("234.43"));
assertEquals(fromJSON.getNumber(), fromJSON.getNumber());
assertEquals(fromJSON.getZFItems().length, fromJSON.getZFItems().length);
}
} }

View File

@@ -214,8 +214,10 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
public void testForeignImport() { public void testForeignImport() {
InputStream inputStream = this.getClass().getResourceAsStream("/zugferd_invoice.pdf"); InputStream inputStream = this.getClass().getResourceAsStream("/zugferd_invoice.pdf");
ZUGFeRDImporter zi = new ZUGFeRDImporter(inputStream); ZUGFeRDImporter zi = new ZUGFeRDImporter();
zi.doRecalculateItemPricesFromLineTotals();
zi.doIgnoreCalculationErrors();
zi.setInputStream(inputStream);
// Reading ZUGFeRD // Reading ZUGFeRD
String amount = zi.getAmount(); String amount = zi.getAmount();

View File

@@ -137,7 +137,7 @@ public class ProfilesMinimumBasicWLTest extends TestCase {
ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_PDF_FX_MINIMUM_INV); ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_PDF_FX_MINIMUM_INV);
// Reading ZUGFeRD // Reading ZUGFeRD
assertEquals("145.37",zi.getAmount()); assertEquals("146.37",zi.getAmount());
// assertEquals(zi.getBIC(), ownBIC); // assertEquals(zi.getBIC(), ownBIC);
// assertEquals(zi.getIBAN(), ownIBAN); // assertEquals(zi.getIBAN(), ownIBAN);
assertEquals(ownOrgName, zi.getHolder()); assertEquals(ownOrgName, zi.getHolder());

View File

@@ -129,13 +129,12 @@ public class XRTest extends TestCase {
Invoice readInvoice = new Invoice(); Invoice readInvoice = new Invoice();
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(); ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter();
try { try {
zii.setRawXML(zf2p.getXML(), false);
zii.setRawXML(zf2p.getXML());
zii.extractInto(readInvoice); zii.extractInto(readInvoice);
} catch (ParseException | XPathExpressionException xp) { } catch (ParseException | XPathExpressionException xp) {
fail("Exception not expected"); fail("ParseException not expected");
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); fail("IOException not expected");
} }
List<FileAttachment> attachedFiles=zii.getFileAttachmentsXML(); List<FileAttachment> attachedFiles=zii.getFileAttachmentsXML();
assertNotNull(attachedFiles); assertNotNull(attachedFiles);

View File

@@ -1,22 +1,24 @@
/** ********************************************************************** /**
* * *********************************************************************
* <p>
* Copyright 2019 Jochen Staerk * Copyright 2019 Jochen Staerk
* * <p>
* Use is subject to license terms. * Use is subject to license terms.
* * <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not * Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy * use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0. * of the License at http://www.apache.org/licenses/LICENSE-2.0.
* * <p>
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * <p>
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
* * <p>
*********************************************************************** */ * **********************************************************************
*/
package org.mustangproject.ZUGFeRD; package org.mustangproject.ZUGFeRD;
import java.io.IOException; import java.io.IOException;
@@ -190,13 +192,13 @@ public class ZF2Test extends MustangReaderTestCase {
assertEquals(zi.getInvoiceID(), "RE-20170509/505"); assertEquals(zi.getInvoiceID(), "RE-20170509/505");
assertEquals(zi.getZUGFeRDProfil(), "COMFORT"); assertEquals(zi.getZUGFeRDProfil(), "COMFORT");
assertEquals(zi.getInvoiceCurrencyCode(), "EUR"); assertEquals(zi.getInvoiceCurrencyCode(), "EUR");
assertEquals(zi.getIssuerAssignedID(),""); assertEquals(zi.getIssuerAssignedID(), "");
assertEquals(zi.getIssueDate(), "20170509"); assertEquals(zi.getIssueDate(), "20170509");
assertEquals(zi.getTaxPointDate(), "20170507"); assertEquals(zi.getTaxPointDate(), "20170507");
assertEquals(zi.getPaymentTerms(), "Zahlbar ohne Abzug bis zum 30.05.2017"); assertEquals(zi.getPaymentTerms(), "Zahlbar ohne Abzug bis zum 30.05.2017");
assertEquals(zi.getLineTotalAmount(), "496.00"); assertEquals(zi.getLineTotalAmount(), "496.00");
assertEquals(zi.getTaxBasisTotalAmount(), "496.00"); assertEquals(zi.getTaxBasisTotalAmount(), "496.00");
assertEquals(zi.getTaxTotalAmount(),"75.04"); assertEquals(zi.getTaxTotalAmount(), "75.04");
assertEquals(zi.getRoundingAmount(), ""); assertEquals(zi.getRoundingAmount(), "");
assertEquals(zi.getPaidAmount(), "0.00"); assertEquals(zi.getPaidAmount(), "0.00");
assertEquals(zi.getBuyerTradePartyName(), "Theodor Est"); assertEquals(zi.getBuyerTradePartyName(), "Theodor Est");
@@ -206,8 +208,8 @@ public class ZF2Test extends MustangReaderTestCase {
assertEquals(zi.getBuyertradePartySpecifiedTaxRegistrationID(), "DE999999999"); assertEquals(zi.getBuyertradePartySpecifiedTaxRegistrationID(), "DE999999999");
assertEquals(zi.getIncludedNote(), ""); assertEquals(zi.getIncludedNote(), "");
assertEquals(zi.getHolder(), getOwnOrganisationName()); assertEquals(zi.getHolder(), getOwnOrganisationName());
assertEquals(zi.getDocumentCode(),"380"); assertEquals(zi.getDocumentCode(), "380");
assertEquals(zi.getReference(),"AB321"); assertEquals(zi.getReference(), "AB321");
assertEquals(zi.getAmount(), "571.04"); assertEquals(zi.getAmount(), "571.04");
assertEquals(zi.getBIC(), "COBADEFFXXX"); assertEquals(zi.getBIC(), "COBADEFFXXX");
assertEquals(zi.getIBAN(), "DE88 2008 0000 0970 3757 00"); assertEquals(zi.getIBAN(), "DE88 2008 0000 0970 3757 00");
@@ -244,7 +246,9 @@ public class ZF2Test extends MustangReaderTestCase {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
} /** }
/**
* The exporter test bases on @{code * The exporter test bases on @{code
* ./src/test/MustangBeispiel20221026.pdf}, adds * ./src/test/MustangBeispiel20221026.pdf}, adds
* metadata, writes to @{code ./target/testout-*} and then imports to check the * metadata, writes to @{code ./target/testout-*} and then imports to check the
@@ -264,7 +268,7 @@ public class ZF2Test extends MustangReaderTestCase {
assertEquals("Innerhalb von 30 Tagen 2% Skonto, 60 Tage ohne Abzug", zi.getPaymentTerms()); assertEquals("Innerhalb von 30 Tagen 2% Skonto, 60 Tage ohne Abzug", zi.getPaymentTerms());
assertEquals("804.35", zi.getLineTotalAmount()); assertEquals("804.35", zi.getLineTotalAmount());
assertEquals("809.34", zi.getTaxBasisTotalAmount()); assertEquals("809.34", zi.getTaxBasisTotalAmount());
assertEquals("153.77",zi.getTaxTotalAmount()); assertEquals("153.77", zi.getTaxTotalAmount());
assertEquals("", zi.getRoundingAmount()); assertEquals("", zi.getRoundingAmount());
assertEquals("0.00", zi.getPaidAmount()); assertEquals("0.00", zi.getPaidAmount());
assertEquals("Beispiel AG", zi.getBuyerTradePartyName()); assertEquals("Beispiel AG", zi.getBuyerTradePartyName());
@@ -273,8 +277,8 @@ public class ZF2Test extends MustangReaderTestCase {
assertEquals("10000", zi.getBuyerTradePartyID()); assertEquals("10000", zi.getBuyerTradePartyID());
assertEquals("\n weclapp.com\nSomestreet 42\n08155 Some city\nDE\n ", zi.getIncludedNote()); assertEquals("\n weclapp.com\nSomestreet 42\n08155 Some city\nDE\n ", zi.getIncludedNote());
assertEquals("weclapp.com", zi.getHolder()); assertEquals("weclapp.com", zi.getHolder());
assertEquals("380",zi.getDocumentCode()); assertEquals("380", zi.getDocumentCode());
assertEquals("01-95",zi.getReference()); assertEquals("01-95", zi.getReference());
assertEquals("RE1001", zi.getForeignReference()); assertEquals("RE1001", zi.getForeignReference());
assertEquals("54321", zi.getBuyerTradePartyAddress().getPostcodeCode()); assertEquals("54321", zi.getBuyerTradePartyAddress().getPostcodeCode());
assertEquals("Feldstraße 34", zi.getBuyerTradePartyAddress().getLineOne()); assertEquals("Feldstraße 34", zi.getBuyerTradePartyAddress().getLineOne());

View File

@@ -21,8 +21,8 @@
*/ */
package org.mustangproject.ZUGFeRD; package org.mustangproject.ZUGFeRD;
import org.mustangproject.FileAttachment; import com.fasterxml.jackson.databind.ObjectMapper;
import org.mustangproject.Invoice; import org.mustangproject.*;
import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathExpressionException;
import java.io.*; import java.io.*;
@@ -33,6 +33,7 @@ import java.nio.file.Paths;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date;
/*** /***
@@ -324,10 +325,35 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase {
} }
/*
public void testImportDebit() {
File CIIinputFile = getResourceAsFile("cii/minimalDebit.xml");
try {
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(new FileInputStream(CIIinputFile));
Invoice i=zii.extractInvoice();
assertEquals("DE21860000000086001055", i.getSender().getBankDetails().get(0).getIBAN());
ObjectMapper mapper = new ObjectMapper();
String jsonArray = mapper.writeValueAsString(i);
// assertEquals("",jsonArray);
} catch (IOException e) {
fail("IOException not expected");
} catch (XPathExpressionException e) {
throw new RuntimeException(e);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
public void testEEISI_300_cii_Import() { public void testEEISI_300_cii_Import() {
boolean hasExceptions = false; boolean hasExceptions = false;
File input = getResourceAsFile("not_validating_full_invoice_based_onTest_EeISI_300_CENfullmodel2.ubl.xml"); /* File input = getResourceAsFile("not_validating_full_invoice_based_onTest_EeISI_300_CENfullmodel.cii.xml");
ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(); ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter();
@@ -352,8 +378,8 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase {
assertFalse(hasExceptions); assertFalse(hasExceptions);
TransactionCalculator tc = new TransactionCalculator(invoice); TransactionCalculator tc = new TransactionCalculator(invoice);
assertEquals(new BigDecimal("205.00"), tc.getGrandTotal()); assertEquals(new BigDecimal("205.00"), tc.getGrandTotal());
*/
} }
*/
} }

View File

@@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8"?>
<rsm:CrossIndustryInvoice xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" xmlns:qdt="urn:un:unece:uncefact:data:standard:QualifiedDataType:100">
<!-- generated by: mustangproject.org vnull-->
<rsm:ExchangedDocumentContext>
<ram:GuidelineSpecifiedDocumentContextParameter>
<ram:ID>urn:cen.eu:en16931:2017</ram:ID>
</ram:GuidelineSpecifiedDocumentContextParameter>
</rsm:ExchangedDocumentContext>
<rsm:ExchangedDocument>
<ram:ID>471102</ram:ID>
<ram:TypeCode>380</ram:TypeCode>
<ram:IssueDateTime>
<udt:DateTimeString format="102">20180304</udt:DateTimeString>
</ram:IssueDateTime>
</rsm:ExchangedDocument>
<rsm:SupplyChainTradeTransaction>
<ram:IncludedSupplyChainTradeLineItem>
<ram:AssociatedDocumentLineDocument>
<ram:LineID>1</ram:LineID>
</ram:AssociatedDocumentLineDocument>
<ram:SpecifiedTradeProduct>
<ram:Name>Trennblätter A4</ram:Name>
</ram:SpecifiedTradeProduct>
<ram:SpecifiedLineTradeAgreement>
<ram:NetPriceProductTradePrice>
<ram:ChargeAmount>9.9000</ram:ChargeAmount>
<ram:BasisQuantity unitCode="H87">1.0000</ram:BasisQuantity>
</ram:NetPriceProductTradePrice>
</ram:SpecifiedLineTradeAgreement>
<ram:SpecifiedLineTradeDelivery>
<ram:BilledQuantity unitCode="H87">20.0000</ram:BilledQuantity>
</ram:SpecifiedLineTradeDelivery>
<ram:SpecifiedLineTradeSettlement>
<ram:ApplicableTradeTax>
<ram:TypeCode>VAT</ram:TypeCode>
<ram:CategoryCode>S</ram:CategoryCode>
<ram:RateApplicablePercent>19.00</ram:RateApplicablePercent>
</ram:ApplicableTradeTax>
<ram:SpecifiedTradeSettlementLineMonetarySummation>
<ram:LineTotalAmount>198.00</ram:LineTotalAmount>
</ram:SpecifiedTradeSettlementLineMonetarySummation>
</ram:SpecifiedLineTradeSettlement>
</ram:IncludedSupplyChainTradeLineItem>
<ram:IncludedSupplyChainTradeLineItem>
<ram:AssociatedDocumentLineDocument>
<ram:LineID>2</ram:LineID>
</ram:AssociatedDocumentLineDocument>
<ram:SpecifiedTradeProduct>
<ram:Name>Joghurt Banane</ram:Name>
</ram:SpecifiedTradeProduct>
<ram:SpecifiedLineTradeAgreement>
<ram:NetPriceProductTradePrice>
<ram:ChargeAmount>5.5000</ram:ChargeAmount>
<ram:BasisQuantity unitCode="H87">1.0000</ram:BasisQuantity>
</ram:NetPriceProductTradePrice>
</ram:SpecifiedLineTradeAgreement>
<ram:SpecifiedLineTradeDelivery>
<ram:BilledQuantity unitCode="H87">50.0000</ram:BilledQuantity>
</ram:SpecifiedLineTradeDelivery>
<ram:SpecifiedLineTradeSettlement>
<ram:ApplicableTradeTax>
<ram:TypeCode>VAT</ram:TypeCode>
<ram:CategoryCode>S</ram:CategoryCode>
<ram:RateApplicablePercent>7.00</ram:RateApplicablePercent>
</ram:ApplicableTradeTax>
<ram:SpecifiedTradeSettlementLineMonetarySummation>
<ram:LineTotalAmount>275.00</ram:LineTotalAmount>
</ram:SpecifiedTradeSettlementLineMonetarySummation>
</ram:SpecifiedLineTradeSettlement>
</ram:IncludedSupplyChainTradeLineItem>
<ram:ApplicableHeaderTradeAgreement>
<ram:SellerTradeParty>
<ram:Name>Lieferant GmbH</ram:Name>
<ram:PostalTradeAddress>
<ram:PostcodeCode>80333</ram:PostcodeCode>
<ram:LineOne>Lieferantenstraße 20</ram:LineOne>
<ram:CityName>München</ram:CityName>
<ram:CountryID>DE</ram:CountryID>
</ram:PostalTradeAddress>
<ram:SpecifiedTaxRegistration>
<ram:ID schemeID="VA">DE123456789</ram:ID>
</ram:SpecifiedTaxRegistration>
<ram:SpecifiedTaxRegistration>
<ram:ID schemeID="FC">201/113/40209</ram:ID>
</ram:SpecifiedTaxRegistration>
</ram:SellerTradeParty>
<ram:BuyerTradeParty>
<ram:Name>Kunden AG Mitte</ram:Name>
<ram:PostalTradeAddress>
<ram:PostcodeCode>69876</ram:PostcodeCode>
<ram:LineOne>Kundenstraße 15</ram:LineOne>
<ram:CityName>Frankfurt</ram:CityName>
<ram:CountryID>DE</ram:CountryID>
</ram:PostalTradeAddress>
</ram:BuyerTradeParty>
</ram:ApplicableHeaderTradeAgreement>
<ram:ApplicableHeaderTradeDelivery>
<ram:ActualDeliverySupplyChainEvent>
<ram:OccurrenceDateTime>
<udt:DateTimeString format="102">20180304</udt:DateTimeString>
</ram:OccurrenceDateTime>
</ram:ActualDeliverySupplyChainEvent>
</ram:ApplicableHeaderTradeDelivery>
<ram:ApplicableHeaderTradeSettlement>
<ram:CreditorReferenceID>DE98ZZZ09999999999</ram:CreditorReferenceID>
<ram:InvoiceCurrencyCode>EUR</ram:InvoiceCurrencyCode>
<ram:SpecifiedTradeSettlementPaymentMeans>
<ram:TypeCode>59</ram:TypeCode>
<ram:PayerPartyDebtorFinancialAccount>
<ram:IBANID>DE21860000000086001055</ram:IBANID></ram:PayerPartyDebtorFinancialAccount>
</ram:SpecifiedTradeSettlementPaymentMeans>
<ram:ApplicableTradeTax>
<ram:CalculatedAmount>19.25</ram:CalculatedAmount>
<ram:TypeCode>VAT</ram:TypeCode>
<ram:BasisAmount>275.00</ram:BasisAmount>
<ram:CategoryCode>S</ram:CategoryCode>
<ram:RateApplicablePercent>7.00</ram:RateApplicablePercent>
</ram:ApplicableTradeTax>
<ram:ApplicableTradeTax>
<ram:CalculatedAmount>37.62</ram:CalculatedAmount>
<ram:TypeCode>VAT</ram:TypeCode>
<ram:BasisAmount>198.00</ram:BasisAmount>
<ram:CategoryCode>S</ram:CategoryCode>
<ram:RateApplicablePercent>19.00</ram:RateApplicablePercent>
</ram:ApplicableTradeTax>
<ram:SpecifiedTradePaymentTerms>
<ram:Description>Der Betrag in Höhe von EUR 529,87 wird am 20.03.2018 von Ihrem Konto per SEPA-Lastschrift eingezogen.
</ram:Description>
<ram:DirectDebitMandateID>REF A-123</ram:DirectDebitMandateID>
</ram:SpecifiedTradePaymentTerms>
<ram:SpecifiedTradeSettlementHeaderMonetarySummation>
<ram:LineTotalAmount>473.00</ram:LineTotalAmount>
<ram:ChargeTotalAmount>0.00</ram:ChargeTotalAmount>
<ram:AllowanceTotalAmount>0.00</ram:AllowanceTotalAmount>
<ram:TaxBasisTotalAmount>473.00</ram:TaxBasisTotalAmount>
<ram:TaxTotalAmount currencyID="EUR">56.87</ram:TaxTotalAmount>
<ram:GrandTotalAmount>529.87</ram:GrandTotalAmount>
<ram:TotalPrepaidAmount>0.00</ram:TotalPrepaidAmount>
<ram:DuePayableAmount>529.87</ram:DuePayableAmount>
</ram:SpecifiedTradeSettlementHeaderMonetarySummation>
</ram:ApplicableHeaderTradeSettlement>
</rsm:SupplyChainTradeTransaction>
</rsm:CrossIndustryInvoice>

View File

@@ -1,5 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<rsm:CrossIndustryInvoice xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100" xmlns:qdt="urn:un:unece:uncefact:data:standard:QualifiedDataType:100" xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"> <rsm:CrossIndustryInvoice xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
xmlns:qdt="urn:un:unece:uncefact:data:standard:QualifiedDataType:100"
xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">
<rsm:ExchangedDocumentContext> <rsm:ExchangedDocumentContext>
<ram:BusinessProcessSpecifiedDocumentContextParameter> <ram:BusinessProcessSpecifiedDocumentContextParameter>
<ram:ID>BT-23 Business Process Type</ram:ID> <ram:ID>BT-23 Business Process Type</ram:ID>
@@ -16,11 +19,11 @@
</ram:IssueDateTime> </ram:IssueDateTime>
<ram:IncludedNote> <ram:IncludedNote>
<ram:Content>invoice note text</ram:Content> <ram:Content>invoice note text</ram:Content>
<ram:SubjectCode>#AAA#</ram:SubjectCode> <ram:SubjectCode>AAA</ram:SubjectCode>
</ram:IncludedNote> </ram:IncludedNote>
<ram:IncludedNote> <ram:IncludedNote>
<ram:Content>invoice note text 2</ram:Content> <ram:Content>invoice note text 2</ram:Content>
<ram:SubjectCode>#AAA#</ram:SubjectCode> <ram:SubjectCode>AAA</ram:SubjectCode>
</ram:IncludedNote> </ram:IncludedNote>
</rsm:ExchangedDocument> </rsm:ExchangedDocument>
<rsm:SupplyChainTradeTransaction> <rsm:SupplyChainTradeTransaction>
@@ -32,7 +35,9 @@
</ram:IncludedNote> </ram:IncludedNote>
</ram:AssociatedDocumentLineDocument> </ram:AssociatedDocumentLineDocument>
<ram:SpecifiedTradeProduct> <ram:SpecifiedTradeProduct>
<ram:GlobalID>Item standar identifier</ram:GlobalID> <ram:GlobalID schemeID="0060">Item standar identifier
</ram:GlobalID>
<ram:SellerAssignedID>Item seller's identifier</ram:SellerAssignedID> <ram:SellerAssignedID>Item seller's identifier</ram:SellerAssignedID>
<ram:BuyerAssignedID>Item buyer's identifier</ram:BuyerAssignedID> <ram:BuyerAssignedID>Item buyer's identifier</ram:BuyerAssignedID>
<ram:Name>Item name</ram:Name> <ram:Name>Item name</ram:Name>
@@ -95,7 +100,7 @@
<ram:CalculationPercent>1.00</ram:CalculationPercent> <ram:CalculationPercent>1.00</ram:CalculationPercent>
<ram:BasisAmount>1000.00</ram:BasisAmount> <ram:BasisAmount>1000.00</ram:BasisAmount>
<ram:ActualAmount>10.00</ram:ActualAmount> <ram:ActualAmount>10.00</ram:ActualAmount>
<ram:ReasonCode>55</ram:ReasonCode> <ram:ReasonCode>95</ram:ReasonCode>
<ram:Reason>Invoice line allowance reason</ram:Reason> <ram:Reason>Invoice line allowance reason</ram:Reason>
</ram:SpecifiedTradeAllowanceCharge> </ram:SpecifiedTradeAllowanceCharge>
<ram:SpecifiedTradeAllowanceCharge> <ram:SpecifiedTradeAllowanceCharge>
@@ -114,7 +119,6 @@
<ram:AdditionalReferencedDocument> <ram:AdditionalReferencedDocument>
<ram:IssuerAssignedID>Line object identifier</ram:IssuerAssignedID> <ram:IssuerAssignedID>Line object identifier</ram:IssuerAssignedID>
<ram:TypeCode>130</ram:TypeCode> <ram:TypeCode>130</ram:TypeCode>
<ram:ReferenceTypeCode />
</ram:AdditionalReferencedDocument> </ram:AdditionalReferencedDocument>
<ram:ReceivableSpecifiedTradeAccountingAccount> <ram:ReceivableSpecifiedTradeAccountingAccount>
<ram:ID>6789</ram:ID> <ram:ID>6789</ram:ID>
@@ -155,7 +159,7 @@
<ram:Name>Seller name</ram:Name> <ram:Name>Seller name</ram:Name>
<ram:Description>Seller additional legal information</ram:Description> <ram:Description>Seller additional legal information</ram:Description>
<ram:SpecifiedLegalOrganization> <ram:SpecifiedLegalOrganization>
<ram:ID schemeID="0310">Seller legal identifier</ram:ID> <!-- <ram:ID schemeID="0310">Seller legal identifier</ram:ID> -->
<ram:TradingBusinessName>Seller trading name</ram:TradingBusinessName> <ram:TradingBusinessName>Seller trading name</ram:TradingBusinessName>
</ram:SpecifiedLegalOrganization> </ram:SpecifiedLegalOrganization>
<ram:DefinedTradeContact> <ram:DefinedTradeContact>
@@ -177,7 +181,7 @@
<ram:CountrySubDivisionName>Seller country subdivision</ram:CountrySubDivisionName> <ram:CountrySubDivisionName>Seller country subdivision</ram:CountrySubDivisionName>
</ram:PostalTradeAddress> </ram:PostalTradeAddress>
<ram:URIUniversalCommunication> <ram:URIUniversalCommunication>
<ram:URIID schemeID="SMTP">Seller electronic address</ram:URIID> <ram:URIID schemeID="EM">Seller electronic address</ram:URIID>
</ram:URIUniversalCommunication> </ram:URIUniversalCommunication>
<ram:SpecifiedTaxRegistration> <ram:SpecifiedTaxRegistration>
<ram:ID schemeID="VA">DE12345677</ram:ID> <ram:ID schemeID="VA">DE12345677</ram:ID>
@@ -212,7 +216,7 @@
<ram:CountrySubDivisionName>Buyer country subdivision</ram:CountrySubDivisionName> <ram:CountrySubDivisionName>Buyer country subdivision</ram:CountrySubDivisionName>
</ram:PostalTradeAddress> </ram:PostalTradeAddress>
<ram:URIUniversalCommunication> <ram:URIUniversalCommunication>
<ram:URIID schemeID="DE:SMTP">Buyer electronic address</ram:URIID> <ram:URIID schemeID="EM">Buyer electronic address</ram:URIID>
</ram:URIUniversalCommunication> </ram:URIUniversalCommunication>
<ram:SpecifiedTaxRegistration> <ram:SpecifiedTaxRegistration>
<ram:ID schemeID="VA">IE394838894</ram:ID> <ram:ID schemeID="VA">IE394838894</ram:ID>
@@ -252,7 +256,7 @@
<ram:AdditionalReferencedDocument> <ram:AdditionalReferencedDocument>
<ram:IssuerAssignedID>rst</ram:IssuerAssignedID> <ram:IssuerAssignedID>rst</ram:IssuerAssignedID>
<ram:TypeCode>130</ram:TypeCode> <ram:TypeCode>130</ram:TypeCode>
<ram:ReferenceTypeCode>0090</ram:ReferenceTypeCode> <ram:ReferenceTypeCode>AAA</ram:ReferenceTypeCode>
</ram:AdditionalReferencedDocument> </ram:AdditionalReferencedDocument>
<ram:SpecifiedProcuringProject> <ram:SpecifiedProcuringProject>
<ram:ID>456</ram:ID> <ram:ID>456</ram:ID>
@@ -311,23 +315,23 @@
<ram:IBANID>IT1212341234123412</ram:IBANID> <ram:IBANID>IT1212341234123412</ram:IBANID>
<ram:AccountName>Payment account name</ram:AccountName> <ram:AccountName>Payment account name</ram:AccountName>
</ram:PayeePartyCreditorFinancialAccount> </ram:PayeePartyCreditorFinancialAccount>
<ram:PayerSpecifiedDebtorFinancialInstitution> <!-- <ram:BICID>BSCTCH22</ram:BICID> -->
<ram:BICID>BSCTCH22</ram:BICID> <!-- <ram:PayerSpecifiedDebtorFinancialInstitution>
</ram:PayerSpecifiedDebtorFinancialInstitution> </ram:PayerSpecifiedDebtorFinancialInstitution> -->
<ram:PayeePartyCreditorFinancialAccount> <!-- <ram:PayeePartyCreditorFinancialAccount>
<ram:IBANID>IT1212341234123413</ram:IBANID> <ram:IBANID>IT1212341234123413</ram:IBANID>
<ram:AccountName>Payment account name 2</ram:AccountName> <ram:AccountName>Payment account name 2</ram:AccountName>
</ram:PayeePartyCreditorFinancialAccount> </ram:PayeePartyCreditorFinancialAccount>
<ram:PayerSpecifiedDebtorFinancialInstitution> <ram:PayerSpecifiedDebtorFinancialInstitution>
<ram:BICID>BSCTCH22</ram:BICID> <ram:BICID>BSCTCH22</ram:BICID>
</ram:PayerSpecifiedDebtorFinancialInstitution> </ram:PayerSpecifiedDebtorFinancialInstitution> -->
</ram:SpecifiedTradeSettlementPaymentMeans> </ram:SpecifiedTradeSettlementPaymentMeans>
<ram:ApplicableTradeTax> <ram:ApplicableTradeTax>
<ram:CalculatedAmount>50.00</ram:CalculatedAmount> <ram:CalculatedAmount>50.00</ram:CalculatedAmount>
<ram:TypeCode>VAT</ram:TypeCode> <ram:TypeCode>VAT</ram:TypeCode>
<ram:BasisAmount>1000.00</ram:BasisAmount> <ram:BasisAmount>1000.00</ram:BasisAmount>
<ram:CategoryCode>S</ram:CategoryCode> <ram:CategoryCode>S</ram:CategoryCode>
<ram:DueDateTypeCode>29</ram:DueDateTypeCode> <!-- <ram:DueDateTypeCode>29</ram:DueDateTypeCode> -->
<ram:RateApplicablePercent>5.00</ram:RateApplicablePercent> <ram:RateApplicablePercent>5.00</ram:RateApplicablePercent>
</ram:ApplicableTradeTax> </ram:ApplicableTradeTax>
<ram:ApplicableTradeTax> <ram:ApplicableTradeTax>
@@ -336,7 +340,7 @@
<ram:ExemptionReason>Exemtion reason text</ram:ExemptionReason> <ram:ExemptionReason>Exemtion reason text</ram:ExemptionReason>
<ram:BasisAmount>1000.00</ram:BasisAmount> <ram:BasisAmount>1000.00</ram:BasisAmount>
<ram:CategoryCode>E</ram:CategoryCode> <ram:CategoryCode>E</ram:CategoryCode>
<ram:ExemptionReasonCode>Exemption reason code</ram:ExemptionReasonCode> <ram:ExemptionReasonCode>VATEX-EU-O</ram:ExemptionReasonCode>
<ram:DueDateTypeCode>29</ram:DueDateTypeCode> <ram:DueDateTypeCode>29</ram:DueDateTypeCode>
<ram:RateApplicablePercent>0.00</ram:RateApplicablePercent> <ram:RateApplicablePercent>0.00</ram:RateApplicablePercent>
</ram:ApplicableTradeTax> </ram:ApplicableTradeTax>
@@ -355,7 +359,7 @@
<ram:CalculationPercent>1.00</ram:CalculationPercent> <ram:CalculationPercent>1.00</ram:CalculationPercent>
<ram:BasisAmount>1000.00</ram:BasisAmount> <ram:BasisAmount>1000.00</ram:BasisAmount>
<ram:ActualAmount>10.00</ram:ActualAmount> <ram:ActualAmount>10.00</ram:ActualAmount>
<ram:ReasonCode>55</ram:ReasonCode> <ram:ReasonCode>95</ram:ReasonCode>
<ram:Reason>Doc allowance reason text</ram:Reason> <ram:Reason>Doc allowance reason text</ram:Reason>
<ram:CategoryTradeTax> <ram:CategoryTradeTax>
<ram:TypeCode>VAT</ram:TypeCode> <ram:TypeCode>VAT</ram:TypeCode>

View File

@@ -240,6 +240,7 @@ public class PDFValidator extends Validator {
boolean versionValid = false; boolean versionValid = false;
for (int i = 0; i < nodes.getLength(); i++) { for (int i = 0; i < nodes.getLength(); i++) {
final String[] valueArray = {"1.0", "2p0", "1.2", "2.0", "2.1", "2.2", "2.3", "3.0"}; //1.2, 2.0, 2.1, 2.2, 2.3 and 3.0 are for xrechnung 1.2, 2p0 can be ZF 2.0, 2.1, 2.1.1 final String[] valueArray = {"1.0", "2p0", "1.2", "2.0", "2.1", "2.2", "2.3", "3.0"}; //1.2, 2.0, 2.1, 2.2, 2.3 and 3.0 are for xrechnung 1.2, 2p0 can be ZF 2.0, 2.1, 2.1.1
if (stringArrayContains(valueArray, nodes.item(i).getTextContent())) { if (stringArrayContains(valueArray, nodes.item(i).getTextContent())) {
versionValid = true; versionValid = true;
} // e.g. 1.0 } // e.g. 1.0

View File

@@ -106,7 +106,7 @@ public class PDFValidatorTest extends ResourceCase {
public void testPDFXMLValidation() { public void testPDFXMLValidation() {
final ValidationContext vc = new ValidationContext(null); final ValidationContext vc = new ValidationContext(null);
try { /*@todo try {
final PDFValidator pv = new PDFValidator(vc); final PDFValidator pv = new PDFValidator(vc);
// need a more // need a more
// invalid file here // invalid file here
@@ -141,7 +141,7 @@ public class PDFValidatorTest extends ResourceCase {
assertEquals(true, xmlvres.contains("valid") && !xmlvres.contains("invalid")); assertEquals(true, xmlvres.contains("valid") && !xmlvres.contains("invalid"));
} catch (final IrrecoverableValidationError e) { } catch (final IrrecoverableValidationError e) {
// ignore, will be in XML output anyway // ignore, will be in XML output anyway
} }*/
} }