diff --git a/History.md b/History.md
index ccc1d053..cbfe26eb 100644
--- a/History.md
+++ b/History.md
@@ -1,23 +1,21 @@
-595
-591
-577
-575
-573
-601
-
2.15.1
=======
+2024-12-07
- #566 Failed to parse PDF - Could not reproduce the invoice
-? be able to access ID in error message
- closes #579 prepaidamount is only read in UBL
-- #581 parse lineTotalAmount
-- also parse TaxBasisAmount
+- #581 parse lineTotalAmount, TaxBasisAmount, duePayableAmount
- #576 read lineid, #578 set lineid
-- log error ids
+- log error ids, be able to access them from context
- #503 import more ubl
- allow jackson to run over more classes, e.g., DirectDebit, bean contructor for direct debit
-- allow json includedNotes
-- support importing duePayableAmount
+- allow json in document includedNotes and
+- #591 Import IncludedNotes also on item level
+- #595 Treat all fatal XR schematron rules as errors, not as warnings.
+- #577 dom4j: exclude all (optional) dependencies to avoid potential conflicts.
+- #575 Fix compilation problems.
+- #573 Feature/category code
+- #601 exceptions in metrics
+- #606 also parse BT-25
2.15.0
diff --git a/Mustang-CLI/pom.xml b/Mustang-CLI/pom.xml
index 249b47c1..89e9f5fc 100644
--- a/Mustang-CLI/pom.xml
+++ b/Mustang-CLI/pom.xml
@@ -3,7 +3,7 @@
org.mustangproject
core
- 2.15.1-SNAPSHOT
+ 2.15.2-SNAPSHOT
4.0.0
org.mustangproject
@@ -12,7 +12,7 @@
should also work for XRechnung/CII.
jar
- 2.15.1-SNAPSHOT
+ 2.15.2-SNAPSHOT
UTF-8
11
@@ -23,7 +23,7 @@
org.mustangproject
validator
- 2.15.1-SNAPSHOT
+ 2.15.2-SNAPSHOT
diff --git a/library/pom.xml b/library/pom.xml
index b9e92200..2c6a1284 100644
--- a/library/pom.xml
+++ b/library/pom.xml
@@ -3,13 +3,13 @@
org.mustangproject
core
- 2.15.1-SNAPSHOT
+ 2.15.2-SNAPSHOT
4.0.0
org.mustangproject
library
- 2.15.1-SNAPSHOT
+ 2.15.2-SNAPSHOT
jar
Library to write, read and validate e-invoices (Factur-X, ZUGFeRD, Order-X, XRechnung/CII)
FOSS Java library to read, write and validate european electronic invoices and orders in the UN/CEFACT
diff --git a/library/src/main/java/org/mustangproject/DesignatedProductClassification.java b/library/src/main/java/org/mustangproject/DesignatedProductClassification.java
index 1917568d..ffa35c17 100644
--- a/library/src/main/java/org/mustangproject/DesignatedProductClassification.java
+++ b/library/src/main/java/org/mustangproject/DesignatedProductClassification.java
@@ -31,7 +31,7 @@ import org.mustangproject.ZUGFeRD.IDesignatedProductClassification;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class DesignatedProductClassification implements IDesignatedProductClassification {
- private final ClassCode classCode;
+ private ClassCode classCode;
private String className;
/**
@@ -54,6 +54,13 @@ public class DesignatedProductClassification implements IDesignatedProductClassi
this(classCode, null);
}
+ /**
+ * Bean constructor for schemed product descriptor
+ */
+ public DesignatedProductClassification() {
+ classCode = null;// we need this constructor for jackson, i.e. to be able to JSON
+ }
+
@Override
public ClassCode getClassCode() {
return classCode;
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java
index abff63bb..ffcfaac1 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java
@@ -808,6 +808,7 @@ public class ZUGFeRDInvoiceImporter {
} else {
zpp.setDespatchAdviceReferencedDocumentID(extractString("//*[local-name()=\"DespatchDocumentReference\"]/*[local-name()=\"ID\"]"));
}
+ zpp.setInvoiceReferencedDocumentID(extractString("//*[local-name()=\"InvoiceReferencedDocument\"]/*[local-name()=\"IssuerAssignedID\"]|//*[local-name()=\"BillingReference\"]/*[local-name()=\"InvoiceDocumentReference\"]/*[local-name()=\"ID\"]"));
zpp.setOwnOrganisationName(extractString("//*[local-name()=\"SellerTradeParty\"]/*[local-name()=\"Name\"]|//*[local-name()=\"AccountingSupplierParty\"]/*[local-name()=\"Party\"]/*[local-name()=\"PartyName\"]").trim());
@@ -929,6 +930,7 @@ public class ZUGFeRDInvoiceImporter {
}
+
TransactionCalculator tc = new TransactionCalculator(zpp);
String expectedStringTotalGross = tc.getGrandTotal()
.subtract(Objects.requireNonNullElse(zpp.getTotalPrepaidAmount(), BigDecimal.ZERO)).toPlainString();
diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java
index 6ac57f72..d435e58e 100644
--- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java
+++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java
@@ -539,6 +539,7 @@ public class ZF2PushTest extends TestCase {
.addAllowance(new Allowance(new BigDecimal(0.2)).setReason("discount").setTaxPercent(new BigDecimal(16)))
.addCashDiscount(new CashDiscount(new BigDecimal(2), 14))
.setDeliveryDate(sdf.parse("2020-11-02")).setNumber(number).setVATDueDateTypeCode(EventTimeCodeTypeConstants.PAYMENT_DATE)
+ .setInvoiceReferencedDocumentID("abc123")
);
} catch (ParseException e) {
e.printStackTrace();
@@ -582,6 +583,7 @@ public class ZF2PushTest extends TestCase {
try {
Invoice i = zii.extractInvoice();
+ assertEquals("abc123", i.getInvoiceReferencedDocumentID());
assertEquals("4304171000002", i.getRecipient().getGlobalID());
assertEquals("2001015001325", i.getZFItems()[0].getProduct().getGlobalID());
assertEquals(orgID, i.getSender().getID());
diff --git a/pom.xml b/pom.xml
index 7d58b324..a66ce931 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
4.0.0
org.mustangproject
core
- 2.15.1-SNAPSHOT
+ 2.15.2-SNAPSHOT
pom
Mustang
diff --git a/validator/pom.xml b/validator/pom.xml
index 36ef5550..8fe7e62c 100644
--- a/validator/pom.xml
+++ b/validator/pom.xml
@@ -3,7 +3,7 @@
org.mustangproject
core
- 2.15.1-SNAPSHOT
+ 2.15.2-SNAPSHOT
4.0.0
org.mustangproject
@@ -11,7 +11,7 @@
Library to validate e-invoices (ZUGFeRD, Factur-X and Xrechnung)
jar
- 2.15.1-SNAPSHOT
+ 2.15.2-SNAPSHOT
@@ -38,7 +38,7 @@
${project.groupId}
library
- 2.15.1-SNAPSHOT
+ 2.15.2-SNAPSHOT
org.dom4j
diff --git a/validator/src/main/java/org/mustangproject/validator/PDFValidator.java b/validator/src/main/java/org/mustangproject/validator/PDFValidator.java
index f967eb15..a73e1aa6 100644
--- a/validator/src/main/java/org/mustangproject/validator/PDFValidator.java
+++ b/validator/src/main/java/org/mustangproject/validator/PDFValidator.java
@@ -267,6 +267,7 @@ public class PDFValidator extends Validator {
final byte[] pdfMachineSignature = "pdfMachine from Broadgun Software".getBytes(StandardCharsets.UTF_8);
final byte[] ghostscriptSignature = "%%Invocation:".getBytes(StandardCharsets.UTF_8);
final byte[] cibpdfbrewerSignature = "CIB pdf brewer".getBytes(StandardCharsets.UTF_8);
+ final byte[] lexofficeSignature = "lexoffice".getBytes(StandardCharsets.UTF_8);
if (ByteArraySearcher.contains(fileContents, symtraxSignature)) {
Signature = "Symtrax";
@@ -284,6 +285,8 @@ public class PDFValidator extends Validator {
Signature = "Ghostscript";
} else if (ByteArraySearcher.contains(fileContents, cibpdfbrewerSignature)) {
Signature = "CIB pdf brewer";
+ } else if (ByteArraySearcher.contains(fileContents, lexofficeSignature)) {
+ Signature = "Lexware office";
}
context.setSignature(Signature);
diff --git a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java
index 336701b3..77061ab9 100644
--- a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java
+++ b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java
@@ -1,4 +1,5 @@
package org.mustangproject.validator;
+
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
@@ -69,15 +70,13 @@ public class XMLValidator extends Validator {
final ValidationResultItem vri = new ValidationResultItem(ESeverity.exception, e.getMessage()).setSection(9)
.setPart(EPart.fx);
try (final StringWriter sw = new StringWriter();
- final PrintWriter pw = new PrintWriter(sw))
- {
- e.printStackTrace(pw);
- vri.setStacktrace(sw.toString());
- context.addResultItem(vri);
+ final PrintWriter pw = new PrintWriter(sw)) {
+ e.printStackTrace(pw);
+ vri.setStacktrace(sw.toString());
+ context.addResultItem(vri);
+ } catch (IOException ex) {
+ throw new UncheckedIOException(ex);
}
- catch (IOException ex) {
- throw new UncheckedIOException (ex);
- }
}
}
@@ -125,9 +124,9 @@ public class XMLValidator extends Validator {
if (zfXML.isEmpty()) {
final ValidationResultItem res = new ValidationResultItem(ESeverity.exception,
- "XML data not found in " + filename
- + ": did you specify a pdf or xml file and does the xml file contain an embedded XML file?")
- .setSection(3);
+ "XML data not found in " + filename
+ + ": did you specify a pdf or xml file and does the xml file contain an embedded XML file?")
+ .setSection(3);
context.addResultItem(res);
} else {
@@ -170,7 +169,7 @@ public class XMLValidator extends Validator {
// Create XPath object
final XPath xpath = xpathFactory.newXPath();
final XPathExpression expr = xpath.compile(
- "(//*[local-name()=\"GuidelineSpecifiedDocumentContextParameter\"]/*[local-name()=\"ID\"])/text()|//*[local-name()=\"CustomizationID\"]/text()");
+ "(//*[local-name()=\"GuidelineSpecifiedDocumentContextParameter\"]/*[local-name()=\"ID\"])/text()|//*[local-name()=\"CustomizationID\"]/text()");
// evaluate expression result on XML document
ndList = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
@@ -191,7 +190,7 @@ public class XMLValidator extends Validator {
boolean isExtended = false;
boolean isXRechnung = false;
String currentZFVersionDir = "ZF_232";
- int mainSchematronSectionErrorTypeCode=4;
+ int mainSchematronSectionErrorTypeCode = 4;
String xsltFilename = null;
// urn:ferd:CrossIndustryDocument:invoice:1p0:extended,
// urn:ferd:CrossIndustryDocument:invoice:1p0:comfort,
@@ -224,7 +223,7 @@ public class XMLValidator extends Validator {
isBasic = false;// basicwl also contains the string basic...
}
isEN16931 = matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017:compliant:factur-x.eu:1p0:en16931")
- || matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017");
+ || matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017");
isExtended = context.getProfile().contains("extended");
isXRechnung = context.getProfile().contains("xrechnung");
@@ -269,16 +268,16 @@ public class XMLValidator extends Validator {
// saxon java net.sf.saxon.Transform -o tcdl2.0.tsdtf.sch.tmp.xsl -s
// tcdl2.0.tsdtf.sch iso_svrl.xsl
- } else if (root.getLocalName().equalsIgnoreCase("Invoice") || root.getLocalName().equalsIgnoreCase("CreditNote") ) {
+ } else if (root.getLocalName().equalsIgnoreCase("Invoice") || root.getLocalName().equalsIgnoreCase("CreditNote")) {
context.setGeneration("2");
context.setFormat("UBL");
isXRechnung = context.getProfile().contains("xrechnung");
// UBL
LOGGER.debug("UBL");
- validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "UBL_21/maindoc/UBL-"+root.getLocalName()+"-2.1.xsd", 18, EPart.fx);
+ validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "UBL_21/maindoc/UBL-" + root.getLocalName() + "-2.1.xsd", 18, EPart.fx);
xsltFilename = "/xslt/en16931schematron/EN16931-UBL-validation.xslt";
- mainSchematronSectionErrorTypeCode=24;
+ mainSchematronSectionErrorTypeCode = 24;
if (isXRechnung) {
validateSchematron(zfXML, xsltFilename, 24, ESeverity.error);
@@ -287,14 +286,14 @@ public class XMLValidator extends Validator {
XRechnung is a EN16931 subset so the validation vis a vis FACTUR-X_EN16931.xslt=schematron also has to pass
* */
//validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "ZF_211/EN16931/FACTUR-X_EN16931.xsd", 18, EPart.fx);
- String xrVersion=context.getProfile().substring(context.getProfile().length()-3).replace(".","");
- if (!xrVersion.equals("12")&&!xrVersion.equals("20")&&!xrVersion.equals("21")&&!xrVersion.equals("22")&&!xrVersion.equals("23")&&!xrVersion.equals("30")) {
+ String xrVersion = context.getProfile().substring(context.getProfile().length() - 3).replace(".", "");
+ if (!xrVersion.equals("12") && !xrVersion.equals("20") && !xrVersion.equals("21") && !xrVersion.equals("22") && !xrVersion.equals("23") && !xrVersion.equals("30")) {
throw new Exception("Unsupported XR version");
}
- LOGGER.debug("is XRechnung v"+xrVersion);
- xsltFilename = "/xslt/XR_"+xrVersion+"/XRechnung-UBL-validation.xslt";
+ LOGGER.debug("is XRechnung v" + xrVersion);
+ xsltFilename = "/xslt/XR_" + xrVersion + "/XRechnung-UBL-validation.xslt";
XrechnungSeverity = ESeverity.error;
- mainSchematronSectionErrorTypeCode=27;
+ mainSchematronSectionErrorTypeCode = 27;
}
@@ -302,58 +301,58 @@ public class XMLValidator extends Validator {
context.setGeneration("1");
//
if ((!matchesURI(context.getProfile(), "urn:ferd:CrossIndustryDocument:invoice:1p0:basic"))
- && (!matchesURI(context.getProfile(), "urn:ferd:CrossIndustryDocument:invoice:1p0:comfort"))
- && (!matchesURI(context.getProfile(), "urn:ferd:CrossIndustryDocument:invoice:1p0:extended"))) {
+ && (!matchesURI(context.getProfile(), "urn:ferd:CrossIndustryDocument:invoice:1p0:comfort"))
+ && (!matchesURI(context.getProfile(), "urn:ferd:CrossIndustryDocument:invoice:1p0:extended"))) {
context.addResultItem(new ValidationResultItem(ESeverity.error, "Unsupported profile type")
- .setSection(25).setPart(EPart.fx));
+ .setSection(25).setPart(EPart.fx));
}
validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "ZF_10/ZUGFeRD1p0.xsd", 18, EPart.fx);
xsltFilename = "/xslt/ZUGFeRD_1p0.xslt";
} else { // unknown document root
context.addResultItem(new ValidationResultItem(ESeverity.fatal, "Unsupported root element")
- .setSection(3).setPart(EPart.fx));
+ .setSection(3).setPart(EPart.fx));
}
if (context.getFormat().equals("CII")) {
if (context.getGeneration().equals("2")) {
if ((!matchesURI(context.getProfile(), "urn:factur-x.eu:1p0:minimum"))
- && (!matchesURI(context.getProfile(), "urn:zugferd.de:2p0:minimum"))
- && (!matchesURI(context.getProfile(), "urn:factur-x.eu:1p0:basicwl"))
- && (!matchesURI(context.getProfile(), "urn:zugferd.de:2p0:basicwl"))
- && (!matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:basic"))
- && (!matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017#compliant#urn:zugferd.de:2p0:basic"))
- && (!matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017"))
- && (!matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended"))
- && (!matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017#conformant#urn:zugferd.de:2p0:extended"))) {
+ && (!matchesURI(context.getProfile(), "urn:zugferd.de:2p0:minimum"))
+ && (!matchesURI(context.getProfile(), "urn:factur-x.eu:1p0:basicwl"))
+ && (!matchesURI(context.getProfile(), "urn:zugferd.de:2p0:basicwl"))
+ && (!matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:basic"))
+ && (!matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017#compliant#urn:zugferd.de:2p0:basic"))
+ && (!matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017"))
+ && (!matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended"))
+ && (!matchesURI(context.getProfile(), "urn:cen.eu:en16931:2017#conformant#urn:zugferd.de:2p0:extended"))) {
context.addResultItem(
- new ValidationResultItem(ESeverity.error, "Unsupported profile type " + context.getProfile())
- .setSection(25).setPart(EPart.fx));
+ new ValidationResultItem(ESeverity.error, "Unsupported profile type " + context.getProfile())
+ .setSection(25).setPart(EPart.fx));
}
} else /** v1 */ {
if (isOrderX) {
//order-x 1.0
if ((!matchesURI(context.getProfile(), "urn:order-x.eu:1p0:basic"))
- && (!matchesURI(context.getProfile(), "urn:order-x.eu:1p0:comfort"))
- && (!matchesURI(context.getProfile(), "urn:order-x.eu:1p0:extended"))) {
+ && (!matchesURI(context.getProfile(), "urn:order-x.eu:1p0:comfort"))
+ && (!matchesURI(context.getProfile(), "urn:order-x.eu:1p0:extended"))) {
//zf 1.0
context.addResultItem(new ValidationResultItem(ESeverity.error, "Unsupported profile type")
- .setSection(25).setPart(EPart.fx));
+ .setSection(25).setPart(EPart.fx));
}
} else if ((!matchesURI(context.getProfile(), "urn:ferd:CrossIndustryDocument:invoice:1p0:basic"))
- && (!matchesURI(context.getProfile(), "urn:ferd:CrossIndustryDocument:invoice:1p0:comfort"))
- && (!matchesURI(context.getProfile(), "urn:ferd:CrossIndustryDocument:invoice:1p0:extended"))) {
+ && (!matchesURI(context.getProfile(), "urn:ferd:CrossIndustryDocument:invoice:1p0:comfort"))
+ && (!matchesURI(context.getProfile(), "urn:ferd:CrossIndustryDocument:invoice:1p0:extended"))) {
//zf 1.0
context.addResultItem(new ValidationResultItem(ESeverity.error, "Unsupported profile type")
- .setSection(25).setPart(EPart.fx));
+ .setSection(25).setPart(EPart.fx));
}
}
}
- if (xsltFilename!=null) {
+ if (xsltFilename != null) {
// main schematron validation
validateSchematron(zfXML, xsltFilename, mainSchematronSectionErrorTypeCode, ESeverity.error);
@@ -362,7 +361,7 @@ public class XMLValidator extends Validator {
if (context.getFormat().equals("CII")) {
if (context.getGeneration().equals("2")
- && (isBasic || isEN16931 || isXRechnung)) {
+ && (isBasic || isEN16931 || isXRechnung)) {
//additionally validate against CEN
validateSchematron(zfXML, "/xslt/en16931schematron/EN16931-CII-validation.xslt", 24, ESeverity.error);
if (!disableNotices || XrechnungSeverity != ESeverity.notice) {
@@ -376,7 +375,7 @@ public class XMLValidator extends Validator {
throw er;
} catch (final Exception e) {
final ValidationResultItem vri = new ValidationResultItem(ESeverity.exception, e.getMessage()).setSection(22)
- .setPart(EPart.fx);
+ .setPart(EPart.fx);
final StringWriter sw = new StringWriter();
final PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
@@ -388,8 +387,8 @@ public class XMLValidator extends Validator {
final long endTime = Calendar.getInstance().getTimeInMillis();
context.addCustomXML("" + ((context.getGeneration() != null) ? context.getGeneration() : "invalid")
- + "" + ((context.getProfile() != null) ? context.getProfile() : "invalid") +
- "" + firedRules + "" + failedRules + "" + "" + (endTime - startXMLTime) + "");
+ + "" + ((context.getProfile() != null) ? context.getProfile() : "invalid") +
+ "" + firedRules + "" + failedRules + "" + "" + (endTime - startXMLTime) + "");
}
@@ -434,7 +433,7 @@ public class XMLValidator extends Validator {
final SchematronOutputType sout;
try {
sout = aResSCH
- .applySchematronValidationToSVRL(new StreamSource(new StringReader(xml)));
+ .applySchematronValidationToSVRL(new StreamSource(new StringReader(xml)));
} catch (final Exception e) {
throw new IrrecoverableValidationError(e.getMessage());
}
@@ -471,7 +470,7 @@ public class XMLValidator extends Validator {
if (defaultSeverity == ESeverity.notice) {
severity = defaultSeverity;
} else if (currentFailNode.getAttributes().getNamedItem("flag") != null
- && currentFailNode.getAttributes().getNamedItem("flag").getNodeValue().equals("warning")) {
+ && currentFailNode.getAttributes().getNamedItem("flag").getNodeValue().equals("warning")) {
// the XR issues warnings with flag=warning
severity = ESeverity.warning;
} else {
@@ -493,8 +492,8 @@ public class XMLValidator extends Validator {
LOGGER.info("FailedAssert ", thisFailText);
context.addResultItem(new ValidationResultItem(severity, thisFailText + thisFailIDStr + " from " + xsltFilename + ")")
- .setLocation(thisFailLocation).setCriterion(thisFailTest).setSection(section).setID(thisFailID)
- .setPart(EPart.fx));
+ .setLocation(thisFailLocation).setCriterion(thisFailTest).setSection(section).setID(thisFailID)
+ .setPart(EPart.fx));
failedRules++;
}
@@ -525,7 +524,7 @@ public class XMLValidator extends Validator {
if (firedRules == 0) {
context.addResultItem(new ValidationResultItem(ESeverity.error, "No rules matched, XML to minimal?").setSection(26)
- .setPart(EPart.fx));
+ .setPart(EPart.fx));
}
// for (String currentString : sout.getText()) {