From ce0941848d600aeca3f53933d557b7517ca6688b Mon Sep 17 00:00:00 2001 From: langfr Date: Tue, 24 Jun 2025 15:22:45 +0100 Subject: [PATCH 01/30] =?UTF-8?q?Return=20Account=20Holder=C2=B4s=20name?= =?UTF-8?q?=20in=20prefrence=20over=20Seller=C2=B4s=20Name.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java | 10 ++++++++++ .../java/org/mustangproject/ZUGFeRD/ZF2PushTest.java | 6 +++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java index cbf08b7b..5afa5a18 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java @@ -351,6 +351,16 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { public String getHolder() { + if (importedInvoice!=null && importedInvoice.getTradeSettlement()!=null) { + for (IZUGFeRDTradeSettlement settlement : importedInvoice.getTradeSettlement()) { + if (settlement instanceof IZUGFeRDTradeSettlementPayment) { + String s = ((IZUGFeRDTradeSettlementPayment) settlement).getAccountName(); + if ( s != null ) { + return s; + } + } + } + } return extractString("//*[local-name() = 'SellerTradeParty']/*[local-name() = 'Name']"); } diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java index 9c60b75b..75f516f6 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java @@ -114,8 +114,8 @@ public class ZF2PushTest extends TestCase { ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_PDF); assertTrue(zi.getUTF8().contains("DE88200800000970375700")); //the iban assertTrue(zi.getUTF8().contains("Max Mustermann")); //account holder - assertTrue(zi.getUTF8().contains("DueDateDateTime")); //account holder - assertTrue(zi.getUTF8().contains("20201212")); //account holder + assertTrue(zi.getUTF8().contains("DueDateDateTime")); + assertTrue(zi.getUTF8().contains("20201212")); assertTrue(zi.getUTF8().contains(" Date: Thu, 26 Jun 2025 14:16:36 +0200 Subject: [PATCH 02/30] erster versuch --- .../Exceptions/ArithmeticException.java | 16 ++ .../Exceptions/ArithmetricException.java | 1 + .../validator/XMLValidator.java | 35 ++++ .../validator/XMLValidatorTest.java | 26 +++ .../test/resources/invalidArithmetrics.xml | 186 ++++++++++++++++++ 5 files changed, 264 insertions(+) create mode 100644 library/src/main/java/org/mustangproject/Exceptions/ArithmeticException.java create mode 100644 validator/src/test/resources/invalidArithmetrics.xml diff --git a/library/src/main/java/org/mustangproject/Exceptions/ArithmeticException.java b/library/src/main/java/org/mustangproject/Exceptions/ArithmeticException.java new file mode 100644 index 00000000..f2341e1a --- /dev/null +++ b/library/src/main/java/org/mustangproject/Exceptions/ArithmeticException.java @@ -0,0 +1,16 @@ +package org.mustangproject.Exceptions; + +import java.text.ParseException; + +/*** + * will be thrown if an invoice cant be reproduced numerically + */ +public class ArithmeticException extends ArithmetricException { + public ArithmeticException() { + this(""); + } + + public ArithmeticException(String details) { +// super("Could not reproduce the invoice. " + details, 0); + } +} diff --git a/library/src/main/java/org/mustangproject/Exceptions/ArithmetricException.java b/library/src/main/java/org/mustangproject/Exceptions/ArithmetricException.java index e5b4071d..16af335b 100644 --- a/library/src/main/java/org/mustangproject/Exceptions/ArithmetricException.java +++ b/library/src/main/java/org/mustangproject/Exceptions/ArithmetricException.java @@ -4,6 +4,7 @@ import java.text.ParseException; /*** * will be thrown if an invoice cant be reproduced numerically + * (deprecated, because of typo) */ public class ArithmetricException extends ParseException { public ArithmetricException() { diff --git a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java index 6f364751..c69d085b 100644 --- a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java @@ -8,6 +8,7 @@ import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; +import java.text.ParseException; import java.util.Calendar; import javax.xml.XMLConstants; @@ -20,7 +21,12 @@ import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; +import org.mustangproject.CalculatedInvoice; +import org.mustangproject.Exceptions.ArithmetricException; import org.mustangproject.XMLTools; +import org.mustangproject.ZUGFeRD.TransactionCalculator; +import org.mustangproject.ZUGFeRD.ZUGFeRDImporter; +import org.mustangproject.ZUGFeRD.ZUGFeRDInvoiceImporter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -387,6 +393,7 @@ public class XMLValidator extends Validator { } } } + checkArithmetrics(context); } catch (final IrrecoverableValidationError er) { @@ -410,6 +417,34 @@ public class XMLValidator extends Validator { } + private void checkArithmetrics(ValidationContext context) { + ZUGFeRDInvoiceImporter zi=new ZUGFeRDInvoiceImporter(); + try { + zi.setRawXML(zfXML.getBytes()); + CalculatedInvoice ci=new CalculatedInvoice(); + zi.extractInto(ci); + TransactionCalculator tc=new TransactionCalculator(ci); + if (tc.getGrandTotal().compareTo(ci.getGrandTotal())!=0) { + + } + + } catch ( ArithmetricException e) { + try { + context.addResultItem(new ValidationResultItem(ESeverity.warning, "Can not arithmetrically reproduce the invoice.").setSection(10)); + + } catch (IrrecoverableValidationError ie) { + LOGGER.error(ie.getMessage(), ie); + } + } catch ( IOException e) { + LOGGER.error(e.getMessage(), e); + } catch (XPathExpressionException e) { + LOGGER.error(e.getMessage(), e); + } catch (ParseException e) { + LOGGER.error(e.getMessage(), e); + } + + } + public void validateXR(String xml, ESeverity errorImpact) throws IrrecoverableValidationError { //Guideline ID=urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_1.2 or diff --git a/validator/src/test/java/org/mustangproject/validator/XMLValidatorTest.java b/validator/src/test/java/org/mustangproject/validator/XMLValidatorTest.java index 2fda10ef..0a7032af 100644 --- a/validator/src/test/java/org/mustangproject/validator/XMLValidatorTest.java +++ b/validator/src/test/java/org/mustangproject/validator/XMLValidatorTest.java @@ -8,6 +8,8 @@ import org.xmlunit.builder.Input; import org.xmlunit.xpath.JAXPXPathEngine; import org.xmlunit.xpath.XPathEngine; +import static org.xmlunit.assertj.XmlAssert.assertThat; + public class XMLValidatorTest extends ResourceCase { public void testZF2XMLValidation() { @@ -282,6 +284,30 @@ public class XMLValidatorTest extends ResourceCase { } + public void testArithmetrics() { + final ValidationContext ctx = new ValidationContext(null); + final XMLValidator xv = new XMLValidator(ctx); + final XPathEngine xpath = new JAXPXPathEngine(); + + File tempFile = getResourceAsFile("invalidArithmetrics.xml"); + try { + xv.setFilename(tempFile.getAbsolutePath()); + xv.validate(); + + String s="" + xv.getXMLResult() + ""; + Source source = Input.fromString(s).build(); + String content = xpath.evaluate("/validation/summary/@status", source); + assertEquals("invalid", content); + assertThat(s).valueByXPath("count(//warning)") + .asInt() + .isEqualTo(1); + + } catch (final IrrecoverableValidationError e) { + // ignore, will be in XML output anyway + } + + } + public void testXRValidationUBL() { ValidationContext ctx = new ValidationContext(null); XMLValidator xv = new XMLValidator(ctx); diff --git a/validator/src/test/resources/invalidArithmetrics.xml b/validator/src/test/resources/invalidArithmetrics.xml new file mode 100644 index 00000000..e32358f0 --- /dev/null +++ b/validator/src/test/resources/invalidArithmetrics.xml @@ -0,0 +1,186 @@ + + + + + urn:cen.eu:en16931:2017 + + + + RE-20171118/506 + 380 + 20171118 + + + + + 1 + + + Künstlerische Gestaltung (Stunde): Einer Beispielrechnung + + + + + 160.0000 + 2.0000 + + + 160.0000 + 2.0000 + + + + 2.0000 + + + + VAT + S + 7.00 + + + 160.00 + + + + + + 2 + + + Luftballon: Bunt, ca. 500ml + + + + + 0.7900 + 1.0000 + + + 0.7900 + 1.0000 + + + + 400.0000 + + + + VAT + S + 19.00 + + + 316.00 + + + + + + 3 + + + Heiße Luft pro Liter + + + + + 0.1000 + 1.0000 + + + 0.1000 + 1.0000 + + + + 200.0000 + + + + VAT + S + 19.00 + + + 20.00 + + + + + + Bei Spiel GmbH + + 12345 + Ecke 12 + Stadthausen + DE + + + 22/815/0815/4 + + + DE136695976 + + + + Theodor Est + + 88802 + Bahnstr. 42 + Spielkreis + DE + + + DE999999999 + + + + + + 20171117 + + + + RE-20171118/506 + EUR + + 42 + Überweisung + + DE88 2008 0000 0970 3757 00 + + + COBADEFFXXX + + + + 11.20 + VAT + 160.00 + S + 7.00 + + + 63.84 + VAT + 336.00 + S + 19.00 + + + Zahlbar ohne Abzug bis 09.12.2017 + 20171209 + + + 496.00 + 0.00 + 0.00 + 496.00 + 75.04 + 571.04 + 571.05 + + + + From 16a839b50679eb542849ca3224f930ed13b89572 Mon Sep 17 00:00:00 2001 From: langfr Date: Thu, 26 Jun 2025 21:29:29 +0100 Subject: [PATCH 03/30] Remove System.out.println from XRTest.java. --- library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java index e7a2a951..3a211610 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java @@ -189,8 +189,7 @@ public class XRTest extends TestCase { zf2p.setProfile(Profiles.getByName("XRechnung")); zf2p.generateXML(i); - final String xmlGen = new String(zf2p.getXML()); - System.out.println(xmlGen); + final Document doc = DocumentBuilderFactory.newInstance() .newDocumentBuilder() .parse(new ByteArrayInputStream(zf2p.getXML())); From 8ce3d21eaefc639c5ca3f64651e2be845aa101de Mon Sep 17 00:00:00 2001 From: jstaerk Date: Mon, 30 Jun 2025 10:53:52 +0200 Subject: [PATCH 04/30] working on #878 --- .../ZUGFeRD/XRechnungImporter.java | 14 +++++------- .../ZUGFeRD/ZUGFeRDImporter.java | 7 +++++- .../ZUGFeRD/ZUGFeRDInvoiceImporter.java | 22 +++++++++++++------ .../ZUGFeRD/DeSerializationTest.java | 2 +- .../validator/XMLValidator.java | 10 ++------- .../validator/XMLValidatorTest.java | 2 +- .../validator/ZUGFeRDValidatorTest.java | 2 +- 7 files changed, 32 insertions(+), 27 deletions(-) diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/XRechnungImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/XRechnungImporter.java index dc00e2bb..d9662240 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/XRechnungImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/XRechnungImporter.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Paths; +import java.text.ParseException; import org.mustangproject.XMLTools; import org.slf4j.Logger; @@ -18,7 +19,7 @@ public class XRechnungImporter extends ZUGFeRDImporter { try { setRawXML(rawXml); containsMeta = true; - } catch (final IOException e) { + } catch (final IOException | ParseException e) { LOGGER.error ("Failed to set raw XML", e); throw new ZUGFeRDExportException(e); } @@ -30,24 +31,21 @@ public class XRechnungImporter extends ZUGFeRDImporter { try { setRawXML(Files.readAllBytes(Paths.get(filename))); containsMeta = true; - } catch (final IOException e) { - LOGGER.error ("Failed to set raw XML", e); + } catch (final IOException | ParseException e) { + LOGGER.error ("Failed to set raw XML", e); throw new ZUGFeRDExportException(e); } } public XRechnungImporter(InputStream fileinput) { super(); - try { setRawXML(XMLTools.getBytesFromStream(fileinput)); containsMeta = true; - } catch (final IOException e) { - LOGGER.error ("Failed to set raw XML", e); + } catch (final IOException | ParseException e) { + LOGGER.error ("Failed to set raw XML", e); throw new ZUGFeRDExportException(e); } - - } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java index cbf08b7b..af03cfa7 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java @@ -14,6 +14,7 @@ package org.mustangproject.ZUGFeRD; * @author jstaerk */ import java.io.*; +import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; @@ -452,7 +453,11 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { * @throws IOException if raw can not be set */ public void setMeta(String meta) throws IOException { - setRawXML(meta.getBytes()); + try { + setRawXML(meta.getBytes()); + } catch (ParseException e) { + LOGGER.error("Failed to parse", e); + } } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index 8b4ab8d7..a8ec32ed 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -174,7 +174,12 @@ public class ZUGFeRDInvoiceImporter { } else { // no PDF probably XML containsMeta = true; - setRawXML(XMLTools.getBytesFromStream(pdfStream)); + try { + setRawXML(XMLTools.getBytesFromStream(pdfStream)); + } catch(ParseException e) { + LOGGER.error("Failed to parse PDF", e); + } + } } @@ -219,8 +224,11 @@ public class ZUGFeRDInvoiceImporter { // ByteArrayOutputStream(); // FileOutputStream fos = new FileOutputStream(file); - setRawXML(embeddedFile.toByteArray()); - + try { + setRawXML(embeddedFile.toByteArray()); + } catch (ParseException e) { + LOGGER.error("Failed to parse XML", e); + } // fos.write(embeddedFile.getByteArray()); // fos.close(); } @@ -237,7 +245,7 @@ public class ZUGFeRDInvoiceImporter { * @param doParse automatically parse input for zugferdImporter (not ZUGFeRDInvoiceImporter) * @throws IOException if parsing xml throws it (unlikely its string based) */ - public void setRawXML(byte[] rawXML, boolean doParse) throws IOException { + public void setRawXML(byte[] rawXML, boolean doParse) throws IOException, ParseException { this.containsMeta = true; this.rawXML = rawXML; this.version = null; @@ -245,7 +253,7 @@ public class ZUGFeRDInvoiceImporter { try { setDocument(); - } catch (ParserConfigurationException | SAXException | ParseException e) { + } catch (ParserConfigurationException | SAXException e) { LOGGER.error("Failed to parse XML", e); throw new ZUGFeRDExportException(e); } @@ -257,7 +265,7 @@ public class ZUGFeRDInvoiceImporter { * @param rawXML the cii(?) as a string * @throws IOException if parsing xml throws it (unlikely its string based) */ - public void setRawXML(byte[] rawXML) throws IOException { + public void setRawXML(byte[] rawXML) throws IOException, ParseException { setRawXML(rawXML, true); } @@ -1205,7 +1213,7 @@ public class ZUGFeRDInvoiceImporter { * sets the XML for the importer to parse * @param XML the UBL or CII */ - public void fromXML(String XML) { + public void fromXML(String XML) throws ParseException{ try { containsMeta = true; setRawXML(XML.getBytes(StandardCharsets.UTF_8)); diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java index fd0e7b97..561ee4e3 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java @@ -79,7 +79,7 @@ public class DeSerializationTest extends ResourceCase { try { zii.fromXML(new String(Files.readAllBytes(inputCII.toPath()), StandardCharsets.UTF_8)); - } catch (IOException e) { + } catch (IOException | ParseException e) { hasExceptions = true; } diff --git a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java index c69d085b..3f737947 100644 --- a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java @@ -420,23 +420,17 @@ public class XMLValidator extends Validator { private void checkArithmetrics(ValidationContext context) { ZUGFeRDInvoiceImporter zi=new ZUGFeRDInvoiceImporter(); try { - zi.setRawXML(zfXML.getBytes()); + zi.fromXML(zfXML); CalculatedInvoice ci=new CalculatedInvoice(); zi.extractInto(ci); - TransactionCalculator tc=new TransactionCalculator(ci); - if (tc.getGrandTotal().compareTo(ci.getGrandTotal())!=0) { - - } } catch ( ArithmetricException e) { try { - context.addResultItem(new ValidationResultItem(ESeverity.warning, "Can not arithmetrically reproduce the invoice.").setSection(10)); + context.addResultItem(new ValidationResultItem(ESeverity.warning, "Arithmetical issue:"+e.getMessage()).setSection(10)); } catch (IrrecoverableValidationError ie) { LOGGER.error(ie.getMessage(), ie); } - } catch ( IOException e) { - LOGGER.error(e.getMessage(), e); } catch (XPathExpressionException e) { LOGGER.error(e.getMessage(), e); } catch (ParseException e) { diff --git a/validator/src/test/java/org/mustangproject/validator/XMLValidatorTest.java b/validator/src/test/java/org/mustangproject/validator/XMLValidatorTest.java index 0a7032af..e3636261 100644 --- a/validator/src/test/java/org/mustangproject/validator/XMLValidatorTest.java +++ b/validator/src/test/java/org/mustangproject/validator/XMLValidatorTest.java @@ -297,7 +297,7 @@ public class XMLValidatorTest extends ResourceCase { String s="" + xv.getXMLResult() + ""; Source source = Input.fromString(s).build(); String content = xpath.evaluate("/validation/summary/@status", source); - assertEquals("invalid", content); + assertEquals("valid", content); assertThat(s).valueByXPath("count(//warning)") .asInt() .isEqualTo(1); diff --git a/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java b/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java index 55affaa9..ec938490 100644 --- a/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java +++ b/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java @@ -219,7 +219,7 @@ public class ZUGFeRDValidatorTest extends ResourceCase { .isEqualTo(2); assertThat(res).valueByXPath("count(//warning)") .asInt() - .isEqualTo(2); + .isEqualTo(3); assertThat(res).valueByXPath("count(//notice)") .asInt() From 248372c98a39729e3dc0d239dda87348623d6e06 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Mon, 30 Jun 2025 11:39:43 +0200 Subject: [PATCH 05/30] closes #878 --- History.md | 2 ++ .../mustangproject/Exceptions/ArithmeticException.java | 5 +++-- .../mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java | 3 +-- .../java/org/mustangproject/validator/XMLValidator.java | 8 +++----- .../org/mustangproject/validator/XMLValidatorTest.java | 4 ++-- .../{invalidArithmetrics.xml => invalidArithmetics.xml} | 6 +++--- 6 files changed, 14 insertions(+), 14 deletions(-) rename validator/src/test/resources/{invalidArithmetrics.xml => invalidArithmetics.xml} (97%) diff --git a/History.md b/History.md index cdd3208c..d501ba69 100644 --- a/History.md +++ b/History.md @@ -12,6 +12,8 @@ - #855 - #874 - #876/830 +- #878 +- corrected typo ArithmetricException to ArithmeticException 2.17.0 ======= diff --git a/library/src/main/java/org/mustangproject/Exceptions/ArithmeticException.java b/library/src/main/java/org/mustangproject/Exceptions/ArithmeticException.java index f2341e1a..d0679073 100644 --- a/library/src/main/java/org/mustangproject/Exceptions/ArithmeticException.java +++ b/library/src/main/java/org/mustangproject/Exceptions/ArithmeticException.java @@ -4,13 +4,14 @@ import java.text.ParseException; /*** * will be thrown if an invoice cant be reproduced numerically + * ArithmetricException for backwards compatibility, was a spelling error */ public class ArithmeticException extends ArithmetricException { public ArithmeticException() { - this(""); + super(); } public ArithmeticException(String details) { -// super("Could not reproduce the invoice. " + details, 0); + super(details); } } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index a8ec32ed..4cb47c4a 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -10,7 +10,6 @@ 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.Exceptions.ArithmetricException; import org.mustangproject.Exceptions.StructureException; import org.mustangproject.util.NodeMap; import org.slf4j.Logger; @@ -1096,7 +1095,7 @@ public class ZUGFeRDInvoiceImporter { .collect(Collectors.joining(" + ")); } catch (Exception ignored) { } - throw new ArithmetricException("Payable total in XML is " + payableTotalFromXml + ", but calculated total is " + calculatedPayableTotal + moreDetails); + throw new ArithmeticException("Payable total in XML is " + payableTotalFromXml + ", but calculated total is " + calculatedPayableTotal + moreDetails); } } } diff --git a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java index 3f737947..ccd57b3a 100644 --- a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java @@ -24,8 +24,6 @@ import javax.xml.xpath.XPathFactory; import org.mustangproject.CalculatedInvoice; import org.mustangproject.Exceptions.ArithmetricException; import org.mustangproject.XMLTools; -import org.mustangproject.ZUGFeRD.TransactionCalculator; -import org.mustangproject.ZUGFeRD.ZUGFeRDImporter; import org.mustangproject.ZUGFeRD.ZUGFeRDInvoiceImporter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -393,7 +391,7 @@ public class XMLValidator extends Validator { } } } - checkArithmetrics(context); + checkArithmetics(context); } catch (final IrrecoverableValidationError er) { @@ -417,14 +415,14 @@ public class XMLValidator extends Validator { } - private void checkArithmetrics(ValidationContext context) { + private void checkArithmetics(ValidationContext context) { ZUGFeRDInvoiceImporter zi=new ZUGFeRDInvoiceImporter(); try { zi.fromXML(zfXML); CalculatedInvoice ci=new CalculatedInvoice(); zi.extractInto(ci); - } catch ( ArithmetricException e) { + } catch ( ArithmeticException e) { try { context.addResultItem(new ValidationResultItem(ESeverity.warning, "Arithmetical issue:"+e.getMessage()).setSection(10)); diff --git a/validator/src/test/java/org/mustangproject/validator/XMLValidatorTest.java b/validator/src/test/java/org/mustangproject/validator/XMLValidatorTest.java index e3636261..5bb8d1d9 100644 --- a/validator/src/test/java/org/mustangproject/validator/XMLValidatorTest.java +++ b/validator/src/test/java/org/mustangproject/validator/XMLValidatorTest.java @@ -284,12 +284,12 @@ public class XMLValidatorTest extends ResourceCase { } - public void testArithmetrics() { + public void testArithmetics() { final ValidationContext ctx = new ValidationContext(null); final XMLValidator xv = new XMLValidator(ctx); final XPathEngine xpath = new JAXPXPathEngine(); - File tempFile = getResourceAsFile("invalidArithmetrics.xml"); + File tempFile = getResourceAsFile("invalidArithmetics.xml"); try { xv.setFilename(tempFile.getAbsolutePath()); xv.validate(); diff --git a/validator/src/test/resources/invalidArithmetrics.xml b/validator/src/test/resources/invalidArithmetics.xml similarity index 97% rename from validator/src/test/resources/invalidArithmetrics.xml rename to validator/src/test/resources/invalidArithmetics.xml index e32358f0..3986c2d3 100644 --- a/validator/src/test/resources/invalidArithmetrics.xml +++ b/validator/src/test/resources/invalidArithmetics.xml @@ -22,11 +22,11 @@ 160.0000 - 2.0000 + 1.0000 160.0000 - 2.0000 + 1.0000 @@ -179,7 +179,7 @@ 496.00 75.04 571.04 - 571.05 + 571.04 From 7309821c7a8c0d4bcc59e20a91ad65885889083e Mon Sep 17 00:00:00 2001 From: Pawel Tschudnowsky Date: Thu, 3 Jul 2025 12:26:33 +0200 Subject: [PATCH 06/30] Fix Product Country of origin closes #861 --- .../main/java/org/mustangproject/Product.java | 10 ++-- .../ZUGFeRD/DeSerializationTest.java | 49 ++++++++++++------- .../test/resources/Extended_fremdwaehrung.xml | 4 ++ 3 files changed, 40 insertions(+), 23 deletions(-) diff --git a/library/src/main/java/org/mustangproject/Product.java b/library/src/main/java/org/mustangproject/Product.java index 4f0397ee..ef641dd0 100644 --- a/library/src/main/java/org/mustangproject/Product.java +++ b/library/src/main/java/org/mustangproject/Product.java @@ -1,13 +1,10 @@ package org.mustangproject; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.*; import org.mustangproject.ZUGFeRD.IDesignatedProductClassification; import org.mustangproject.ZUGFeRD.IZUGFeRDExportableProduct; import org.mustangproject.util.NodeMap; import org.w3c.dom.Node; -import org.w3c.dom.NodeList; import java.math.BigDecimal; import java.util.ArrayList; @@ -105,7 +102,10 @@ public class Product implements IZUGFeRDExportableProduct { classifications.add(new DesignatedProductClassification(classCode, className))); }); - nodeMap.getAsString("OriginTradeCounty").ifPresent(this::setCountryOfOrigin); + nodeMap.getAsNodeMap("OriginTradeCountry") + .flatMap(nodes -> nodes.getNode("ID")) + .map(Node::getTextContent) + .ifPresent(this::setCountryOfOrigin); } /*** diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java index bb41fb5a..c586df7a 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java @@ -21,6 +21,13 @@ */ package org.mustangproject.ZUGFeRD; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.FixMethodOrder; +import org.junit.runners.MethodSorters; +import org.mustangproject.*; +import org.mustangproject.ZUGFeRD.model.EventTimeCodeTypeConstants; + import java.io.File; import java.io.IOException; import java.math.BigDecimal; @@ -29,28 +36,11 @@ import java.nio.file.Files; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; -import java.util.TimeZone; import javax.xml.xpath.XPathExpressionException; -import org.junit.FixMethodOrder; -import org.junit.experimental.theories.FromDataPoints; -import org.junit.runners.MethodSorters; -import org.mustangproject.Allowance; -import org.mustangproject.BankDetails; -import org.mustangproject.CalculatedInvoice; -import org.mustangproject.CashDiscount; -import org.mustangproject.Charge; -import org.mustangproject.Contact; -import org.mustangproject.Invoice; -import org.mustangproject.Item; -import org.mustangproject.Product; -import org.mustangproject.SchemedID; -import org.mustangproject.TradeParty; -import org.mustangproject.ZUGFeRD.model.EventTimeCodeTypeConstants; +import static org.assertj.core.api.Assertions.assertThat; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class DeSerializationTest extends ResourceCase { @@ -71,6 +61,29 @@ public class DeSerializationTest extends ResourceCase { } + public void testProduct() throws IOException, XPathExpressionException, ParseException { + File inputCII = getResourceAsFile("Extended_fremdwaehrung.xml"); + var zii = new ZUGFeRDInvoiceImporter(); + zii.doIgnoreCalculationErrors(); + zii.fromXML(Files.readString(inputCII.toPath())); + var product = zii.extractInvoice() + .getZFItems()[0] + .getProduct(); + + assertThat(product.getCountryOfOrigin()).as("Product Country of origin") + .isEqualTo("DE"); + assertThat(product.getSellerAssignedID()).as("Product Seller assigned ID") + .isEqualTo("CO-123/V2A"); + assertThat(product.getBuyerAssignedID()).as("Product Buyer assigned ID") + .isEqualTo("Toolbox 0815"); + assertThat(product.getName()).as("Name") + .isEqualTo("Stahlcoil"); + + assertThat(product.getAttributes()).as("Product attributes") + .containsKey("LeoID") + .containsValue("704310.0105636504"); + } + public void testInvoiceLine() throws JsonProcessingException { File inputCII = getResourceAsFile("factur-x.xml"); boolean hasExceptions = false; diff --git a/library/src/test/resources/Extended_fremdwaehrung.xml b/library/src/test/resources/Extended_fremdwaehrung.xml index 4d10dea8..e5cb611d 100644 --- a/library/src/test/resources/Extended_fremdwaehrung.xml +++ b/library/src/test/resources/Extended_fremdwaehrung.xml @@ -131,6 +131,10 @@ costs, losses or damages could normally have been foreseen.--> CO-123/V2A Toolbox 0815 Stahlcoil + + LeoID + 704310.0105636504 + DE From c68c68679e10e70a9250cb846fbeec6262748500 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Fri, 4 Jul 2025 11:26:39 +0200 Subject: [PATCH 07/30] closes #885 --- History.md | 4 +++- library/pom.xml | 6 ++++++ .../main/java/org/mustangproject/Item.java | 7 +++++-- .../ZUGFeRD/ZF2ZInvoiceImporterTest.java | 19 +++++++++++++++++-- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/History.md b/History.md index d501ba69..6124fe76 100644 --- a/History.md +++ b/History.md @@ -13,7 +13,9 @@ - #874 - #876/830 - #878 -- corrected typo ArithmetricException to ArithmeticException +- corrected typo ArithmetricException to ArithmeticException +- 726 +- #885 2.17.0 ======= diff --git a/library/pom.xml b/library/pom.xml index ed2754e2..8630b9cc 100644 --- a/library/pom.xml +++ b/library/pom.xml @@ -137,6 +137,12 @@ + + org.skyscreamer + jsonassert + 2.0-rc1 + test + org.junit.jupiter junit-jupiter-api diff --git a/library/src/main/java/org/mustangproject/Item.java b/library/src/main/java/org/mustangproject/Item.java index 8e0cc8c7..59e604fc 100644 --- a/library/src/main/java/org/mustangproject/Item.java +++ b/library/src/main/java/org/mustangproject/Item.java @@ -1,5 +1,6 @@ package org.mustangproject; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import org.mustangproject.ZUGFeRD.IReferencedDocument; @@ -301,12 +302,14 @@ public class Item implements IZUGFeRDExportableItem { return this; } - @Override public IZUGFeRDAllowanceCharge[] getAllowances() { + @JsonIgnore + @Override public IZUGFeRDAllowanceCharge[] getAllowances() { // in JSON is already returned as itemAllowances (and only read from there) IZUGFeRDAllowanceCharge[] izac=new IZUGFeRDAllowanceCharge[Allowances.size()]; return Allowances.toArray(izac); } - @Override public IZUGFeRDAllowanceCharge[] getCharges() { + @JsonIgnore + @Override public IZUGFeRDAllowanceCharge[] getCharges() { // in JSON is already returned as itemAllowances (and only read from there) IZUGFeRDAllowanceCharge[] izac=new IZUGFeRDAllowanceCharge[Charges.size()]; return Charges.toArray(izac); } diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java index bad53f35..546cb582 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import org.mustangproject.*; +import org.skyscreamer.jsonassert.JSONAssert; import javax.xml.xpath.XPathExpressionException; import java.io.File; @@ -405,8 +406,7 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { ObjectMapper mapper = new ObjectMapper(); String jsonArray = mapper.writeValueAsString(i); - - // assertEquals("",jsonArray); + JSONAssert.assertEquals("{\"documentCode\":\"380\",\"number\":\"471102\",\"currency\":\"EUR\",\"paymentTermDescription\":\"Der Betrag in Höhe von EUR 529,87 wird am 20.03.2018 von Ihrem Konto per SEPA-Lastschrift eingezogen.\\n \",\"issueDate\":1520118000000,\"deliveryDate\":1520118000000,\"sender\":{\"name\":\"Lieferant GmbH\",\"zip\":\"80333\",\"street\":\"Lieferantenstraße 20\",\"location\":\"München\",\"country\":\"DE\",\"taxID\":\"201/113/40209\",\"vatID\":\"DE123456789\",\"debitDetails\":[{\"mandate\":\"REF A-123\",\"paymentMeansCode\":\"59\",\"paymentMeansInformation\":\"SEPA direct debit\",\"iban\":\"DE21860000000086001055\"}],\"vatid\":\"DE123456789\"},\"recipient\":{\"name\":\"Kunden AG Mitte\",\"zip\":\"69876\",\"street\":\"Kundenstraße 15\",\"location\":\"Frankfurt\",\"country\":\"DE\",\"bankDetails\":[{\"paymentMeansCode\":\"58\",\"paymentMeansInformation\":\"SEPA credit transfer\",\"iban\":\"DE21860000000086001055\"}]},\"totalPrepaidAmount\":0.00,\"creditorReferenceID\":\"DE98ZZZ09999999999\",\"valid\":false,\"zfitems\":[{\"price\":9.9000,\"quantity\":20.0000,\"basisQuantity\":1.0000,\"id\":\"1\",\"product\":{\"unit\":\"H87\",\"name\":\"Trennblätter A4\",\"taxCategoryCode\":\"S\",\"vatpercent\":19.00,\"reverseCharge\":false,\"intraCommunitySupply\":false},\"value\":9.9000},{\"price\":5.5000,\"quantity\":50.0000,\"basisQuantity\":1.0000,\"id\":\"2\",\"product\":{\"unit\":\"H87\",\"name\":\"Joghurt Banane\",\"taxCategoryCode\":\"S\",\"vatpercent\":7.00,\"reverseCharge\":false,\"intraCommunitySupply\":false},\"value\":5.5000}],\"tradeSettlement\":[{\"mandate\":\"REF A-123\",\"paymentMeansCode\":\"59\",\"paymentMeansInformation\":\"SEPA direct debit\",\"iban\":\"DE21860000000086001055\"}],\"ownTaxID\":\"201/113/40209\",\"ownZIP\":\"80333\",\"ownCountry\":\"DE\",\"ownVATID\":\"DE123456789\",\"ownLocation\":\"München\",\"ownStreet\":\"Lieferantenstraße 20\"}",jsonArray,false); } catch (IOException e) { fail("IOException not expected"); @@ -415,8 +415,23 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { } catch (ParseException e) { throw new RuntimeException(e); } + } + public void testImportAllowances() { + try { + ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter("./target/testout-ZF2PushItemChargesAllowances.pdf"); + Invoice i = zii.extractInvoice(); + ObjectMapper mapper = new ObjectMapper(); + String jsonArray = mapper.writeValueAsString(i); + JSONAssert.assertEquals("{ \"documentCode\": \"380\", \"number\": \"123\", \"currency\": \"EUR\", \"paymentTermDescription\": \"Please remit until 30.06.2025\", \"issueDate\": 1751234400000, \"dueDate\": 1751234400000, \"sender\": { \"name\": \"Test company\", \"zip\": \"55232\", \"street\": \"teststr\", \"location\": \"teststadt\", \"country\": \"DE\", \"taxID\": \"4711\", \"vatID\": \"DE0815\", \"vatid\": \"DE0815\" }, \"recipient\": { \"name\": \"Franz Müller\", \"zip\": \"55232\", \"street\": \"teststr.12\", \"location\": \"Entenhausen\", \"country\": \"DE\", \"contact\": { \"name\": \"contact testname\", \"phone\": \"123456\", \"email\": \"contact.testemail@example.org\", \"fax\": \"0911623562\" } }, \"totalPrepaidAmount\": 0.00, \"valid\": true, \"zfitems\": [ { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"1\", \"product\": { \"unit\": \"C62\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemAllowances\": [ { \"totalAmount\": 0.10, \"taxPercent\": 0, \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"2\", \"product\": { \"unit\": \"C62\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemAllowances\": [ { \"percent\": 50.00, \"totalAmount\": 1, \"basisAmount\": 3.00, \"taxPercent\": 0, \"reason\": \"In love with salesperson\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 2.0000, \"basisQuantity\": 1.0000, \"id\": \"3\", \"product\": { \"unit\": \"C62\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemCharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"AnotherReason\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"4\", \"product\": { \"unit\": \"C62\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemCharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"Yet another reason\", \"categoryCode\": \"S\" } ], \"itemAllowances\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"Something completely strange\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 } ], \"ownCountry\": \"DE\", \"zfcharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 19.00, \"reason\": \"AReason\", \"reasonCode\": \"ABK\", \"categoryCode\": \"S\" } ], \"ownVATID\": \"DE0815\", \"ownStreet\": \"teststr\", \"ownTaxID\": \"4711\", \"ownLocation\": \"teststadt\", \"ownZIP\": \"55232\"}",jsonArray,true); + } catch (IOException e) { + fail("IOException not expected"); + } catch (XPathExpressionException e) { + throw new RuntimeException(e); + } catch (ParseException e) { + throw new RuntimeException(e); + } } public void testImportMinimum() { From a9bce8669c6f2e1d41a43d96349a06e74c839d7e Mon Sep 17 00:00:00 2001 From: jstaerk Date: Mon, 7 Jul 2025 15:23:17 +0200 Subject: [PATCH 08/30] working on #887 --- .../java/org/mustangproject/Allowance.java | 2 +- .../ZUGFeRD/LineCalculator.java | 2 +- .../ZUGFeRD/CalculationTest.java | 66 ++++++++++++++++--- 3 files changed, 60 insertions(+), 10 deletions(-) diff --git a/library/src/main/java/org/mustangproject/Allowance.java b/library/src/main/java/org/mustangproject/Allowance.java index d8b5f3fe..cca2033a 100644 --- a/library/src/main/java/org/mustangproject/Allowance.java +++ b/library/src/main/java/org/mustangproject/Allowance.java @@ -38,7 +38,7 @@ public class Allowance extends Charge { if(totalAmount != null) { return totalAmount; } else if (percent!=null) { - BigDecimal singlePrice=currentItem.getValue().divide(BigDecimal.ONE.add(getPercent().divide(new BigDecimal(100))), 18, RoundingMode.HALF_UP); + BigDecimal singlePrice=currentItem.getValue().multiply(BigDecimal.ONE.subtract(getPercent().divide(new BigDecimal(100)))); // BigDecimal singlePrice=currentItem.getValue().multiply(BigDecimal.ONE.subtract(getPercent().divide(new BigDecimal(100)))); BigDecimal singlePriceDiff=currentItem.getValue().subtract(singlePrice); return singlePriceDiff; diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java index a0566f3e..a78a72df 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java @@ -94,7 +94,7 @@ public class LineCalculator { ? BigDecimal.ONE.setScale(4) : currentItem.getBasisQuantity(); itemTotalNetAmount = quantity.multiply(price).divide(basisQuantity, 18, RoundingMode.HALF_UP) - .add(lineCharge).subtract(lineAllowance).subtract(allowanceItemTotal).setScale(2, RoundingMode.HALF_UP); + .add(lineCharge).subtract(lineAllowance).subtract(allowanceItemTotal.setScale(2, RoundingMode.HALF_UP)).setScale(2, RoundingMode.HALF_UP); itemTotalVATAmount = itemTotalNetAmount.multiply(multiplicator);//.setScale(2, RoundingMode.HALF_UP); } diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java index 7fc94f5f..f7f1ce72 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java @@ -79,6 +79,56 @@ public class CalculationTest extends ResourceCase { assertEquals(valueOf(287.9408).stripTrailingZeros(), calculator.getItemTotalVATAmount().stripTrailingZeros()); } + @Test + public void testAllowanceAndChargeEx4() { + /** numbers from en16931 example 4 */ + SimpleDateFormat sqlDate = new SimpleDateFormat("yyyy-MM-dd"); + + Invoice invoice = new Invoice(); + invoice.setDocumentName("Rechnung"); + invoice.setNumber("777777"); + try { + invoice.setIssueDate(sqlDate.parse("2020-12-31")); + } catch (Exception e) { + LOGGER.error("Failed to set dates", e); + + } + + /* trade party (sender) */ + TradeParty sender = new TradeParty("Maier GmbH", "Musterweg 5", "11111", "Testung", "DE"); + sender.addVATID("DE2222222222"); + invoice.setSender(sender); + + /* trade party (recipient) */ + TradeParty recipient = new TradeParty("Teston GmbH" + " " + "Zentrale" + " " + "", "Testweg 5", "11111", "Testung", "DE"); + invoice.setRecipient(recipient); + + /* item */ + Product product; + Item item; + + product = new Product("Pens", "", "H84", new BigDecimal(25)); + product.addAllowance(new Allowance(new BigDecimal(1))); + item = new Item(product, new BigDecimal("9.50"), new BigDecimal(25)); + item.addCharge(new Charge(new BigDecimal(10)).setReasonCode("ZZZ").setReason("Zuschlag")); + LineCalculator lc = new LineCalculator(item); + assertEquals(new BigDecimal("222.50"), lc.getItemTotalNetAmount()); + invoice.addItem(item); + product = new Product("Paper", "", "H84", new BigDecimal(25)); + item = new Item(product, new BigDecimal("4.50"), new BigDecimal(15)); + item.addAllowance(new Allowance().setPercent(new BigDecimal(5)).setReasonCode("ZZZ").setReason("Zuschlag")); + lc = new LineCalculator(item); + assertEquals(new BigDecimal("64.12"), lc.getItemTotalNetAmount()); + invoice.addItem(item); + invoice.addAllowance(new Allowance().setPercent(new BigDecimal(10)).setTaxPercent(new BigDecimal(25)).setReasonCode("ZZZ").setReason("Mengenrabatt")); + invoice.addCharge(new Charge(new BigDecimal(15)).setReasonCode("ZZZ").setReason("Frachtkosten")); + + TransactionCalculator calculator = new TransactionCalculator(invoice); + assertEquals(valueOf(286.62).stripTrailingZeros(), calculator.getTotal());// interestingly, EN16931-1 has 286.63 here? + assertEquals(valueOf(272.96).stripTrailingZeros(), calculator.getTaxBasis()); // and 272.97 here + assertEquals(valueOf(337.45).stripTrailingZeros(), calculator.getDuePayable()); // and 337.46 here??? + } + @Test public void testLineCalculatorForeignCurrencyExample() { /*** xml of official fx sample with allowances and charges @@ -88,19 +138,19 @@ public class CalculationTest extends ResourceCase { */ File inputCII = getResourceAsFile("Extended_fremdwaehrung.xml"); - ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter(); - Invoice invoice=null; + ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(); + Invoice invoice = null; zii.doIgnoreCalculationErrors(); - boolean hasExceptions=false; + boolean hasExceptions = false; try { zii.setInputStream(new FileInputStream(inputCII)); - invoice=zii.extractInvoice(); + invoice = zii.extractInvoice(); } catch (XPathExpressionException | ParseException e) { // handle Exceptions - hasExceptions=true; + hasExceptions = true; } catch (FileNotFoundException e) { - hasExceptions=true; + hasExceptions = true; } assertFalse(hasExceptions); // Reading ZUGFeRD @@ -266,7 +316,7 @@ public class CalculationTest extends ResourceCase { invoice.addItem(item); TransactionCalculator calculator = new TransactionCalculator(invoice); - assertEquals(new BigDecimal(5), calculator.getGrandTotal().stripTrailingZeros()); + assertEquals(new BigDecimal("4.95"), calculator.getGrandTotal().stripTrailingZeros()); } public void testSimpleItemTotalAllowance() { @@ -314,7 +364,7 @@ public class CalculationTest extends ResourceCase { /** * LineCalculator should not throw an exception when calculating a non-terminating decimal expansion - * */ + */ @Test public void testNonTerminatingDecimalExpansion() { final Product product = new Product(); From dad26e190075f8d6b1b7550822fcb4600d28c134 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Wed, 9 Jul 2025 10:23:35 +0200 Subject: [PATCH 09/30] added two new tests --- .../main/java/org/mustangproject/Charge.java | 8 +-- .../ZUGFeRD/CalculationTest.java | 57 +++++++++++++++++-- .../ZUGFeRD/DeSerializationTest.java | 2 +- .../mustangproject/ZUGFeRD/ZF2PushTest.java | 10 ++-- .../ZUGFeRD/ZF2ZInvoiceImporterTest.java | 2 +- 5 files changed, 64 insertions(+), 15 deletions(-) diff --git a/library/src/main/java/org/mustangproject/Charge.java b/library/src/main/java/org/mustangproject/Charge.java index c906a704..f1a12bf6 100644 --- a/library/src/main/java/org/mustangproject/Charge.java +++ b/library/src/main/java/org/mustangproject/Charge.java @@ -145,10 +145,10 @@ public class Charge implements IZUGFeRDAllowanceCharge { if(totalAmount != null) { return totalAmount; } else if (percent!=null) { - BigDecimal singlePrice=currentItem.getValue().divide(BigDecimal.ONE.add(getPercent().divide(new BigDecimal(100))), 18, RoundingMode.HALF_UP); -// BigDecimal singlePrice=currentItem.getValue().multiply(BigDecimal.ONE.subtract(getPercent().divide(new BigDecimal(100)))); - BigDecimal singlePriceDiff=currentItem.getValue().add(singlePrice); - return singlePriceDiff; +// BigDecimal singlePrice=currentItem.getValue().divide(BigDecimal.ONE.add(getPercent().divide(new BigDecimal(100))), 18, RoundingMode.HALF_UP); + BigDecimal singlePrice=currentItem.getValue().multiply(BigDecimal.ONE.subtract(getPercent().divide(new BigDecimal(100)))); +// BigDecimal singlePriceDiff=currentItem.getValue().add(singlePrice); + return singlePrice; } else { throw new RuntimeException("percent must be set"); } diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java index f7f1ce72..99c73b64 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java @@ -12,12 +12,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.xml.xpath.XPathExpressionException; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; +import java.io.*; import java.math.BigDecimal; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.Date; /*** * tests the linecalculator and transactioncalculator classes @@ -274,7 +273,7 @@ public class CalculationTest extends ResourceCase { invoice.addAllowance(new Allowance().setPercent(total_discount_percent).setTaxPercent(sales_tax_percent1).setReasonCode("95").setReason("Rabatte")); } TransactionCalculator calculator = new TransactionCalculator(invoice); - assertEquals(valueOf(307.18).stripTrailingZeros(), calculator.getGrandTotal().stripTrailingZeros()); + assertEquals(valueOf(306.38).stripTrailingZeros(), calculator.getGrandTotal().stripTrailingZeros()); } public void testSimpleItemPercentAllowance() { @@ -319,6 +318,56 @@ public class CalculationTest extends ResourceCase { assertEquals(new BigDecimal("4.95"), calculator.getGrandTotal().stripTrailingZeros()); } + public void testSimpleDocumentPercentCharge() { + + String orgname = "Test company"; + String number = "123"; + String priceStr = "3.00"; + BigDecimal price = new BigDecimal(priceStr); + + + // similar, but slightly less complicated to whats later testted in testRelativeChargesAllowancesExport + Invoice i = new Invoice().setCurrency("CHF").setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()) + .setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID("4711").addVATID("DE0815")) + .setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE")) + .setNumber(number) + .addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(19)), price, new BigDecimal(1.0))) + .addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(19)), price, new BigDecimal(1.0))) + .addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(19)), price, new BigDecimal(1.0))) + .addCharge(new Charge().setPercent(new BigDecimal(50)).setTaxPercent(new BigDecimal(19)).setReasonCode("ABK")); + // 9+50%=>13,50 expected net + // .addAllowance(new Allowance().setPercent(new BigDecimal(50)).setTaxPercent(new BigDecimal(19)).setReason("Mengenrabatt")) + TransactionCalculator tc = new TransactionCalculator(i); + assertEquals(new BigDecimal("13.50"), tc.getTaxBasis()); + + assertEquals(new BigDecimal("16.07"), tc.getDuePayable()); + } + + public void testSimpleDocumentPercentAllowance() { + + String orgname = "Test company"; + String number = "123"; + String priceStr = "3.00"; + BigDecimal price = new BigDecimal(priceStr); + + + // similar, but slightly less complicated to whats later testted in testRelativeChargesAllowancesExport + Invoice i = new Invoice().setCurrency("CHF").setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()) + .setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID("4711").addVATID("DE0815")) + .setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE")) + .setNumber(number) + .addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(19)), price, new BigDecimal(1.0))) + .addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(19)), price, new BigDecimal(1.0))) + .addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(19)), price, new BigDecimal(1.0))) + .addAllowance(new Allowance().setPercent(new BigDecimal(50)).setTaxPercent(new BigDecimal(19)).setReasonCode("ABK")); + // 9-50%=>4,50 expected net + // .addAllowance(new Allowance().setPercent(new BigDecimal(50)).setTaxPercent(new BigDecimal(19)).setReason("Mengenrabatt")) + TransactionCalculator tc = new TransactionCalculator(i); + assertEquals(new BigDecimal("4.50"), tc.getTaxBasis()); + + assertEquals(new BigDecimal("5.36"), tc.getDuePayable()); + } + public void testSimpleItemTotalAllowance() { /*** * a product with net 1 and qty 5 and absolute _item_ allowance of 1 should return 4 as line total, and grand total diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java index 561ee4e3..e527021e 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java @@ -395,7 +395,7 @@ public class DeSerializationTest extends ResourceCase { try { Invoice newInvoiceFromJSON = mapper.readValue(json, Invoice.class); TransactionCalculator tc=new TransactionCalculator(newInvoiceFromJSON); - assertEquals(new BigDecimal("18.92"),tc.getGrandTotal()); + assertEquals(new BigDecimal("18.33"),tc.getGrandTotal()); } catch (JsonProcessingException e) { throw new RuntimeException(e); diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java index fc000301..86b2f730 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java @@ -283,7 +283,7 @@ public class ZF2PushTest extends TestCase { assertTrue(zi.getUTF8().contains("ABK")); // Reading ZUGFeRD - assertEquals("18.92", zi.getAmount()); + assertEquals("18.33", zi.getAmount()); assertEquals(orgname, zi.getHolder()); assertEquals(number, zi.getForeignReference()); assertEquals(zi.getVersion(), 2); @@ -675,10 +675,10 @@ public class ZF2PushTest extends TestCase { .setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID("4711").addVATID("DE0815")) .setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE")) .setNumber(number) - .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, new BigDecimal(1.0))) - .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, new BigDecimal(1.0))) - .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, new BigDecimal(1.0)).addCharge(new Charge().setPercent(new BigDecimal(50)).setTaxPercent(new BigDecimal(19)).setReasonCode("ABK"))) - .addAllowance(new Allowance().setPercent(new BigDecimal(50)).setTaxPercent(new BigDecimal(19)).setReason("Mengenrabatt")) + .addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(19)), price, new BigDecimal(1.0))) + .addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(19)), price, new BigDecimal(1.0))) + .addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(19)), price, new BigDecimal(1.0))).addCharge(new Charge().setPercent(new BigDecimal(50)).setTaxPercent(new BigDecimal(19)).setReasonCode("ABK")) + .addAllowance(new Allowance().setPercent(new BigDecimal(50)).setTaxPercent(new BigDecimal(19)).setReason("Mengenrabatt")) ); String theXML = new String(ze.getProvider().getXML()); assertTrue(theXML.contains(" Date: Thu, 10 Jul 2025 11:42:47 +0200 Subject: [PATCH 10/30] correcting test to reproducible value (9*1.5/1.5*1.19) --- .../src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java index 86b2f730..a72a7953 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java @@ -691,7 +691,7 @@ public class ZF2PushTest extends TestCase { ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_RELATIVECHARGESALLOWANCESPDF); assertEquals("CHF", zi.getInvoiceCurrencyCode()); - assertEquals("11.10", zi.getAmount()); + assertEquals("10.71", zi.getAmount()); assertEquals(orgname, zi.getHolder()); assertEquals(number, zi.getForeignReference()); try { From 1a2ef1bec48f5a53d25f932a1d24acd8031cfa52 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Fri, 11 Jul 2025 15:01:08 +0200 Subject: [PATCH 11/30] correcting some tests --- .../main/java/org/mustangproject/Charge.java | 5 ++- .../ZUGFeRD/TransactionCalculator.java | 1 + .../ZUGFeRD/CalculationTest.java | 33 +------------------ .../ZUGFeRD/ZF2ZInvoiceImporterTest.java | 19 +++++++++-- 4 files changed, 21 insertions(+), 37 deletions(-) diff --git a/library/src/main/java/org/mustangproject/Charge.java b/library/src/main/java/org/mustangproject/Charge.java index f1a12bf6..3d647757 100644 --- a/library/src/main/java/org/mustangproject/Charge.java +++ b/library/src/main/java/org/mustangproject/Charge.java @@ -145,9 +145,8 @@ public class Charge implements IZUGFeRDAllowanceCharge { if(totalAmount != null) { return totalAmount; } else if (percent!=null) { -// BigDecimal singlePrice=currentItem.getValue().divide(BigDecimal.ONE.add(getPercent().divide(new BigDecimal(100))), 18, RoundingMode.HALF_UP); - BigDecimal singlePrice=currentItem.getValue().multiply(BigDecimal.ONE.subtract(getPercent().divide(new BigDecimal(100)))); -// BigDecimal singlePriceDiff=currentItem.getValue().add(singlePrice); + BigDecimal factor=getPercent().divide(new BigDecimal(100), 18, RoundingMode.HALF_UP); + BigDecimal singlePrice=currentItem.getValue().multiply(factor); return singlePrice; } else { throw new RuntimeException("percent must be set"); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java index fc15b318..edb94dd5 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java @@ -172,6 +172,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { * @return item sum +- charges/allowances */ public BigDecimal getTaxBasis() { + BigDecimal debug_1=getTotal(); return getTotal().add(getChargesForPercent(null).setScale(2, RoundingMode.HALF_UP)) .subtract(getAllowancesForPercent(null).setScale(2, RoundingMode.HALF_UP)) .setScale(2, RoundingMode.HALF_UP); diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java index 99c73b64..ebdcf643 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java @@ -217,54 +217,23 @@ public class CalculationTest extends ResourceCase { } invoice.addItem(item); - // reset values for additional charge and discount used for next lines - item_increase = BigDecimal.ZERO; - item_discount = BigDecimal.ZERO; product = new Product("BBB", "", "H84", sales_tax_percent1).setSellerAssignedID("2BBB"); item = new Item(product, new BigDecimal("5.750"), new BigDecimal(4.00)); - if (item_increase.compareTo(BigDecimal.ZERO) > 0) { - item.addCharge(new Charge().setPercent(item_increase).setTaxPercent(sales_tax_percent1).setReasonCode("ZZZ").setReason("Zuschlag")); - } - if (item_discount.compareTo(BigDecimal.ZERO) > 0) { - item.addAllowance(new Allowance().setPercent(item_discount).setTaxPercent(sales_tax_percent1).setReasonCode("95").setReason("Rabatt")); - } invoice.addItem(item); product = new Product("CCC", "", "H84", sales_tax_percent1).setSellerAssignedID("3CCC"); item = new Item(product, new BigDecimal("6.750"), new BigDecimal(3.00)); - if (item_increase.compareTo(BigDecimal.ZERO) > 0) { - item.addCharge(new Charge().setPercent(item_increase).setTaxPercent(sales_tax_percent1).setReasonCode("ZZZ").setReason("Zuschlag")); - } - if (item_discount.compareTo(BigDecimal.ZERO) > 0) { - item.addAllowance(new Allowance().setPercent(item_discount).setTaxPercent(sales_tax_percent1).setReasonCode("95").setReason("Rabatt")); - } invoice.addItem(item); product = new Product("DDD", "", "H84", sales_tax_percent1).setSellerAssignedID("4DDD"); item = new Item(product, new BigDecimal("7.750"), new BigDecimal(2.00)); - if (item_increase.compareTo(BigDecimal.ZERO) > 0) { - item.addCharge(new Charge().setPercent(item_increase).setTaxPercent(sales_tax_percent1).setReasonCode("ZZZ").setReason("Zuschlag")); - } - if (item_discount.compareTo(BigDecimal.ZERO) > 0) { - item.addAllowance(new Allowance().setPercent(item_discount).setTaxPercent(sales_tax_percent1).setReasonCode("95").setReason("Rabatt")); - } invoice.addItem(item); product = new Product("EEE", "", "H84", sales_tax_percent1).setSellerAssignedID("5EEE"); item = new Item(product, new BigDecimal("8.750"), new BigDecimal(1.00)); - if (item_increase.compareTo(BigDecimal.ZERO) > 0) { - item.addCharge(new Charge().setPercent(item_increase).setTaxPercent(sales_tax_percent1).setReasonCode("ZZZ").setReason("Zuschlag")); - } - if (item_discount.compareTo(BigDecimal.ZERO) > 0) { - item.addAllowance(new Allowance().setPercent(item_discount).setTaxPercent(sales_tax_percent1).setReasonCode("95").setReason("Rabatt")); - - } invoice.addItem(item); - // reset values for additional charge and discount used on invoice level - item_increase = BigDecimal.valueOf(3.50); - item_discount = BigDecimal.valueOf(10.00); if (total_increase_percent.compareTo(BigDecimal.ZERO) > 0) { invoice.addCharge(new Charge().setPercent(total_increase_percent).setTaxPercent(sales_tax_percent1).setReasonCode("ZZZ").setReason("Zuschläge")); @@ -273,7 +242,7 @@ public class CalculationTest extends ResourceCase { invoice.addAllowance(new Allowance().setPercent(total_discount_percent).setTaxPercent(sales_tax_percent1).setReasonCode("95").setReason("Rabatte")); } TransactionCalculator calculator = new TransactionCalculator(invoice); - assertEquals(valueOf(306.38).stripTrailingZeros(), calculator.getGrandTotal().stripTrailingZeros()); + assertEquals(valueOf(101.85).stripTrailingZeros(), calculator.getGrandTotal().stripTrailingZeros()); } public void testSimpleItemPercentAllowance() { diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java index 30c9b2a4..48ddea3a 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java @@ -39,6 +39,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.*; import java.util.Arrays; import java.util.Date; import java.util.List; @@ -416,7 +417,12 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { throw new RuntimeException(e); } } - + public static Date atStartOfDay(Date date) { + ZoneId tz=ZoneId.ofOffset("GMT", ZoneOffset.ofHours(+2)); + LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), tz); + LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN); + return Date.from(startOfDay.atZone(tz).toInstant()); + } public void testImportAllowances() { try { ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter("./target/testout-ZF2PushItemChargesAllowances.pdf"); @@ -424,7 +430,16 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { ObjectMapper mapper = new ObjectMapper(); String jsonArray = mapper.writeValueAsString(i); - JSONAssert.assertEquals("{ \"documentCode\": \"380\", \"number\": \"123\", \"currency\": \"EUR\", \"paymentTermDescription\": \"Please remit until 30.06.2025\", \"issueDate\": 1751234400000, \"dueDate\": 1751234400000, \"sender\": { \"name\": \"Test company\", \"zip\": \"55232\", \"street\": \"teststr\", \"location\": \"teststadt\", \"country\": \"DE\", \"taxID\": \"4711\", \"vatID\": \"DE0815\", \"vatid\": \"DE0815\" }, \"recipient\": { \"name\": \"Franz Müller\", \"zip\": \"55232\", \"street\": \"teststr.12\", \"location\": \"Entenhausen\", \"country\": \"DE\", \"contact\": { \"name\": \"contact testname\", \"phone\": \"123456\", \"email\": \"contact.testemail@example.org\", \"fax\": \"0911623562\" } }, \"totalPrepaidAmount\": 0.00, \"valid\": true, \"zfitems\": [ { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"1\", \"product\": { \"unit\": \"C62\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemAllowances\": [ { \"totalAmount\": 0.10, \"taxPercent\": 0, \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"2\", \"product\": { \"unit\": \"C62\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemAllowances\": [ { \"percent\": 50.00, \"totalAmount\": 1, \"basisAmount\": 3.00, \"taxPercent\": 0, \"reason\": \"In love with salesperson\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 2.0000, \"basisQuantity\": 1.0000, \"id\": \"3\", \"product\": { \"unit\": \"C62\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemCharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"AnotherReason\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"4\", \"product\": { \"unit\": \"C62\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemCharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"Yet another reason\", \"categoryCode\": \"S\" } ], \"itemAllowances\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"Something completely strange\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 } ], \"ownCountry\": \"DE\", \"zfcharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 19.00, \"reason\": \"AReason\", \"reasonCode\": \"ABK\", \"categoryCode\": \"S\" } ], \"ownVATID\": \"DE0815\", \"ownStreet\": \"teststr\", \"ownTaxID\": \"4711\", \"ownLocation\": \"teststadt\", \"ownZIP\": \"55232\"}",jsonArray,true); + SimpleDateFormat iso=new SimpleDateFormat("yyyy-MM-dd"); + SimpleDateFormat german=new SimpleDateFormat("dd.MM.yyyy"); + Date now=new Date(); + Date morning=atStartOfDay(now); + + String expectedDueDate= String.valueOf(morning.toInstant().getEpochSecond() *1000); + String expectedIssueDate= String.valueOf(morning.toInstant().getEpochSecond() *1000); + String expectedPaymentTermDesciption="Please remit until "+german.format(now); + + JSONAssert.assertEquals("{ \"documentCode\": \"380\", \"number\": \"123\", \"currency\": \"EUR\", \"paymentTermDescription\": \""+expectedPaymentTermDesciption+"\", \"issueDate\": "+expectedIssueDate+", \"dueDate\": "+expectedDueDate+", \"sender\": { \"name\": \"Test company\", \"zip\": \"55232\", \"street\": \"teststr\", \"location\": \"teststadt\", \"country\": \"DE\", \"taxID\": \"4711\", \"vatID\": \"DE0815\", \"vatid\": \"DE0815\" }, \"recipient\": { \"name\": \"Franz Müller\", \"zip\": \"55232\", \"street\": \"teststr.12\", \"location\": \"Entenhausen\", \"country\": \"DE\", \"contact\": { \"name\": \"contact testname\", \"phone\": \"123456\", \"email\": \"contact.testemail@example.org\", \"fax\": \"0911623562\" } }, \"totalPrepaidAmount\": 0.00, \"valid\": true, \"zfitems\": [ { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"1\", \"product\": { \"unit\": \"C62\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemAllowances\": [ { \"totalAmount\": 0.10, \"taxPercent\": 0, \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"2\", \"product\": { \"unit\": \"C62\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemAllowances\": [ { \"percent\": 50.00, \"totalAmount\": 1, \"basisAmount\": 3.00, \"taxPercent\": 0, \"reason\": \"In love with salesperson\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 2.0000, \"basisQuantity\": 1.0000, \"id\": \"3\", \"product\": { \"unit\": \"C62\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemCharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"AnotherReason\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"4\", \"product\": { \"unit\": \"C62\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemCharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"Yet another reason\", \"categoryCode\": \"S\" } ], \"itemAllowances\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"Something completely strange\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 } ], \"ownCountry\": \"DE\", \"zfcharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 19.00, \"reason\": \"AReason\", \"reasonCode\": \"ABK\", \"categoryCode\": \"S\" } ], \"ownVATID\": \"DE0815\", \"ownStreet\": \"teststr\", \"ownTaxID\": \"4711\", \"ownLocation\": \"teststadt\", \"ownZIP\": \"55232\"}",jsonArray,true); } catch (IOException e) { fail("IOException not expected"); } catch (XPathExpressionException e) { From 1f7bab70dc3f13c4d9eacac73ab83122d8f9f9dd Mon Sep 17 00:00:00 2001 From: jstaerk Date: Fri, 11 Jul 2025 15:18:37 +0200 Subject: [PATCH 12/30] fixed another test --- .../org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java index 48ddea3a..ec987111 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java @@ -439,7 +439,7 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { String expectedIssueDate= String.valueOf(morning.toInstant().getEpochSecond() *1000); String expectedPaymentTermDesciption="Please remit until "+german.format(now); - JSONAssert.assertEquals("{ \"documentCode\": \"380\", \"number\": \"123\", \"currency\": \"EUR\", \"paymentTermDescription\": \""+expectedPaymentTermDesciption+"\", \"issueDate\": "+expectedIssueDate+", \"dueDate\": "+expectedDueDate+", \"sender\": { \"name\": \"Test company\", \"zip\": \"55232\", \"street\": \"teststr\", \"location\": \"teststadt\", \"country\": \"DE\", \"taxID\": \"4711\", \"vatID\": \"DE0815\", \"vatid\": \"DE0815\" }, \"recipient\": { \"name\": \"Franz Müller\", \"zip\": \"55232\", \"street\": \"teststr.12\", \"location\": \"Entenhausen\", \"country\": \"DE\", \"contact\": { \"name\": \"contact testname\", \"phone\": \"123456\", \"email\": \"contact.testemail@example.org\", \"fax\": \"0911623562\" } }, \"totalPrepaidAmount\": 0.00, \"valid\": true, \"zfitems\": [ { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"1\", \"product\": { \"unit\": \"C62\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemAllowances\": [ { \"totalAmount\": 0.10, \"taxPercent\": 0, \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"2\", \"product\": { \"unit\": \"C62\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemAllowances\": [ { \"percent\": 50.00, \"totalAmount\": 1, \"basisAmount\": 3.00, \"taxPercent\": 0, \"reason\": \"In love with salesperson\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 2.0000, \"basisQuantity\": 1.0000, \"id\": \"3\", \"product\": { \"unit\": \"C62\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemCharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"AnotherReason\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"4\", \"product\": { \"unit\": \"C62\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemCharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"Yet another reason\", \"categoryCode\": \"S\" } ], \"itemAllowances\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"Something completely strange\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 } ], \"ownCountry\": \"DE\", \"zfcharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 19.00, \"reason\": \"AReason\", \"reasonCode\": \"ABK\", \"categoryCode\": \"S\" } ], \"ownVATID\": \"DE0815\", \"ownStreet\": \"teststr\", \"ownTaxID\": \"4711\", \"ownLocation\": \"teststadt\", \"ownZIP\": \"55232\"}",jsonArray,true); + JSONAssert.assertEquals("{ \"documentCode\": \"380\", \"number\": \"123\", \"currency\": \"EUR\", \"paymentTermDescription\": \""+expectedPaymentTermDesciption+"\", \"issueDate\": "+expectedIssueDate+", \"dueDate\": "+expectedDueDate+", \"sender\": { \"name\": \"Test company\", \"zip\": \"55232\", \"street\": \"teststr\", \"location\": \"teststadt\", \"country\": \"DE\", \"taxID\": \"4711\", \"vatID\": \"DE0815\", \"vatid\": \"DE0815\" }, \"recipient\": { \"name\": \"Franz Müller\", \"zip\": \"55232\", \"street\": \"teststr.12\", \"location\": \"Entenhausen\", \"country\": \"DE\", \"contact\": { \"name\": \"contact testname\", \"phone\": \"123456\", \"email\": \"contact.testemail@example.org\", \"fax\": \"0911623562\" } }, \"totalPrepaidAmount\": 0.00, \"valid\": true, \"zfitems\": [ { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"1\", \"product\": { \"unit\": \"C62\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemAllowances\": [ { \"totalAmount\": 0.10, \"taxPercent\": 0, \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"2\", \"product\": { \"unit\": \"C62\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemAllowances\": [ { \"percent\": 50.00, \"totalAmount\": 1.5, \"basisAmount\": 3.00, \"taxPercent\": 0, \"reason\": \"In love with salesperson\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 2.0000, \"basisQuantity\": 1.0000, \"id\": \"3\", \"product\": { \"unit\": \"C62\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemCharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"AnotherReason\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 }, { \"price\": 3.0000, \"quantity\": 1.0000, \"basisQuantity\": 1.0000, \"id\": \"4\", \"product\": { \"unit\": \"C62\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"vatpercent\": 19.00, \"reverseCharge\": false, \"intraCommunitySupply\": false }, \"itemCharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"Yet another reason\", \"categoryCode\": \"S\" } ], \"itemAllowances\": [ { \"totalAmount\": 1.00, \"taxPercent\": 0, \"reason\": \"Something completely strange\", \"categoryCode\": \"S\" } ], \"value\": 3.0000 } ], \"ownCountry\": \"DE\", \"zfcharges\": [ { \"totalAmount\": 1.00, \"taxPercent\": 19.00, \"reason\": \"AReason\", \"reasonCode\": \"ABK\", \"categoryCode\": \"S\" } ], \"ownVATID\": \"DE0815\", \"ownStreet\": \"teststr\", \"ownTaxID\": \"4711\", \"ownLocation\": \"teststadt\", \"ownZIP\": \"55232\"}",jsonArray,true); } catch (IOException e) { fail("IOException not expected"); } catch (XPathExpressionException e) { From 61bdf8b2b134dc1bd60483b923781460cee949f3 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Fri, 11 Jul 2025 15:30:13 +0200 Subject: [PATCH 13/30] closes #887 --- History.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/History.md b/History.md index 6124fe76..c9176fa7 100644 --- a/History.md +++ b/History.md @@ -1,21 +1,21 @@ - support parsing of BT-90 CreditorReferenceID -- #870 -- #871 -- #868 -- #729 -- #863 -- #731 -- #865 -- #849 -- #856 -- #850/#843 -- #855 -- #874 -- #876/830 -- #878 +- #871 schema validation does not ignore external entities +- #868 Fix wrong version in History.md +- #729 Updates about SpecifiedTradeSettlementHeaderMonetarySummation and SpecifiedTradeSettlementPaymentMeans +- #863 LineCalculator throws NPE if product is null (since 2.17.0) +- #731 Got a broken translation key when visualizing XML into PDF (xr:Business_process_type) +- #865 Add sevdesk signature to PDF creators +- #849 Ignore calculation errors when extracting xml from pdf +- #856 Read contact´s fax number. +- #850/#843 Correction for "Re-Initialize the HTML-template on language change +- #855 Suppress empty nodes in output XML +- #874 Skip PDNameTreeNodes if the names are null or empty +- #830 Invalid XML generated: Item vat-category-code summed up with other 0 percent category codes +- #878 report arithmetic issues in validation report - corrected typo ArithmetricException to ArithmeticException -- 726 -- #885 +- #726 Financial account information (IBAN) is lost when converting a cii invoice to ubl +- #885 JSON duplicates on item allowances/charges +- #887 incorrect percentual item allowances 2.17.0 ======= From 5b9bf652239b008c61968fe62f9b9c306ad6b2bd Mon Sep 17 00:00:00 2001 From: jstaerk Date: Mon, 14 Jul 2025 11:01:53 +0200 Subject: [PATCH 14/30] updated history --- History.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/History.md b/History.md index c9176fa7..8585c2f4 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,7 @@ +2.18.0 +======= +2025-07-14 + - support parsing of BT-90 CreditorReferenceID - #871 schema validation does not ignore external entities - #868 Fix wrong version in History.md From 930a2ba9615974d6e6fd7ab8980e40f6ec2fb671 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Mon, 14 Jul 2025 11:06:07 +0200 Subject: [PATCH 15/30] [maven-release-plugin] prepare release core-2.18.0 --- Mustang-CLI/pom.xml | 6 +++--- library/pom.xml | 6 +++--- pom.xml | 4 ++-- validator/pom.xml | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Mustang-CLI/pom.xml b/Mustang-CLI/pom.xml index 2922415d..8f971037 100644 --- a/Mustang-CLI/pom.xml +++ b/Mustang-CLI/pom.xml @@ -3,7 +3,7 @@ org.mustangproject core - 2.17.1-SNAPSHOT + 2.18.0 4.0.0 org.mustangproject @@ -12,7 +12,7 @@ should also work for XRechnung/CII. jar - 2.17.1-SNAPSHOT + 2.18.0 UTF-8 11 @@ -23,7 +23,7 @@ org.mustangproject validator - 2.17.1-SNAPSHOT + 2.18.0 diff --git a/library/pom.xml b/library/pom.xml index 8630b9cc..94987254 100644 --- a/library/pom.xml +++ b/library/pom.xml @@ -3,13 +3,13 @@ org.mustangproject core - 2.17.1-SNAPSHOT + 2.18.0 4.0.0 org.mustangproject library - 2.17.1-SNAPSHOT + 2.18.0 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 @@ -20,7 +20,7 @@ scm:git:https://github.com/ZUGFeRD/mustangproject.git scm:git:https://github.com/ZUGFeRD/mustangproject.git https://github.com/ZUGFeRD/mustangproject - core-2.3.2 + core-2.18.0 diff --git a/pom.xml b/pom.xml index 252cdccf..ce322aaa 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.mustangproject core - 2.17.1-SNAPSHOT pom + 2.18.0 pom Mustang @@ -19,7 +19,7 @@ scm:git:git://github.com/dexecutor/dependent-tasks-executor.git scm:git:git@github.com:dexecutor/dexecutor.git https://github.com/dexecutor/dependent-tasks-executor - core-2.3.2 + core-2.18.0 diff --git a/validator/pom.xml b/validator/pom.xml index 19cc2e97..db794ca0 100644 --- a/validator/pom.xml +++ b/validator/pom.xml @@ -3,7 +3,7 @@ org.mustangproject core - 2.17.1-SNAPSHOT + 2.18.0 4.0.0 org.mustangproject @@ -11,7 +11,7 @@ Library to validate e-invoices (ZUGFeRD, Factur-X and Xrechnung) jar - 2.17.1-SNAPSHOT + 2.18.0 @@ -38,7 +38,7 @@ ${project.groupId} library - 2.17.1-SNAPSHOT + 2.18.0 org.dom4j From a9ae47cb5f622746a9640ce864df8d04f32034b9 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Mon, 14 Jul 2025 11:06:08 +0200 Subject: [PATCH 16/30] [maven-release-plugin] prepare for next development iteration --- Mustang-CLI/pom.xml | 6 +++--- library/pom.xml | 6 +++--- pom.xml | 4 ++-- validator/pom.xml | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Mustang-CLI/pom.xml b/Mustang-CLI/pom.xml index 8f971037..3a2ae520 100644 --- a/Mustang-CLI/pom.xml +++ b/Mustang-CLI/pom.xml @@ -3,7 +3,7 @@ org.mustangproject core - 2.18.0 + 2.18.1-SNAPSHOT 4.0.0 org.mustangproject @@ -12,7 +12,7 @@ should also work for XRechnung/CII. jar - 2.18.0 + 2.18.1-SNAPSHOT UTF-8 11 @@ -23,7 +23,7 @@ org.mustangproject validator - 2.18.0 + 2.18.1-SNAPSHOT diff --git a/library/pom.xml b/library/pom.xml index 94987254..55bf65f5 100644 --- a/library/pom.xml +++ b/library/pom.xml @@ -3,13 +3,13 @@ org.mustangproject core - 2.18.0 + 2.18.1-SNAPSHOT 4.0.0 org.mustangproject library - 2.18.0 + 2.18.1-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 @@ -20,7 +20,7 @@ scm:git:https://github.com/ZUGFeRD/mustangproject.git scm:git:https://github.com/ZUGFeRD/mustangproject.git https://github.com/ZUGFeRD/mustangproject - core-2.18.0 + core-2.3.2 diff --git a/pom.xml b/pom.xml index ce322aaa..6ceee7c1 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.mustangproject core - 2.18.0 pom + 2.18.1-SNAPSHOT pom Mustang @@ -19,7 +19,7 @@ scm:git:git://github.com/dexecutor/dependent-tasks-executor.git scm:git:git@github.com:dexecutor/dexecutor.git https://github.com/dexecutor/dependent-tasks-executor - core-2.18.0 + core-2.3.2 diff --git a/validator/pom.xml b/validator/pom.xml index db794ca0..f5b5281a 100644 --- a/validator/pom.xml +++ b/validator/pom.xml @@ -3,7 +3,7 @@ org.mustangproject core - 2.18.0 + 2.18.1-SNAPSHOT 4.0.0 org.mustangproject @@ -11,7 +11,7 @@ Library to validate e-invoices (ZUGFeRD, Factur-X and Xrechnung) jar - 2.18.0 + 2.18.1-SNAPSHOT @@ -38,7 +38,7 @@ ${project.groupId} library - 2.18.0 + 2.18.1-SNAPSHOT org.dom4j From 1ed479c8ddf19897e74e00367e358e8f5251efee Mon Sep 17 00:00:00 2001 From: jstaerk Date: Mon, 14 Jul 2025 11:26:02 +0200 Subject: [PATCH 17/30] [maven-release-plugin] rollback the release of core-2.18.0 --- Mustang-CLI/pom.xml | 6 +++--- library/pom.xml | 4 ++-- pom.xml | 2 +- validator/pom.xml | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Mustang-CLI/pom.xml b/Mustang-CLI/pom.xml index 3a2ae520..2922415d 100644 --- a/Mustang-CLI/pom.xml +++ b/Mustang-CLI/pom.xml @@ -3,7 +3,7 @@ org.mustangproject core - 2.18.1-SNAPSHOT + 2.17.1-SNAPSHOT 4.0.0 org.mustangproject @@ -12,7 +12,7 @@ should also work for XRechnung/CII. jar - 2.18.1-SNAPSHOT + 2.17.1-SNAPSHOT UTF-8 11 @@ -23,7 +23,7 @@ org.mustangproject validator - 2.18.1-SNAPSHOT + 2.17.1-SNAPSHOT diff --git a/library/pom.xml b/library/pom.xml index 55bf65f5..8630b9cc 100644 --- a/library/pom.xml +++ b/library/pom.xml @@ -3,13 +3,13 @@ org.mustangproject core - 2.18.1-SNAPSHOT + 2.17.1-SNAPSHOT 4.0.0 org.mustangproject library - 2.18.1-SNAPSHOT + 2.17.1-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/pom.xml b/pom.xml index 6ceee7c1..252cdccf 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.mustangproject core - 2.18.1-SNAPSHOT pom + 2.17.1-SNAPSHOT pom Mustang diff --git a/validator/pom.xml b/validator/pom.xml index f5b5281a..19cc2e97 100644 --- a/validator/pom.xml +++ b/validator/pom.xml @@ -3,7 +3,7 @@ org.mustangproject core - 2.18.1-SNAPSHOT + 2.17.1-SNAPSHOT 4.0.0 org.mustangproject @@ -11,7 +11,7 @@ Library to validate e-invoices (ZUGFeRD, Factur-X and Xrechnung) jar - 2.18.1-SNAPSHOT + 2.17.1-SNAPSHOT @@ -38,7 +38,7 @@ ${project.groupId} library - 2.18.1-SNAPSHOT + 2.17.1-SNAPSHOT org.dom4j From fe215c1221b2a394b17fe4a32b4b40595c8ff2de Mon Sep 17 00:00:00 2001 From: jstaerk Date: Mon, 14 Jul 2025 11:27:00 +0200 Subject: [PATCH 18/30] updated maven central settings --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 252cdccf..11008f89 100644 --- a/pom.xml +++ b/pom.xml @@ -70,11 +70,11 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.13 + 1.7.0 true ossrh - https://s01.oss.sonatype.org/ + https://ossrh-staging-api.central.sonatype.com/ true From 6abfa5a6dd1c9eb5aae59c797879b1acf9f2110a Mon Sep 17 00:00:00 2001 From: jstaerk Date: Mon, 14 Jul 2025 11:30:06 +0200 Subject: [PATCH 19/30] [maven-release-plugin] prepare release core-2.18.0 --- Mustang-CLI/pom.xml | 6 +++--- library/pom.xml | 6 +++--- pom.xml | 4 ++-- validator/pom.xml | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Mustang-CLI/pom.xml b/Mustang-CLI/pom.xml index 2922415d..8f971037 100644 --- a/Mustang-CLI/pom.xml +++ b/Mustang-CLI/pom.xml @@ -3,7 +3,7 @@ org.mustangproject core - 2.17.1-SNAPSHOT + 2.18.0 4.0.0 org.mustangproject @@ -12,7 +12,7 @@ should also work for XRechnung/CII. jar - 2.17.1-SNAPSHOT + 2.18.0 UTF-8 11 @@ -23,7 +23,7 @@ org.mustangproject validator - 2.17.1-SNAPSHOT + 2.18.0 diff --git a/library/pom.xml b/library/pom.xml index 8630b9cc..94987254 100644 --- a/library/pom.xml +++ b/library/pom.xml @@ -3,13 +3,13 @@ org.mustangproject core - 2.17.1-SNAPSHOT + 2.18.0 4.0.0 org.mustangproject library - 2.17.1-SNAPSHOT + 2.18.0 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 @@ -20,7 +20,7 @@ scm:git:https://github.com/ZUGFeRD/mustangproject.git scm:git:https://github.com/ZUGFeRD/mustangproject.git https://github.com/ZUGFeRD/mustangproject - core-2.3.2 + core-2.18.0 diff --git a/pom.xml b/pom.xml index 11008f89..23280c49 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.mustangproject core - 2.17.1-SNAPSHOT pom + 2.18.0 pom Mustang @@ -19,7 +19,7 @@ scm:git:git://github.com/dexecutor/dependent-tasks-executor.git scm:git:git@github.com:dexecutor/dexecutor.git https://github.com/dexecutor/dependent-tasks-executor - core-2.3.2 + core-2.18.0 diff --git a/validator/pom.xml b/validator/pom.xml index 19cc2e97..db794ca0 100644 --- a/validator/pom.xml +++ b/validator/pom.xml @@ -3,7 +3,7 @@ org.mustangproject core - 2.17.1-SNAPSHOT + 2.18.0 4.0.0 org.mustangproject @@ -11,7 +11,7 @@ Library to validate e-invoices (ZUGFeRD, Factur-X and Xrechnung) jar - 2.17.1-SNAPSHOT + 2.18.0 @@ -38,7 +38,7 @@ ${project.groupId} library - 2.17.1-SNAPSHOT + 2.18.0 org.dom4j From b25daa4f6b148f571258da5d70131ffc554fbb08 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Mon, 14 Jul 2025 11:31:44 +0200 Subject: [PATCH 20/30] [maven-release-plugin] prepare for next development iteration --- Mustang-CLI/pom.xml | 6 +++--- library/pom.xml | 6 +++--- pom.xml | 4 ++-- validator/pom.xml | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Mustang-CLI/pom.xml b/Mustang-CLI/pom.xml index 8f971037..3a2ae520 100644 --- a/Mustang-CLI/pom.xml +++ b/Mustang-CLI/pom.xml @@ -3,7 +3,7 @@ org.mustangproject core - 2.18.0 + 2.18.1-SNAPSHOT 4.0.0 org.mustangproject @@ -12,7 +12,7 @@ should also work for XRechnung/CII. jar - 2.18.0 + 2.18.1-SNAPSHOT UTF-8 11 @@ -23,7 +23,7 @@ org.mustangproject validator - 2.18.0 + 2.18.1-SNAPSHOT diff --git a/library/pom.xml b/library/pom.xml index 94987254..55bf65f5 100644 --- a/library/pom.xml +++ b/library/pom.xml @@ -3,13 +3,13 @@ org.mustangproject core - 2.18.0 + 2.18.1-SNAPSHOT 4.0.0 org.mustangproject library - 2.18.0 + 2.18.1-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 @@ -20,7 +20,7 @@ scm:git:https://github.com/ZUGFeRD/mustangproject.git scm:git:https://github.com/ZUGFeRD/mustangproject.git https://github.com/ZUGFeRD/mustangproject - core-2.18.0 + core-2.3.2 diff --git a/pom.xml b/pom.xml index 23280c49..52b863b8 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.mustangproject core - 2.18.0 pom + 2.18.1-SNAPSHOT pom Mustang @@ -19,7 +19,7 @@ scm:git:git://github.com/dexecutor/dependent-tasks-executor.git scm:git:git@github.com:dexecutor/dexecutor.git https://github.com/dexecutor/dependent-tasks-executor - core-2.18.0 + core-2.3.2 diff --git a/validator/pom.xml b/validator/pom.xml index db794ca0..f5b5281a 100644 --- a/validator/pom.xml +++ b/validator/pom.xml @@ -3,7 +3,7 @@ org.mustangproject core - 2.18.0 + 2.18.1-SNAPSHOT 4.0.0 org.mustangproject @@ -11,7 +11,7 @@ Library to validate e-invoices (ZUGFeRD, Factur-X and Xrechnung) jar - 2.18.0 + 2.18.1-SNAPSHOT @@ -38,7 +38,7 @@ ${project.groupId} library - 2.18.0 + 2.18.1-SNAPSHOT org.dom4j From 216fa91355592984329f7c8f173bea28da279ca7 Mon Sep 17 00:00:00 2001 From: Daniel Luckas Date: Thu, 17 Jul 2025 10:14:34 +0200 Subject: [PATCH 21/30] Close resources after usage --- .../org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index 4cb47c4a..90baad58 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -126,8 +126,7 @@ public class ZUGFeRDInvoiceImporter { if (Arrays.equals(pad, pdfSignature)) { // we have a pdf - try { - PDDocument doc = Loader.loadPDF(IOUtils.toByteArray(pdfStream)); + try(PDDocument doc = Loader.loadPDF(IOUtils.toByteArray(pdfStream))) { // PDDocumentInformation info = doc.getDocumentInformation(); final PDDocumentNameDictionary names = new PDDocumentNameDictionary(doc.getDocumentCatalog()); //start From adab361477342b684963f186ed5c5d04f73874f3 Mon Sep 17 00:00:00 2001 From: Daniel Luckas Date: Thu, 17 Jul 2025 10:15:35 +0200 Subject: [PATCH 22/30] Enhance code quality --- .../org/mustangproject/commandline/Main.java | 14 +- .../commandline/ValidatorFileWalker.java | 4 +- .../main/java/org/mustangproject/Invoice.java | 10 +- .../main/java/org/mustangproject/Item.java | 37 ++- .../java/org/mustangproject/TradeParty.java | 2 +- .../ZUGFeRD/DAPullProvider.java | 4 +- .../ZUGFeRD/LineCalculator.java | 6 +- .../ZUGFeRD/OXPullProvider.java | 11 +- .../ZUGFeRD/TransactionCalculator.java | 12 +- .../ZUGFeRD/ZUGFeRD2PullProvider.java | 23 +- .../ZUGFeRD/ZUGFeRDExporterFromA3.java | 5 +- .../ZUGFeRD/ZUGFeRDInvoiceImporter.java | 69 +++--- .../validator/PDFValidator.java | 230 +++++++++--------- .../validator/ValidationContext.java | 31 ++- .../validator/XMLValidator.java | 5 +- 15 files changed, 227 insertions(+), 236 deletions(-) diff --git a/Mustang-CLI/src/main/java/org/mustangproject/commandline/Main.java b/Mustang-CLI/src/main/java/org/mustangproject/commandline/Main.java index b2ea96fc..1657a91c 100755 --- a/Mustang-CLI/src/main/java/org/mustangproject/commandline/Main.java +++ b/Mustang-CLI/src/main/java/org/mustangproject/commandline/Main.java @@ -371,17 +371,17 @@ public class Main { boolean optionsRecognized = false; String action = ""; - Boolean disableFileLogging = false; + boolean disableFileLogging = false; try { cmd = parser.parse(options, args); // Retrieve all options action = cmd.getOptionValue("action"); String directoryName = cmd.getOptionValue("directory"); - Boolean filesFromStdIn = cmd.hasOption("listfromstdin");//((Number)cmdLine.getParsedOptionValue("integer-option")).intValue(); - Boolean ignoreFileExt = cmd.hasOption("ignorefileextension"); - Boolean noAttachments = cmd.hasOption("no-additional-attachments"); - Boolean helpRequested = cmd.hasOption("help") || ((action != null) && (action.equals("help"))); + boolean filesFromStdIn = cmd.hasOption("listfromstdin");//((Number)cmdLine.getParsedOptionValue("integer-option")).intValue(); + boolean ignoreFileExt = cmd.hasOption("ignorefileextension"); + boolean noAttachments = cmd.hasOption("no-additional-attachments"); + boolean helpRequested = cmd.hasOption("help") || ((action != null) && (action.equals("help"))); disableFileLogging = cmd.hasOption("disable-file-logging"); String sourceName = cmd.getOptionValue("source"); @@ -389,8 +389,8 @@ public class Main { String outName = cmd.getOptionValue("out"); String format = cmd.getOptionValue("format"); String lang = cmd.getOptionValue("language"); - Boolean noNotices = cmd.hasOption("no-notices"); - Boolean LogAsPDF = cmd.hasOption("log-as-pdf"); + boolean noNotices = cmd.hasOption("no-notices"); + boolean LogAsPDF = cmd.hasOption("log-as-pdf"); String zugferdVersion = cmd.getOptionValue("version"); String zugferdProfile = cmd.getOptionValue("profile"); diff --git a/Mustang-CLI/src/main/java/org/mustangproject/commandline/ValidatorFileWalker.java b/Mustang-CLI/src/main/java/org/mustangproject/commandline/ValidatorFileWalker.java index a368baaa..722b7167 100644 --- a/Mustang-CLI/src/main/java/org/mustangproject/commandline/ValidatorFileWalker.java +++ b/Mustang-CLI/src/main/java/org/mustangproject/commandline/ValidatorFileWalker.java @@ -68,7 +68,7 @@ public class ValidatorFileWalker thisResultString = "invalid"; allValid = false; } - LOGGER.info(String.format("\n@%s Testing file %d: %s (%s) ", dateFormat.format(date), fileCount++, thisResultString, file)); + LOGGER.info("\n@{} Testing file {}: {} ({}) ", dateFormat.format(date), fileCount++, thisResultString, file); } } } @@ -79,7 +79,7 @@ public class ValidatorFileWalker @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) { - LOGGER.info(String.format("\nDirectory: %s \n", dir)); + LOGGER.info("\nDirectory: {} \n", dir); return FileVisitResult.CONTINUE; } diff --git a/library/src/main/java/org/mustangproject/Invoice.java b/library/src/main/java/org/mustangproject/Invoice.java index e3123b7a..72d71a6c 100644 --- a/library/src/main/java/org/mustangproject/Invoice.java +++ b/library/src/main/java/org/mustangproject/Invoice.java @@ -591,11 +591,7 @@ public class Invoice implements IExportableTransaction { * @return fluent setter */ public Invoice setZFAllowances(Allowance[] iza) { - Allowances=new ArrayList<>(); - - for (IZUGFeRDAllowanceCharge cz:iza) { - Allowances.add(cz); - } + Allowances=new ArrayList<>(Arrays.asList(iza)); return this; } @@ -616,9 +612,7 @@ public class Invoice implements IExportableTransaction { */ public Invoice setZFCharges(Charge[] iza) { Charges=new ArrayList<>(); - for (IZUGFeRDAllowanceCharge cz:iza) { - Charges.add(cz); - } + Charges.addAll(Arrays.asList(iza)); return this; } diff --git a/library/src/main/java/org/mustangproject/Item.java b/library/src/main/java/org/mustangproject/Item.java index 59e604fc..c062e620 100644 --- a/library/src/main/java/org/mustangproject/Item.java +++ b/library/src/main/java/org/mustangproject/Item.java @@ -78,20 +78,21 @@ public class Item implements IZUGFeRDExportableItem { icnm.getAsString("Name").ifPresent(product::setName); icnm.getAsString("Description").ifPresent(product::setDescription); - icnm.getAsNodeMap("SellersItemIdentification").ifPresent(SellersItemIdentification -> { - SellersItemIdentification.getAsString("ID").ifPresent(product::setSellerAssignedID); - }); + icnm.getAsNodeMap("SellersItemIdentification") + .flatMap(SellersItemIdentification -> SellersItemIdentification.getAsString("ID")) + .ifPresent(product::setSellerAssignedID); - icnm.getAsNodeMap("BuyersItemIdentification").ifPresent(BuyersItemIdentification -> { - BuyersItemIdentification.getAsString("ID").ifPresent(product::setBuyerAssignedID); - }); + icnm.getAsNodeMap("BuyersItemIdentification") + .flatMap(BuyersItemIdentification -> BuyersItemIdentification.getAsString("ID")) + .ifPresent(product::setBuyerAssignedID); - icnm.getAsNodeMap("ClassifiedTaxCategory").flatMap(m -> m.getAsBigDecimal("Percent")) + icnm.getAsNodeMap("ClassifiedTaxCategory") + .flatMap(m -> m.getAsBigDecimal("Percent")) .ifPresent(product::setVATPercent); }); - itemMap.getAsNodeMap("AssociatedDocumentLineDocument").ifPresent(icnm -> { - icnm.getAsString("LineID").ifPresent(this::setId); - }); + itemMap.getAsNodeMap("AssociatedDocumentLineDocument") + .flatMap(icnm -> icnm.getAsString("LineID")) + .ifPresent(this::setId); itemMap.getAsNodeMap("Price").ifPresent(icnm -> { // ubl @@ -181,7 +182,7 @@ public class Item implements IZUGFeRDExportableItem { } if (amountString != null) { izac.setTotalAmount(new BigDecimal(amountString)); - if (percentString!=null&&(percentString!="0")) { + if (percentString!=null&&(!percentString.equals("0"))) { izac.setTotalAmount(new BigDecimal(amountString).divide(getQuantity())); } } @@ -211,11 +212,11 @@ public class Item implements IZUGFeRDExportableItem { icnm.getAllNodes("AdditionalReferencedDocument").map(ReferencedDocument::fromNode).forEach(this::addAdditionalReference); - icnm.getAsString("ReceivableSpecifiedTradeAccountingAccount").ifPresent(s -> this.accountingReference = s == null ? null : s.trim()); + icnm.getAsString("ReceivableSpecifiedTradeAccountingAccount").ifPresent(s -> this.accountingReference = s.trim()); icnm.getAsNodeMap("BillingSpecifiedPeriod").ifPresent(periodNode -> { - Date start = periodNode.getAsNodeMap("StartDateTime").flatMap(dateTimeNode -> dateTimeNode.getNode("DateTimeString")).map(dts -> XMLTools.tryDate(dts)).orElse(null); - Date end = periodNode.getAsNodeMap("EndDateTime").flatMap(dateTimeNode -> dateTimeNode.getNode("DateTimeString")).map(dts -> XMLTools.tryDate(dts)).orElse(null); + Date start = periodNode.getAsNodeMap("StartDateTime").flatMap(dateTimeNode -> dateTimeNode.getNode("DateTimeString")).map(XMLTools::tryDate).orElse(null); + Date end = periodNode.getAsNodeMap("EndDateTime").flatMap(dateTimeNode -> dateTimeNode.getNode("DateTimeString")).map(XMLTools::tryDate).orElse(null); setDetailedDeliveryPeriod(start, end); }); }); @@ -428,9 +429,7 @@ public class Item implements IZUGFeRDExportableItem { public void setItemAllowances(ArrayList theAllowances) { if (theAllowances != null) { Allowances.clear(); - for (Allowance theAllowance : theAllowances) { - Allowances.add(theAllowance); - } + Allowances.addAll(theAllowances); } } @@ -440,9 +439,7 @@ public class Item implements IZUGFeRDExportableItem { public void setItemCharges(ArrayList theCharges) { if (theCharges != null) { Charges.clear(); - for (Charge theCharge : theCharges) { - Charges.add(theCharge); - } + Charges.addAll(theCharges); } } diff --git a/library/src/main/java/org/mustangproject/TradeParty.java b/library/src/main/java/org/mustangproject/TradeParty.java index 73b1ae81..66491cf7 100644 --- a/library/src/main/java/org/mustangproject/TradeParty.java +++ b/library/src/main/java/org/mustangproject/TradeParty.java @@ -746,7 +746,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { if (bankDetails.isEmpty() && debitDetails.isEmpty()) { return null; } - List tradeSettlements = Stream.concat(bankDetails.stream(), debitDetails.stream()).map(IZUGFeRDTradeSettlement.class::cast).collect(Collectors.toList()); + List tradeSettlements = Stream.concat(bankDetails.stream(), debitDetails.stream()).collect(Collectors.toList()); IZUGFeRDTradeSettlement[] result = new IZUGFeRDTradeSettlement[tradeSettlements.size()]; for (int i = 0; i < tradeSettlements.size(); i++) { diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/DAPullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/DAPullProvider.java index a202b371..9bac1b07 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/DAPullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/DAPullProvider.java @@ -97,12 +97,12 @@ public class DAPullProvider extends ZUGFeRD2PullProvider { + XMLTools.encodeXML(currentItem.getProduct().getBuyerAssignedID()) + ""; } String allowanceChargeStr = ""; - if (currentItem.getItemAllowances() != null && currentItem.getItemAllowances().length > 0) { + if (currentItem.getItemAllowances() != null) { for (final IZUGFeRDAllowanceCharge allowance : currentItem.getItemAllowances()) { allowanceChargeStr += getAllowanceChargeStr(allowance, currentItem); } } - if (currentItem.getItemCharges() != null && currentItem.getItemCharges().length > 0) { + if (currentItem.getItemCharges() != null) { for (final IZUGFeRDAllowanceCharge charge : currentItem.getItemCharges()) { allowanceChargeStr += getAllowanceChargeStr(charge, currentItem); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java index a78a72df..50ea54ae 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/LineCalculator.java @@ -22,7 +22,7 @@ public class LineCalculator { public LineCalculator(IZUGFeRDExportableItem currentItem) { - if (currentItem.getItemAllowances() != null && currentItem.getItemAllowances().length > 0) { + if (currentItem.getItemAllowances() != null) { for (IZUGFeRDAllowanceCharge allowance : currentItem.getItemAllowances()) { BigDecimal factor=BigDecimal.ONE; BigDecimal singleAllowance=allowance.getTotalAmount(currentItem); @@ -35,7 +35,7 @@ public class LineCalculator { } } - if (currentItem.getItemCharges() != null && currentItem.getItemCharges().length > 0) { + if (currentItem.getItemCharges() != null) { for (IZUGFeRDAllowanceCharge charge : currentItem.getItemCharges()) { BigDecimal factor=BigDecimal.ONE; BigDecimal singleCharge=charge.getTotalAmount(currentItem); @@ -47,7 +47,7 @@ public class LineCalculator { } } - if (currentItem.getItemTotalAllowances() != null && currentItem.getItemTotalAllowances().length > 0) { + if (currentItem.getItemTotalAllowances() != null) { for (final IZUGFeRDAllowanceCharge itemTotalAllowance : currentItem.getItemTotalAllowances()) { addAllowanceItemTotal(itemTotalAllowance.getTotalAmount(currentItem)); } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java index f6329d52..881bd98b 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java @@ -57,7 +57,7 @@ public class OXPullProvider extends ZUGFeRD2PullProvider { paymentTermsDescription = XMLTools.encodeXML(trans.getPaymentTermDescription()); } - if ((paymentTermsDescription == null) && (trans.getDocumentCode() != CORRECTEDINVOICE)/* && (trans.getDocumentCode() != DocumentCodeTypeConstants.CREDITNOTE)*/) { + if (paymentTermsDescription == null && !trans.getDocumentCode().equals(CORRECTEDINVOICE)/* && (trans.getDocumentCode() != DocumentCodeTypeConstants.CREDITNOTE)*/) { paymentTermsDescription = "Zahlbar ohne Abzug bis " + germanDateFormat.format(trans.getDueDate()); } @@ -125,12 +125,12 @@ public class OXPullProvider extends ZUGFeRD2PullProvider { + XMLTools.encodeXML(currentItem.getProduct().getBuyerAssignedID()) + ""; } String allowanceChargeStr = ""; - if (currentItem.getItemAllowances() != null && currentItem.getItemAllowances().length > 0) { + if (currentItem.getItemAllowances() != null) { for (final IZUGFeRDAllowanceCharge allowance : currentItem.getItemAllowances()) { allowanceChargeStr += getAllowanceChargeStr(allowance, currentItem); } } - if (currentItem.getItemCharges() != null && currentItem.getItemCharges().length > 0) { + if (currentItem.getItemCharges() != null) { for (final IZUGFeRDAllowanceCharge charge : currentItem.getItemCharges()) { allowanceChargeStr += getAllowanceChargeStr(charge, currentItem); @@ -313,8 +313,9 @@ public class OXPullProvider extends ZUGFeRD2PullProvider { for (final IZUGFeRDTradeSettlementPayment payment : trans.getTradeSettlementPayment()) { if (payment != null) { hasDueDate = true; - // xml += payment.getSettlementXML(); - } + break; + // xml += payment.getSettlementXML(); + } } } if (trans.getTradeSettlement() != null) { diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java index edb94dd5..02ae965d 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java @@ -89,7 +89,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { private BigDecimal sumAllowanceCharge(BigDecimal percent, IZUGFeRDAllowanceCharge[] charges) { BigDecimal res = BigDecimal.ZERO; - if ((charges != null) && (charges.length > 0)) { + if (charges != null) { for (IZUGFeRDAllowanceCharge currentCharge : charges) { if ((percent == null) || (currentCharge.getTaxPercent().compareTo(percent) == 0)) { res = res.add(currentCharge.getTotalAmount(this)); @@ -212,7 +212,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { } IZUGFeRDAllowanceCharge[] charges = trans.getZFCharges(); - if ((charges != null) && (charges.length > 0)) { + if (charges != null) { for (IZUGFeRDAllowanceCharge currentCharge : charges) { BigDecimal taxPercent = currentCharge.getTaxPercent(); if (taxPercent != null) { @@ -230,7 +230,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { } } IZUGFeRDAllowanceCharge[] allowances = trans.getZFAllowances(); - if ((allowances != null) && (allowances.length > 0)) { + if (allowances != null) { for (IZUGFeRDAllowanceCharge currentAllowance : allowances) { BigDecimal taxPercent = currentAllowance.getTaxPercent(); if (taxPercent != null) { @@ -286,8 +286,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { } final IZUGFeRDAllowanceCharge[] charges = this.trans.getZFCharges(); - if (charges != null && charges.length > 0) - { + if (charges != null) { for (final IZUGFeRDAllowanceCharge currentCharge : charges) { final BigDecimal taxPercent = currentCharge.getTaxPercent(); @@ -310,8 +309,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { } } final IZUGFeRDAllowanceCharge[] allowances = this.trans.getZFAllowances(); - if (allowances != null && allowances.length > 0) - { + if (allowances != null) { for (final IZUGFeRDAllowanceCharge currentAllowance : allowances) { final BigDecimal taxPercent = currentAllowance.getTaxPercent(); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index 99f98180..7c85155c 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -360,7 +360,10 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { } paymentTermsDescription += discount.getAsXRechnung(); } - } else if ((paymentTermsDescription == null) && (trans.getDocumentCode() != DocumentCodeTypeConstants.CORRECTEDINVOICE) && (trans.getDocumentCode() != DocumentCodeTypeConstants.CREDITNOTE)) { + } else if (paymentTermsDescription == null + && !trans.getDocumentCode().equals(DocumentCodeTypeConstants.CORRECTEDINVOICE) + && !trans.getDocumentCode().equals(DocumentCodeTypeConstants.CREDITNOTE) + ) { if (trans.getDueDate() != null) { paymentTermsDescription = "Please remit until " + germanDateFormat.format(trans.getDueDate()); } @@ -434,12 +437,12 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { + XMLTools.encodeXML(currentItem.getProduct().getBuyerAssignedID()) + ""; } String allowanceChargeStr = ""; - if (currentItem.getProduct().getAllowances() != null && currentItem.getProduct().getAllowances().length > 0) { + if (currentItem.getProduct().getAllowances() != null) { for (final IZUGFeRDAllowanceCharge allowance : currentItem.getProduct().getAllowances()) { allowanceChargeStr += getAllowanceChargeStr(allowance, currentItem); } } - if (currentItem.getProduct().getCharges() != null && currentItem.getProduct().getCharges().length > 0) { + if (currentItem.getProduct().getCharges() != null) { for (final IZUGFeRDAllowanceCharge charge : currentItem.getProduct().getCharges()) { allowanceChargeStr += getAllowanceChargeStr(charge, currentItem); @@ -447,24 +450,24 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { } String itemTotalAllowanceChargeStr = ""; - if (currentItem.getAllowances() != null && currentItem.getAllowances().length > 0) { + if (currentItem.getAllowances() != null) { for (final IZUGFeRDAllowanceCharge itemTotalAllowance : currentItem.getAllowances()) { itemTotalAllowanceChargeStr += getItemTotalAllowanceChargeStr(itemTotalAllowance, currentItem); } } - if (currentItem.getCharges() != null && currentItem.getCharges().length > 0) { + if (currentItem.getCharges() != null) { for (final IZUGFeRDAllowanceCharge itemTotalCharges : currentItem.getCharges()) { itemTotalAllowanceChargeStr += getItemTotalAllowanceChargeStr(itemTotalCharges, currentItem); } } xml += "" + XMLTools.encodeXML(currentItem.getProduct().getName()) + ""; - if (currentItem.getProduct().getDescription() != null && currentItem.getProduct().getDescription().length() > 0) { + if (currentItem.getProduct().getDescription() != null) { xml += "" + XMLTools.encodeXML(currentItem.getProduct().getDescription()) + ""; } - if (currentItem.getProduct().getClassifications() != null && currentItem.getProduct().getClassifications().length > 0) { + if (currentItem.getProduct().getClassifications() != null) { for (IDesignatedProductClassification classification : currentItem.getProduct().getClassifications()) { xml += "" + " 0)) { + if (profile == Profiles.getByName("Extended") && trans.getCashDiscounts() != null) { for (IZUGFeRDCashDiscount discount : trans.getCashDiscounts() ) { xml += discount.getAsCII(); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java index 87137411..0909735f 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java @@ -564,10 +564,9 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte // iterate over all pdf pages - for (Object object : doc.getPages()) { - if (object instanceof PDPage) { + for (PDPage page : doc.getPages()) { + if (page != null) { - PDPage page = (PDPage) object; PDResources res = page.getResources(); // Check for fonts in PDXObjects: diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index 4cb47c4a..7136be78 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -213,7 +213,16 @@ public class ZUGFeRDInvoiceImporter { */ 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")) { + Set validFilenames = Set.of( + "ZUGFeRD-invoice.xml", + "zugferd-invoice.xml", + "factur-x.xml", + "xrechnung.xml", + "order-x.xml", + "cida.xml" + ); + + if (validFilenames.contains(filename)) { containsMeta = true; // String embeddedFilename = filePath + filename; @@ -360,39 +369,31 @@ public class ZUGFeRDInvoiceImporter { delivery.addGlobalID(sID); } }); - deliveryLocationNodeMap.getAsNodeMap("Address").ifPresent(s -> { - s.getAsString("StreetName").ifPresent(t -> delivery.setStreet(t)); - }); - deliveryLocationNodeMap.getAsNodeMap("Address").ifPresent(s -> { - s.getAsString("AdditionalStreetName").ifPresent(t -> delivery.setAdditionalAddress(t)); - }); - deliveryLocationNodeMap.getAsNodeMap("Address").ifPresent(s -> { - s.getAsString("CityName").ifPresent(t -> delivery.setLocation(t)); - }); - deliveryLocationNodeMap.getAsNodeMap("Address").ifPresent(s -> { - s.getAsString("PostalZone").ifPresent(t -> delivery.setZIP(t)); - }); - deliveryLocationNodeMap.getAsNodeMap("Address").ifPresent(s -> { - s.getAsNodeMap("Country").ifPresent(t -> t.getAsString("IdentificationCode").ifPresent(u -> delivery.setCountry(u))); - }); - deliveryLocationNodeMap.getAsNodeMap("Address").ifPresent(s -> { - s.getAsNodeMap("AddressLine").ifPresent(t -> t.getAsString("Line").ifPresent(u -> delivery.setAdditionalAddressExtension(u))); - }); - deliveryLocationNodeMap.getAsNodeMap("Address").ifPresent(s -> { - s.getAsString("AdditionalStreetName").ifPresent(t -> delivery.setAdditionalAddress(t)); - }); - deliveryLocationNodeMap.getAsNodeMap("Address").ifPresent(s -> { - s.getAsString("AdditionalStreetName").ifPresent(t -> delivery.setAdditionalAddress(t)); - }); + Optional addressNodeMapp = deliveryLocationNodeMap.getAsNodeMap("Address"); + addressNodeMapp.flatMap(s -> s.getAsString("StreetName")) + .ifPresent(delivery::setStreet); + addressNodeMapp.flatMap(s -> s.getAsString("AdditionalStreetName")) + .ifPresent(delivery::setAdditionalAddress); + addressNodeMapp.flatMap(s -> s.getAsString("CityName")) + .ifPresent(delivery::setLocation); + addressNodeMapp.flatMap(s -> s.getAsString("PostalZone")) + .ifPresent(delivery::setZIP); + addressNodeMapp.flatMap(s -> s.getAsNodeMap("Country")).flatMap(t -> t.getAsString("IdentificationCode")) + .ifPresent(delivery::setCountry); + addressNodeMapp.flatMap(s -> s.getAsNodeMap("AddressLine")).flatMap(t -> t.getAsString("Line")) + .ifPresent(delivery::setAdditionalAddressExtension); + addressNodeMapp.flatMap(s -> s.getAsString("AdditionalStreetName")) + .ifPresent(delivery::setAdditionalAddress); + addressNodeMapp.flatMap(s -> s.getAsString("AdditionalStreetName")) + .ifPresent(delivery::setAdditionalAddress); }); - new NodeMap(deliveryNode).getAsNodeMap("DeliveryParty").ifPresent(partyMap -> { - partyMap.getAsNodeMap("PartyName").ifPresent(s -> { - s.getAsString("Name").ifPresent(t -> delivery.setName(t)); - }); - }); - String street, name, additionalStreet, city, postal, countrySubentity, line, country = null; + new NodeMap(deliveryNode).getAsNodeMap("DeliveryParty") + .flatMap(partyMap -> partyMap.getAsNodeMap("PartyName")) + .flatMap(s -> s.getAsString("Name")) + .ifPresent(delivery::setName); + zpp.setDeliveryAddress(delivery); } @@ -431,7 +432,7 @@ public class ZUGFeRDInvoiceImporter { xpr = xpath.compile("//*[local-name()=\"ExchangedDocument\"]|//*[local-name()=\"HeaderExchangedDocument\"]"); NodeList ExchangedDocumentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); - + xpr = xpath.compile("//*[local-name()=\"GrandTotalAmount\"]|//*[local-name()=\"TaxInclusiveAmount\"]"); BigDecimal expectedGrandTotal = null; NodeList totalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); @@ -579,11 +580,11 @@ public class ZUGFeRDInvoiceImporter { } String creditorReferenceID = extractString("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]/*[local-name()=\"CreditorReferenceID\"]").trim();//BT-90 - if ((creditorReferenceID == null)||(creditorReferenceID.length()==0)) { + if (creditorReferenceID == null || creditorReferenceID.isEmpty()) { //maybe it's there in UBL? creditorReferenceID = extractString("//*[local-name()=\"AccountingSupplierParty\"]/*[local-name()=\"Party\"]/*[local-name()=\"PartyIdentification\"]/*[local-name()=\"ID\"]").trim(); } - if ((creditorReferenceID != null)&&(creditorReferenceID.length()>0)) { + if (creditorReferenceID != null && !creditorReferenceID.isEmpty()) { zpp.setCreditorReferenceID(creditorReferenceID); } diff --git a/validator/src/main/java/org/mustangproject/validator/PDFValidator.java b/validator/src/main/java/org/mustangproject/validator/PDFValidator.java index e79d3f29..0e35b714 100644 --- a/validator/src/main/java/org/mustangproject/validator/PDFValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/PDFValidator.java @@ -12,7 +12,6 @@ import java.util.Calendar; import java.util.EnumSet; import java.util.HashMap; -import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -130,136 +129,137 @@ public class PDFValidator extends Validator { final Document docXMP; - if (xmp == null || xmp.length() == 0) { + if (xmp == null || xmp.isEmpty()) { context.addResultItem(new ValidationResultItem(ESeverity.error, "Invalid XMP Metadata not found") .setSection(17).setPart(EPart.pdf)); } - else - /* - * checking for sth like EXTENDED - * INVOICE - * ZUGFeRD-invoice.xml - * 1.0 - */ - try { - final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks" - factory.setXIncludeAware(false); + else { + /* + * checking for sth like EXTENDED + * INVOICE + * ZUGFeRD-invoice.xml + * 1.0 + */ + try { + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks" + factory.setXIncludeAware(false); - final DocumentBuilder builder = factory.newDocumentBuilder(); - final InputSource is = new InputSource(new StringReader(xmp)); - docXMP = builder.parse(is); + final DocumentBuilder builder = factory.newDocumentBuilder(); + final InputSource is = new InputSource(new StringReader(xmp)); + docXMP = builder.parse(is); - final XPathFactory xpathFactory = XPathFactory.newInstance(); + final XPathFactory xpathFactory = XPathFactory.newInstance(); - // Create XPath object XPath xpath = xpathFactory.newXPath(); XPathExpression + // Create XPath object XPath xpath = xpathFactory.newXPath(); XPathExpression - final XPath xpath = xpathFactory.newXPath(); - // xpath.compile("//*[local-name()=\"GuidelineSpecifiedDocumentContextParameter\"]/[local-name()=\"ID\"]"); - // evaluate expression result on XML document ndList = (NodeList) + final XPath xpath = xpathFactory.newXPath(); + // xpath.compile("//*[local-name()=\"GuidelineSpecifiedDocumentContextParameter\"]/[local-name()=\"ID\"]"); + // evaluate expression result on XML document ndList = (NodeList) - // get the first element - XPathExpression xpr = xpath.compile( - "//*[local-name()=\"ConformanceLevel\"]|//*[local-name()=\"Description\"]/@ConformanceLevel"); - NodeList nodes = (NodeList) xpr.evaluate(docXMP, XPathConstants.NODESET); + // get the first element + XPathExpression xpr = xpath.compile( + "//*[local-name()=\"ConformanceLevel\"]|//*[local-name()=\"Description\"]/@ConformanceLevel"); + NodeList nodes = (NodeList) xpr.evaluate(docXMP, XPathConstants.NODESET); - if (nodes.getLength() == 0) { - context.addResultItem( - new ValidationResultItem(ESeverity.error, "XMP Metadata: ConformanceLevel not found") - .setSection(11).setPart(EPart.pdf)); - } - - boolean conformanceLevelValid = false; - for (int i = 0; i < nodes.getLength(); i++) { - - final String[] valueArray = {"BASIC WL", "BASIC", "MINIMUM", "EN 16931", "COMFORT", "CIUS", "EXTENDED", "XRECHNUNG"}; - if (stringArrayContains(valueArray, nodes.item(i).getTextContent())) { - conformanceLevelValid = true; - } - } - if (!conformanceLevelValid) { - context.addResultItem(new ValidationResultItem( - ESeverity.error, - "XMP Metadata: ConformanceLevel contains invalid value" - ).setSection(12).setPart(EPart.pdf)); - - } - xpr = xpath.compile("//*[local-name()=\"DocumentType\"]|//*[local-name()=\"Description\"]/@DocumentType"); - nodes = (NodeList) xpr.evaluate(docXMP, XPathConstants.NODESET); - - if (nodes.getLength() == 0) { - context.addResultItem(new ValidationResultItem(ESeverity.error, "XMP Metadata: DocumentType not found") - .setSection(13).setPart(EPart.pdf)); - } - - boolean documentTypeValid = false; - for (int i = 0; i < nodes.getLength(); i++) { - if (nodes.item(i).getTextContent().equals("INVOICE") || nodes.item(i).getTextContent().equals("ORDER") - || nodes.item(i).getTextContent().equals("ORDER_RESPONSE") || nodes.item(i).getTextContent() - .equals("ORDER_CHANGE")) { - documentTypeValid = true; - } - } - if (!documentTypeValid) { - context.addResultItem( - new ValidationResultItem(ESeverity.error, "XMP Metadata: DocumentType invalid") - .setSection(14).setPart(EPart.pdf)); - - } - xpr = xpath.compile( - "//*[local-name()=\"DocumentFileName\"]|//*[local-name()=\"Description\"]/@DocumentFileName"); - nodes = (NodeList) xpr.evaluate(docXMP, XPathConstants.NODESET); - - if (nodes.getLength() == 0) { - context.addResultItem( - new ValidationResultItem(ESeverity.error, "XMP Metadata: DocumentFileName not found") - .setSection(21).setPart(EPart.pdf)); - } - boolean documentFilenameValid = false; - for (int i = 0; i < nodes.getLength(); i++) { - final String[] valueArray = {"factur-x.xml", "ZUGFeRD-invoice.xml", "zugferd-invoice.xml", "xrechnung.xml", "order-x.xml"}; - if (stringArrayContains(valueArray, nodes.item(i).getTextContent())) { - documentFilenameValid = true; + if (nodes.getLength() == 0) { + context.addResultItem( + new ValidationResultItem(ESeverity.error, "XMP Metadata: ConformanceLevel not found") + .setSection(11).setPart(EPart.pdf)); } - // e.g. ZUGFeRD-invoice.xml - } - if (!documentFilenameValid) { + boolean conformanceLevelValid = false; + for (int i = 0; i < nodes.getLength(); i++) { - context.addResultItem(new ValidationResultItem( - ESeverity.error, - "XMP Metadata: DocumentFileName contains invalid value" - ).setSection(19).setPart(EPart.pdf)); - } - xpr = xpath.compile("//*[local-name()=\"Version\"]|//*[local-name()=\"Description\"]/@Version"); - nodes = (NodeList) xpr.evaluate(docXMP, XPathConstants.NODESET); + final String[] valueArray = {"BASIC WL", "BASIC", "MINIMUM", "EN 16931", "COMFORT", "CIUS", "EXTENDED", "XRECHNUNG"}; + if (stringArrayContains(valueArray, nodes.item(i).getTextContent())) { + conformanceLevelValid = true; + } + } + if (!conformanceLevelValid) { + context.addResultItem(new ValidationResultItem( + ESeverity.error, + "XMP Metadata: ConformanceLevel contains invalid value" + ).setSection(12).setPart(EPart.pdf)); - // get all child nodes - // NodeList nodes = element.getChildNodes(); - // expr.evaluate(docXMP, XPathConstants.NODESET); - // print the text content of each child - if (nodes.getLength() == 0) { - context.addResultItem(new ValidationResultItem(ESeverity.error, "XMP Metadata: Version not found") - .setSection(15).setPart(EPart.pdf)); - } + } + xpr = xpath.compile("//*[local-name()=\"DocumentType\"]|//*[local-name()=\"Description\"]/@DocumentType"); + nodes = (NodeList) xpr.evaluate(docXMP, XPathConstants.NODESET); - boolean versionValid = false; - for (int i = 0; i < nodes.getLength(); i++) { - final String[] valueArray = {"1.0", "1p0", "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 (nodes.getLength() == 0) { + context.addResultItem(new ValidationResultItem(ESeverity.error, "XMP Metadata: DocumentType not found") + .setSection(13).setPart(EPart.pdf)); + } - if (stringArrayContains(valueArray, nodes.item(i).getTextContent())) { - versionValid = true; - } // e.g. 1.0 - } - if (!versionValid) { - context.addResultItem( - new ValidationResultItem(ESeverity.error, "XMP Metadata: Version contains invalid value") - .setSection(16).setPart(EPart.pdf)); + boolean documentTypeValid = false; + for (int i = 0; i < nodes.getLength(); i++) { + if (nodes.item(i).getTextContent().equals("INVOICE") || nodes.item(i).getTextContent().equals("ORDER") + || nodes.item(i).getTextContent().equals("ORDER_RESPONSE") || nodes.item(i).getTextContent() + .equals("ORDER_CHANGE")) { + documentTypeValid = true; + } + } + if (!documentTypeValid) { + context.addResultItem( + new ValidationResultItem(ESeverity.error, "XMP Metadata: DocumentType invalid") + .setSection(14).setPart(EPart.pdf)); + } + xpr = xpath.compile( + "//*[local-name()=\"DocumentFileName\"]|//*[local-name()=\"Description\"]/@DocumentFileName"); + nodes = (NodeList) xpr.evaluate(docXMP, XPathConstants.NODESET); + + if (nodes.getLength() == 0) { + context.addResultItem( + new ValidationResultItem(ESeverity.error, "XMP Metadata: DocumentFileName not found") + .setSection(21).setPart(EPart.pdf)); + } + boolean documentFilenameValid = false; + for (int i = 0; i < nodes.getLength(); i++) { + final String[] valueArray = {"factur-x.xml", "ZUGFeRD-invoice.xml", "zugferd-invoice.xml", "xrechnung.xml", "order-x.xml"}; + if (stringArrayContains(valueArray, nodes.item(i).getTextContent())) { + documentFilenameValid = true; + } + + // e.g. ZUGFeRD-invoice.xml + } + if (!documentFilenameValid) { + + context.addResultItem(new ValidationResultItem( + ESeverity.error, + "XMP Metadata: DocumentFileName contains invalid value" + ).setSection(19).setPart(EPart.pdf)); + } + xpr = xpath.compile("//*[local-name()=\"Version\"]|//*[local-name()=\"Description\"]/@Version"); + nodes = (NodeList) xpr.evaluate(docXMP, XPathConstants.NODESET); + + // get all child nodes + // NodeList nodes = element.getChildNodes(); + // expr.evaluate(docXMP, XPathConstants.NODESET); + // print the text content of each child + if (nodes.getLength() == 0) { + context.addResultItem(new ValidationResultItem(ESeverity.error, "XMP Metadata: Version not found") + .setSection(15).setPart(EPart.pdf)); + } + + boolean versionValid = false; + for (int i = 0; i < nodes.getLength(); i++) { + final String[] valueArray = {"1.0", "1p0", "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())) { + versionValid = true; + } // e.g. 1.0 + } + if (!versionValid) { + context.addResultItem( + new ValidationResultItem(ESeverity.error, "XMP Metadata: Version contains invalid value") + .setSection(16).setPart(EPart.pdf)); + + } + } catch (final SAXException | IOException | ParserConfigurationException | XPathExpressionException e) { + LOGGER.error(e.getMessage(), e); } - } catch (final SAXException | IOException | ParserConfigurationException | XPathExpressionException e) { - LOGGER.error(e.getMessage(), e); } zfXML = zi.getUTF8(); @@ -306,7 +306,7 @@ public class PDFValidator extends Validator { final HashMap additionalData = zi.getAdditionalData(); for (final String filename : additionalData.keySet()) { // validating xml in byte[] additionalData.get(filename) - LOGGER.info("validating additionalData " + filename); + LOGGER.info("validating additionalData {}", filename); validateSchema(additionalData.get(filename), "ad/basic/additional_data_base_schema.xsd", 2, EPart.pdf); } diff --git a/validator/src/main/java/org/mustangproject/validator/ValidationContext.java b/validator/src/main/java/org/mustangproject/validator/ValidationContext.java index ce3cde48..4c5edb88 100644 --- a/validator/src/main/java/org/mustangproject/validator/ValidationContext.java +++ b/validator/src/main/java/org/mustangproject/validator/ValidationContext.java @@ -32,13 +32,13 @@ public class ValidationContext { } if (logger != null) { if ((vr.getSeverity() == ESeverity.fatal) || (vr.getSeverity() == ESeverity.exception)) { - logger.error("Fatal Error " + vr.getSection() + ": " + vr.getMessage()); + logger.error("Fatal Error {}: {}", vr.getSection(), vr.getMessage()); } else if ((vr.getSeverity() == ESeverity.error)) { - logger.error("Error " + vr.getSection() + ": " + vr.getMessage()); + logger.error("Error {}: {}", vr.getSection(), vr.getMessage()); } else if (vr.getSeverity() == ESeverity.warning) { - logger.warn("Warning " + vr.getSection() + ": " + vr.getMessage()); + logger.warn("Warning {}: {}", vr.getSection(), vr.getMessage()); } else if (vr.getSeverity() == ESeverity.notice) { - logger.info("Notice " + vr.getSection() + ": " + vr.getMessage()); + logger.info("Notice {}: {}", vr.getSection(), vr.getMessage()); } } @@ -106,20 +106,17 @@ public class ValidationContext { } public String getXMLResult() { - String res = getCustomXML(); - if (results.size() > 0) { - res += ""; + StringBuilder res = new StringBuilder(getCustomXML()); + if (results != null && !results.isEmpty()) { + res.append(""); + for (final ValidationResultItem validationResultItem : results) { + // xml and pdf are handled in their respective sections + res.append(validationResultItem.getXMLOnce()).append("\n"); + } + res.append(""); } - - for (final ValidationResultItem validationResultItem : results) { - // xml and pdf are handled in their respective sections - res += validationResultItem.getXMLOnce() + "\n"; - } - if (results.size() > 0) { - res += ""; - } - res += ""; - return res; + res.append(""); + return res.toString(); } /*** diff --git a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java index ccd57b3a..c258f114 100644 --- a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java @@ -22,7 +22,6 @@ import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.mustangproject.CalculatedInvoice; -import org.mustangproject.Exceptions.ArithmetricException; import org.mustangproject.XMLTools; import org.mustangproject.ZUGFeRD.ZUGFeRDInvoiceImporter; import org.slf4j.Logger; @@ -310,7 +309,7 @@ public class XMLValidator extends Validator { 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); + LOGGER.debug("is XRechnung v{}", xrVersion); xsltFilename = "/xslt/XR_" + xrVersion + "/XRechnung-UBL-validation.xslt"; XrechnungSeverity = ESeverity.error; mainSchematronSectionErrorTypeCode = 27; @@ -534,7 +533,7 @@ public class XMLValidator extends Validator { } } - LOGGER.info("FailedAssert ", thisFailText); + LOGGER.info("FailedAssert {}", thisFailText); context.addResultItem(new ValidationResultItem(severity, thisFailText + thisFailIDStr + " from " + xsltFilename + ")") .setLocation(thisFailLocation).setCriterion(thisFailTest).setSection(section).setID(thisFailID) From 7ebaf3af8a124664413cd8fa8497ebeb496fe3ce Mon Sep 17 00:00:00 2001 From: Daniel Luckas Date: Thu, 17 Jul 2025 10:27:25 +0200 Subject: [PATCH 23/30] Enhance code quality --- .../java/org/mustangproject/ZUGFeRD/OXPullProvider.java | 2 +- .../org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java index 881bd98b..f2a032f2 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java @@ -57,7 +57,7 @@ public class OXPullProvider extends ZUGFeRD2PullProvider { paymentTermsDescription = XMLTools.encodeXML(trans.getPaymentTermDescription()); } - if (paymentTermsDescription == null && !trans.getDocumentCode().equals(CORRECTEDINVOICE)/* && (trans.getDocumentCode() != DocumentCodeTypeConstants.CREDITNOTE)*/) { + if (paymentTermsDescription == null && !CORRECTEDINVOICE.equals(trans.getDocumentCode())/* && (trans.getDocumentCode() != DocumentCodeTypeConstants.CREDITNOTE)*/) { paymentTermsDescription = "Zahlbar ohne Abzug bis " + germanDateFormat.format(trans.getDueDate()); } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index 7c85155c..6efb4b23 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -361,8 +361,8 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { paymentTermsDescription += discount.getAsXRechnung(); } } else if (paymentTermsDescription == null - && !trans.getDocumentCode().equals(DocumentCodeTypeConstants.CORRECTEDINVOICE) - && !trans.getDocumentCode().equals(DocumentCodeTypeConstants.CREDITNOTE) + && !DocumentCodeTypeConstants.CORRECTEDINVOICE.equals(trans.getDocumentCode()) + && !DocumentCodeTypeConstants.CREDITNOTE.equals(trans.getDocumentCode()) ) { if (trans.getDueDate() != null) { paymentTermsDescription = "Please remit until " + germanDateFormat.format(trans.getDueDate()); @@ -721,8 +721,8 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { } } } - if (trans.getDocumentCode().equals(DocumentCodeTypeConstants.CORRECTEDINVOICE) - || trans.getDocumentCode().equals(DocumentCodeTypeConstants.CREDITNOTE) + if (DocumentCodeTypeConstants.CORRECTEDINVOICE.equals(trans.getDocumentCode()) + || DocumentCodeTypeConstants.CREDITNOTE.equals(trans.getDocumentCode()) ) { hasDueDate = false; } From dc15c6e493cefe61848ce728e3df1476a307f17f Mon Sep 17 00:00:00 2001 From: jstaerk Date: Thu, 17 Jul 2025 12:32:40 +0200 Subject: [PATCH 24/30] closes #893 --- History.md | 4 +++ .../java/org/mustangproject/TradeParty.java | 26 +++++++++++++++++++ .../ZUGFeRD/DeSerializationTest.java | 21 ++++++++++++++- 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 8585c2f4..256e7c9f 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,7 @@ +2.18.1 +======= +- #893 Tradeparty globalID is not read from JSON + 2.18.0 ======= 2025-07-14 diff --git a/library/src/main/java/org/mustangproject/TradeParty.java b/library/src/main/java/org/mustangproject/TradeParty.java index 73b1ae81..f5d34c84 100644 --- a/library/src/main/java/org/mustangproject/TradeParty.java +++ b/library/src/main/java/org/mustangproject/TradeParty.java @@ -541,6 +541,32 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { return this; } + /*** + * for jackson, primarily, use addGlobalID(SchemedID) instead + * @param ID the id part without scheme + * @return fluent setter + */ + public TradeParty setGlobalID(String ID) { + if (globalId==null) { + globalId=new SchemedID(); + } + globalId.setId(ID); + return this; + } + + /*** + * for jackson, primarily, use addGlobalID(SchemedID) instead + * @param scheme the scheme part without id + * @return fluent setter + */ + public TradeParty setGlobalIDScheme(String scheme) { + if (globalId==null) { + globalId=new SchemedID(); + } + globalId.setScheme(scheme); + return this; + } + public TradeParty addGlobalID(SchemedID schemedID) { globalId = schemedID; return this; diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java index e527021e..f57c9520 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java @@ -414,10 +414,12 @@ public class DeSerializationTest extends ResourceCase { String number = "123"; String priceStr = "1.00"; String taxID = "9990815"; + BigDecimal price = new BigDecimal(priceStr); Invoice newInvoiceFromJSON = null; boolean hasExceptions = false; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + String json = ""; try { SchemedID gtin = new SchemedID("0160", "2001015001325"); SchemedID gln = new SchemedID("0088", "4304171000002"); @@ -435,7 +437,7 @@ public class DeSerializationTest extends ResourceCase { .addCashDiscount(new CashDiscount(new BigDecimal(2), 14)) .setDeliveryDate(sdf.parse("2020-11-02")).setNumber(number).setVATDueDateTypeCode(EventTimeCodeTypeConstants.PAYMENT_DATE); ObjectMapper mapper = new ObjectMapper(); - String json = mapper.writeValueAsString(i); + json = mapper.writeValueAsString(i); newInvoiceFromJSON = mapper.readValue(json, Invoice.class); } catch (ParseException e) { hasExceptions = true; @@ -447,6 +449,23 @@ public class DeSerializationTest extends ResourceCase { } + public void testFromJSON() throws JsonProcessingException { + String globalID = "4000001123452"; + String globalIDScheme = "0088"; + String itemDeliveryFrom="2022-01-28T23:00:00.000+00:00"; + String itemDeliveryTo="2022-01-30T23:00:00.000+00:00"; + + String json="{\"number\":\"123\",\"buyerOrderReferencedDocumentID\":\"28934\",\"currency\":\"CHF\",\"issueDate\":1752744199178,\"dueDate\":1752744199178,\"deliveryDate\":1604271600000,\"sender\":{\"name\":\"Test company\",\"zip\":\"55232\",\"street\":\"teststr\",\"location\":\"teststadt\",\"country\":\"DE\",\"taxID\":\"9990815\",\"vatID\":\"DE0815\",\"id\":\"0009845\",\"globalID\":\""+globalID+"\",\"globalIDScheme\":\""+globalIDScheme+"\",\"email\":\"sender@test.org\",\"vatid\":\"DE0815\"},\"recipient\":{\"name\":\"Franz Müller\",\"zip\":\"55232\",\"street\":\"teststr.12\",\"location\":\"Entenhausen\",\"country\":\"DE\",\"vatID\":\"DE4711\",\"additionalAddress\":\"Hinterhaus 3\",\"contact\":{\"name\":\"Franz Müller\",\"phone\":\"01779999999\",\"email\":\"franz@mueller.de\",\"zip\":\"55232\",\"street\":\"teststr. 12\",\"location\":\"Entenhausen\",\"country\":\"DE\",\"fax\":\"++49555123456\"},\"globalID\":\"4304171000002\",\"globalIDScheme\":\"0088\",\"email\":\"recipient@test.org\",\"vatid\":\"DE4711\"},\"deliveryAddress\":{\"name\":\"just the other side of the street\",\"zip\":\"55232\",\"street\":\"teststr.12a\",\"location\":\"Entenhausen\",\"country\":\"DE\",\"vatID\":\"DE47110\",\"vatid\":\"DE47110\"},\"cashDiscounts\":[{\"percent\":2,\"days\":14}],\"notes\":[\"document level 1/2\",\"document level 2/2\"],\"sellerOrderReferencedDocumentID\":\"9384\",\"contractReferencedDocument\":\"376zreurzu0983\",\"valid\":true,\"vatdueDateTypeCode\":\"72\",\"zfitems\":[{\"price\":1.00,\"quantity\":1,\"basisQuantity\":1,\"detailedDeliveryPeriodFrom\":\""+itemDeliveryFrom+"\",\"detailedDeliveryPeriodTo\":\""+itemDeliveryTo+"\",\"id\":\"a123\",\"buyerOrderReferencedDocumentLineID\":\"xxx\",\"product\":{\"unit\":\"H87\",\"name\":\"Testprodukt\",\"sellerAssignedID\":\"4711\",\"taxCategoryCode\":\"S\",\"globalID\":\"2001015001325\",\"globalIDScheme\":\"0160\",\"intraCommunitySupply\":false,\"reverseCharge\":false,\"vatpercent\":16},\"notes\":[\"item level 1/1\"],\"notesWithSubjectCode\":[{\"content\":\"item level 1/1\"}],\"itemAllowances\":[{\"totalAmount\":0.0200000000000000004163336342344337026588618755340576171875,\"taxPercent\":16,\"reason\":\"item discount\",\"categoryCode\":\"S\"}],\"value\":1.00}],\"ownVATID\":\"DE0815\",\"detailedDeliveryPeriodFrom\":1601503200000,\"detailedDeliveryPeriodTo\":1601848800000,\"ownTaxID\":\"9990815\",\"ownZIP\":\"55232\",\"ownLocation\":\"teststadt\",\"zfallowances\":[{\"totalAmount\":0.200000000000000011102230246251565404236316680908203125,\"taxPercent\":16,\"reason\":\"discount\",\"categoryCode\":\"S\"}],\"ownStreet\":\"teststr\",\"zfcharges\":[{\"totalAmount\":0.5,\"taxPercent\":16,\"reason\":\"quick delivery charge\",\"categoryCode\":\"S\"}],\"ownCountry\":\"DE\"}"; + + ObjectMapper mapper = new ObjectMapper(); + Invoice fromJSON = mapper.readValue(json, Invoice.class); + assertEquals(globalID, fromJSON.getSender().getGlobalID()); + assertEquals(globalIDScheme, fromJSON.getSender().getGlobalIDScheme()); + SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd"); + assertEquals("2022-01-29", sdf.format(fromJSON.getZFItems()[0].getDetailedDeliveryPeriodFrom())); + assertEquals("2022-01-31", sdf.format(fromJSON.getZFItems()[0].getDetailedDeliveryPeriodTo())); + assertEquals("sender@test.org", fromJSON.getSender().getEmail()); + } public void testDueDateRoundtrip() throws JsonProcessingException { From 0128e39353f017a2c68d87d10601f8c99dc9c87e Mon Sep 17 00:00:00 2001 From: Daniel Luckas Date: Thu, 17 Jul 2025 14:22:27 +0200 Subject: [PATCH 25/30] Close resources after usage --- .../java/org/mustangproject/validator/ResourceCase.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/validator/src/test/java/org/mustangproject/validator/ResourceCase.java b/validator/src/test/java/org/mustangproject/validator/ResourceCase.java index 621da5ef..589d72e3 100644 --- a/validator/src/test/java/org/mustangproject/validator/ResourceCase.java +++ b/validator/src/test/java/org/mustangproject/validator/ResourceCase.java @@ -17,8 +17,7 @@ public class ResourceCase extends TestCase { private static final Logger LOGGER = LoggerFactory.getLogger(ResourceCase.class.getCanonicalName()); // log output is public static File getResourceAsFile(String resourcePath) { - try { - InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(resourcePath); + try(InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(resourcePath)) { if (in == null) { return null; } @@ -42,8 +41,7 @@ public class ResourceCase extends TestCase { } public static byte[] getResourceAsByteArray(String resourcePath) { - try { - InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(resourcePath); + try(InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(resourcePath)) { if (in == null) { return null; } From 8a1361950181f1afbe20671e5256b4423e13daa5 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Tue, 22 Jul 2025 16:28:54 +0200 Subject: [PATCH 26/30] closes #890 --- .../java/org/mustangproject/validator/XMLValidator.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java index ccd57b3a..a86a681b 100644 --- a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java @@ -226,13 +226,7 @@ public class XMLValidator extends Validator { isExtended = context.getProfile().contains("extended"); validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "OX_10/comfort/SCRDMCCBDACIOMessageStructure_100pD20B.xsd", 99, EPart.ox); xsltFilename = "/xslt/OX_10/comfort/SCRDMCCBDACIOMessageStructure_100pD20B_COMFORT.xslt"; - - } else if (root.getLocalName().equalsIgnoreCase("SCRDMCCBDACIOMessageStructure")) { - context.setGeneration("1"); - isOrderX = true; - validateSchema(zfXML.getBytes(StandardCharsets.UTF_8), "OX_10/comfort/SCRDMCCBDACIOMessageStructure_100pD20B.xsd", 99, EPart.ox); - xsltFilename = "/xslt/OX_10/comfort/SCRDMCCBDACIOMessageStructure_100pD20B_COMFORT.xslt"; - + } else if (root.getLocalName().equalsIgnoreCase("CrossIndustryInvoice")) { // ZUGFeRD 2.0 or Factur-X context.setGeneration("2"); From 637e8737df32dbd69144958516172ca0be1d0494 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Fri, 25 Jul 2025 15:47:44 +0200 Subject: [PATCH 27/30] updated documentation --- doc/ZUV-Architektur.graphml | 492 +-- doc/ZUV-Architektur.svg | 6109 +++++++++++++++++++++++++++--- doc/development_documentation.md | 1 - 3 files changed, 5740 insertions(+), 862 deletions(-) diff --git a/doc/ZUV-Architektur.graphml b/doc/ZUV-Architektur.graphml index f1e1e09d..cf52259e 100644 --- a/doc/ZUV-Architektur.graphml +++ b/doc/ZUV-Architektur.graphml @@ -1,6 +1,6 @@ - + @@ -13,43 +13,37 @@ - + - + - PDF+XML - - - - - - + PDF+XML - + - + - ZUGFeRD + ZUGFeRD - + - Folder 2 + Folder 2 @@ -62,73 +56,55 @@ - + - Schematron files - - - - - - + Schematron files - + - Schema files - - - - - - + Schema files - + - Codelist XMLs - - - - - - + Codelist XMLs - + - + - EN16931 + EN16931 - + - Folder 3 + Folder 3 @@ -141,57 +117,45 @@ - + - CEF Codelist Excel - - - - - - + CEF Codelist Excel - + - CEN Schematron - - - - - - + CEN Schematron - + - + - UN/CEFACT + UN/CEFACT - + - Folder 4 + Folder 4 @@ -204,16 +168,10 @@ - + - Schema files - - - - - - + Schema files @@ -222,39 +180,33 @@ - + - XML Report - - - - - - + XML Report - + - + - ZUV + ZUV - + - Folder 5 + Folder 5 @@ -267,167 +219,113 @@ - + - PH-Schematron - - - - - - + PH-Schematron - + - XML - - - - - - + XML - + - Metadata - - - - - - + Metadata - + - Metadata check - - - - - - + Metadata check - + - XSLT files - - - - - - + XSLT files - + - XSLT files - - - - - - + XSLT files - + - Additional data - - - - - - + Additional data - + - Additional data check - - - - - - + Additional data check - + - PH-Schematron - - - - - - + PH-Schematron - + - + - Mustangproject + Mustangproject - + - Folder 1 + Folder 1 @@ -440,57 +338,45 @@ - + - PDFBox - - - - - - + PDFBox - + - Mustang - - - - - - + Mustang - + - + - VeraPDF + VeraPDF - + - Folder 6 + Folder 6 @@ -503,16 +389,10 @@ - + - VeraPDF - - - - - - + VeraPDF @@ -521,74 +401,66 @@ - + - Schema check - - - - - - + Schema check - + - XSLT files - - - - - - + XSLT files - + - PH-Schematron - - - - - - + PH-Schematron + + + + + + + + + + + Intra-line calculation - - + - + - XRechnung + XRechnung - + - Folder 7 + Folder 7 @@ -599,19 +471,12 @@ - - + - Schematron - - - - - - + Schematron @@ -621,8 +486,8 @@ - - + + @@ -634,8 +499,8 @@ - - + + @@ -647,8 +512,8 @@ - - + + @@ -660,8 +525,8 @@ - - + + @@ -683,8 +548,8 @@ - - + + @@ -696,8 +561,8 @@ - - + + @@ -729,8 +594,8 @@ - - + + @@ -742,8 +607,8 @@ - - + + @@ -754,7 +619,10 @@ - + + + + @@ -764,9 +632,9 @@ - - - + + + @@ -777,11 +645,9 @@ - - - - - + + + @@ -792,9 +658,9 @@ - - - + + + @@ -805,9 +671,9 @@ - - - + + + @@ -818,9 +684,9 @@ - - - + + + @@ -831,9 +697,9 @@ - - - + + + @@ -844,9 +710,9 @@ - - - + + + @@ -858,8 +724,8 @@ - - + + @@ -871,8 +737,8 @@ - - + + @@ -884,8 +750,8 @@ - - + + @@ -896,9 +762,9 @@ - - - + + + @@ -910,8 +776,8 @@ - - + + @@ -923,8 +789,8 @@ - - + + @@ -933,13 +799,12 @@ - - + - - + + @@ -948,11 +813,13 @@ - - + - + + + + @@ -960,13 +827,12 @@ - - + - - + + @@ -975,11 +841,43 @@ - + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + diff --git a/doc/ZUV-Architektur.svg b/doc/ZUV-Architektur.svg index f3eda531..5b4bab18 100644 --- a/doc/ZUV-Architektur.svg +++ b/doc/ZUV-Architektur.svg @@ -1,1117 +1,6098 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - PDF+XML + PDF+XML - + - + - ZUGFeRD - + ZUGFeRD + - - - - - - - - - - - + + + + + + + + + + + - Schematron files + Schematron files - - - - - - - - - - - + + + + + + + + + + + - Schema files + Schema files - - - - - - - - - - - + + + + + + + + + + + - Codelist XMLs + Codelist XMLs - - + + - EN16931 - + EN16931 + - - - - - - - - - - - + + + + + + + + + + + - CEF Codelist Excel + CEF Codelist Excel - - - - - - - - - - - + + + + + + + + + + + - CEN Schematron + CEN Schematron - - + + - - + + - UN/CEFACT - + UN/CEFACT + - - - - - - - - - - - + + + + + + + + + + + - Schema files + Schema files - - - - + + + + - XML Report + XML Report - - + + - - + + - ZUV - + ZUV + - - - - - - - - - - - + + + + + + + + + + + - PH-Schematron + PH-Schematron - - - - - - - - - - - + + + + + + + + + + + - XML + XML - - - - - - - - - - - + + + + + + + + + + + - Metadata + Metadata - - - - - - - - - - - + + + + + + + + + + + - Metadata check + Metadata check - - - - - - - - - - - + + + + + + + + + + + - XSLT files + XSLT files - - - - - - - - - - - + + + + + + + + + + + - XSLT files + XSLT files - - - - - - - - - - - + + + + + + + + + + + - Additional data + Additional data - - - - - - - - - - - + + + + + + + + + + + - Additional data check + Additional data check - - - - - - - - - - - + + + + + + + + + + + - PH-Schematron + PH-Schematron - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - Mustangproject - + Mustangproject + - - - - - - - - - - - + + + + + + + + + + + - PDFBox + PDFBox - - - - - - - - - - - + + + + + + + + + + + - Mustang + Mustang - - - - - - - - - - + + + + + + + + + + - - + + - VeraPDF - + VeraPDF + - - - - - - - - - - - + + + + + + + + + + + - VeraPDF + VeraPDF - - - - - - - - - - - + + + + + + + + + + + - Schema check + Schema check - - - - - - - - - - - + + + + + + + + + + + - XSLT files + XSLT files - - - - - - - - - - - + + + + + + + + + + + - PH-Schematron + PH-Schematron + + + + + + + + + + + + + + + + + + + Intra-line calculation - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - + + - XRechnung - + XRechnung + - - - - + + + + - Schematron + Schematron - - + + diff --git a/doc/development_documentation.md b/doc/development_documentation.md index c08c457c..852a6071 100644 --- a/doc/development_documentation.md +++ b/doc/development_documentation.md @@ -184,7 +184,6 @@ maybe not yet even existing new release version: ``` cd validator/target -mvn install:install-file -Dfile=validator-2.17.0-SNAPSHOT-shaded.jar -Dclassifier=shaded -DgroupId="org.mustangproject" -DartifactId=validator -Dversion="2.17.0" -Dpackaging=jar -DgeneratePom=true ``` In gradle you can use something like ``` From 056d01bc49105f9d0b39c964afb6cb9648bca510 Mon Sep 17 00:00:00 2001 From: langfr Date: Mon, 28 Jul 2025 12:44:24 +0100 Subject: [PATCH 28/30] Use UTC as the defined timezone for tests. --- Mustang-CLI/pom.xml | 2 ++ library/pom.xml | 2 ++ .../org/mustangproject/ZUGFeRD/DeSerializationTest.java | 4 ++-- .../mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java | 4 ++-- pom.xml | 7 +++---- validator/pom.xml | 2 ++ 6 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Mustang-CLI/pom.xml b/Mustang-CLI/pom.xml index 3a2ae520..22f41162 100644 --- a/Mustang-CLI/pom.xml +++ b/Mustang-CLI/pom.xml @@ -106,8 +106,10 @@ org.apache.maven.plugins maven-surefire-plugin + 3.5.3 alphabetical + -Duser.timezone=UTC diff --git a/library/pom.xml b/library/pom.xml index 55bf65f5..10442661 100644 --- a/library/pom.xml +++ b/library/pom.xml @@ -179,8 +179,10 @@ org.apache.maven.plugins maven-surefire-plugin + 3.5.3 alphabetical + -Duser.timezone=UTC diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java index f57c9520..f3e68fcc 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java @@ -462,8 +462,8 @@ public class DeSerializationTest extends ResourceCase { assertEquals(globalID, fromJSON.getSender().getGlobalID()); assertEquals(globalIDScheme, fromJSON.getSender().getGlobalIDScheme()); SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd"); - assertEquals("2022-01-29", sdf.format(fromJSON.getZFItems()[0].getDetailedDeliveryPeriodFrom())); - assertEquals("2022-01-31", sdf.format(fromJSON.getZFItems()[0].getDetailedDeliveryPeriodTo())); + assertEquals("2022-01-28", sdf.format(fromJSON.getZFItems()[0].getDetailedDeliveryPeriodFrom())); + assertEquals("2022-01-30", sdf.format(fromJSON.getZFItems()[0].getDetailedDeliveryPeriodTo())); assertEquals("sender@test.org", fromJSON.getSender().getEmail()); } diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java index ec987111..df425725 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java @@ -407,7 +407,7 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { ObjectMapper mapper = new ObjectMapper(); String jsonArray = mapper.writeValueAsString(i); - JSONAssert.assertEquals("{\"documentCode\":\"380\",\"number\":\"471102\",\"currency\":\"EUR\",\"paymentTermDescription\":\"Der Betrag in Höhe von EUR 529,87 wird am 20.03.2018 von Ihrem Konto per SEPA-Lastschrift eingezogen.\\n \",\"issueDate\":1520118000000,\"deliveryDate\":1520118000000,\"sender\":{\"name\":\"Lieferant GmbH\",\"zip\":\"80333\",\"street\":\"Lieferantenstraße 20\",\"location\":\"München\",\"country\":\"DE\",\"taxID\":\"201/113/40209\",\"vatID\":\"DE123456789\",\"debitDetails\":[{\"mandate\":\"REF A-123\",\"paymentMeansCode\":\"59\",\"paymentMeansInformation\":\"SEPA direct debit\",\"iban\":\"DE21860000000086001055\"}],\"vatid\":\"DE123456789\"},\"recipient\":{\"name\":\"Kunden AG Mitte\",\"zip\":\"69876\",\"street\":\"Kundenstraße 15\",\"location\":\"Frankfurt\",\"country\":\"DE\",\"bankDetails\":[{\"paymentMeansCode\":\"58\",\"paymentMeansInformation\":\"SEPA credit transfer\",\"iban\":\"DE21860000000086001055\"}]},\"totalPrepaidAmount\":0.00,\"creditorReferenceID\":\"DE98ZZZ09999999999\",\"valid\":false,\"zfitems\":[{\"price\":9.9000,\"quantity\":20.0000,\"basisQuantity\":1.0000,\"id\":\"1\",\"product\":{\"unit\":\"H87\",\"name\":\"Trennblätter A4\",\"taxCategoryCode\":\"S\",\"vatpercent\":19.00,\"reverseCharge\":false,\"intraCommunitySupply\":false},\"value\":9.9000},{\"price\":5.5000,\"quantity\":50.0000,\"basisQuantity\":1.0000,\"id\":\"2\",\"product\":{\"unit\":\"H87\",\"name\":\"Joghurt Banane\",\"taxCategoryCode\":\"S\",\"vatpercent\":7.00,\"reverseCharge\":false,\"intraCommunitySupply\":false},\"value\":5.5000}],\"tradeSettlement\":[{\"mandate\":\"REF A-123\",\"paymentMeansCode\":\"59\",\"paymentMeansInformation\":\"SEPA direct debit\",\"iban\":\"DE21860000000086001055\"}],\"ownTaxID\":\"201/113/40209\",\"ownZIP\":\"80333\",\"ownCountry\":\"DE\",\"ownVATID\":\"DE123456789\",\"ownLocation\":\"München\",\"ownStreet\":\"Lieferantenstraße 20\"}",jsonArray,false); + JSONAssert.assertEquals("{\"documentCode\":\"380\",\"number\":\"471102\",\"currency\":\"EUR\",\"paymentTermDescription\":\"Der Betrag in Höhe von EUR 529,87 wird am 20.03.2018 von Ihrem Konto per SEPA-Lastschrift eingezogen.\\n \",\"issueDate\":1520121600000,\"deliveryDate\":1520121600000,\"sender\":{\"name\":\"Lieferant GmbH\",\"zip\":\"80333\",\"street\":\"Lieferantenstraße 20\",\"location\":\"München\",\"country\":\"DE\",\"taxID\":\"201/113/40209\",\"vatID\":\"DE123456789\",\"debitDetails\":[{\"mandate\":\"REF A-123\",\"paymentMeansCode\":\"59\",\"paymentMeansInformation\":\"SEPA direct debit\",\"iban\":\"DE21860000000086001055\"}],\"vatid\":\"DE123456789\"},\"recipient\":{\"name\":\"Kunden AG Mitte\",\"zip\":\"69876\",\"street\":\"Kundenstraße 15\",\"location\":\"Frankfurt\",\"country\":\"DE\",\"bankDetails\":[{\"paymentMeansCode\":\"58\",\"paymentMeansInformation\":\"SEPA credit transfer\",\"iban\":\"DE21860000000086001055\"}]},\"totalPrepaidAmount\":0.00,\"creditorReferenceID\":\"DE98ZZZ09999999999\",\"valid\":false,\"zfitems\":[{\"price\":9.9000,\"quantity\":20.0000,\"basisQuantity\":1.0000,\"id\":\"1\",\"product\":{\"unit\":\"H87\",\"name\":\"Trennblätter A4\",\"taxCategoryCode\":\"S\",\"vatpercent\":19.00,\"reverseCharge\":false,\"intraCommunitySupply\":false},\"value\":9.9000},{\"price\":5.5000,\"quantity\":50.0000,\"basisQuantity\":1.0000,\"id\":\"2\",\"product\":{\"unit\":\"H87\",\"name\":\"Joghurt Banane\",\"taxCategoryCode\":\"S\",\"vatpercent\":7.00,\"reverseCharge\":false,\"intraCommunitySupply\":false},\"value\":5.5000}],\"tradeSettlement\":[{\"mandate\":\"REF A-123\",\"paymentMeansCode\":\"59\",\"paymentMeansInformation\":\"SEPA direct debit\",\"iban\":\"DE21860000000086001055\"}],\"ownTaxID\":\"201/113/40209\",\"ownZIP\":\"80333\",\"ownCountry\":\"DE\",\"ownVATID\":\"DE123456789\",\"ownLocation\":\"München\",\"ownStreet\":\"Lieferantenstraße 20\"}",jsonArray,false); } catch (IOException e) { fail("IOException not expected"); @@ -418,7 +418,7 @@ public class ZF2ZInvoiceImporterTest extends ResourceCase { } } public static Date atStartOfDay(Date date) { - ZoneId tz=ZoneId.ofOffset("GMT", ZoneOffset.ofHours(+2)); + ZoneId tz=ZoneId.ofOffset("UTC", ZoneOffset.ofHours(0)); LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), tz); LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN); return Date.from(startOfDay.atZone(tz).toInstant()); diff --git a/pom.xml b/pom.xml index 52b863b8..b5c94c61 100644 --- a/pom.xml +++ b/pom.xml @@ -81,17 +81,16 @@ org.apache.maven.plugins maven-surefire-plugin + 3.5.3 alphabetical + -Duser.timezone=UTC org.apache.maven.plugins maven-source-plugin - 3.2.1 - - UTF-8 - + 3.3.1 attach-javadoc diff --git a/validator/pom.xml b/validator/pom.xml index f5b5281a..57866cba 100644 --- a/validator/pom.xml +++ b/validator/pom.xml @@ -129,8 +129,10 @@ org.apache.maven.plugins maven-surefire-plugin + 3.5.3 alphabetical + -Duser.timezone=UTC From 5555db58aa6e3a0aaac499571d3996fd35aafac4 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Tue, 29 Jul 2025 11:15:28 +0200 Subject: [PATCH 29/30] also read/import product discounts --- doc/development_documentation.md | 18 ++- .../main/java/org/mustangproject/Item.java | 49 ++++-- .../main/java/org/mustangproject/Product.java | 18 +++ .../ZUGFeRD/CalculationTest.java | 18 +-- .../ZUGFeRD/DeSerializationTest.java | 13 +- .../mustangproject/ZUGFeRD/ZF2PushTest.java | 144 +++++++++++++----- .../ZUGFeRD/ZF2ZInvoiceImporterTest.java | 2 +- 7 files changed, 199 insertions(+), 63 deletions(-) diff --git a/doc/development_documentation.md b/doc/development_documentation.md index 852a6071..e940ebda 100644 --- a/doc/development_documentation.md +++ b/doc/development_documentation.md @@ -57,12 +57,26 @@ to validate the XML part of the invoices. ![Architecture of the validator](ZUV-Architektur.svg "Graph of the architecture of the validator component") -## New build +## Aspects +Apart from the fact that apart from +* the code +* we need tests and apart from implementing it in -Target platform is java 1.17 +* the interface +* usually we need functionality in or via the invoice class. + +Reading should work for both +* CII and +* UBL + +And when writing, +* it should be readable as well, usually in the invoiceimporter, +* and it should be readable and writeable via Jackson (i.e. JSON) ## Build +Target platform is java 1.17 + The package can be build with ``` mvnw clean package diff --git a/library/src/main/java/org/mustangproject/Item.java b/library/src/main/java/org/mustangproject/Item.java index c062e620..d2add5d7 100644 --- a/library/src/main/java/org/mustangproject/Item.java +++ b/library/src/main/java/org/mustangproject/Item.java @@ -120,10 +120,14 @@ public class Item implements IZUGFeRDExportableItem { itemMap.getAsString("ID") .ifPresent(this::setId); - itemMap.getAsString("Note") .ifPresent(this::addNote); + if (itemMap.getNode("SpecifiedTradeProduct").isPresent()) { + product = new Product(itemMap.getNode("SpecifiedTradeProduct").get()); + } else { + product = new Product(); + } itemMap.getAsNodeMap("SpecifiedLineTradeAgreement", "SpecifiedSupplyChainTradeAgreement").ifPresent(icnm -> { icnm.getAsNodeMap("BuyerOrderReferencedDocument") @@ -138,14 +142,39 @@ public class Item implements IZUGFeRDExportableItem { npptpNodes.getAsBigDecimal("ChargeAmount").ifPresent(this::setPrice); npptpNodes.getAsBigDecimal("BasisQuantity").ifPresent(this::setBasisQuantity); }); + icnm.getAsNodeMap("GrossPriceProductTradePrice").ifPresent(gpptpNodes -> { + gpptpNodes.getAsNodeMap("AppliedTradeAllowanceCharge").ifPresent(gpptpAtacNodes -> { + + /** mustang attributes differences between net and gross price to the product */ + String chargeIndicator = gpptpAtacNodes.getAsStringOrNull("ChargeIndicator"); + if ((chargeIndicator != null)&&(gpptpAtacNodes.getAsBigDecimal("ActualAmount").isPresent())) { + BigDecimal actual = gpptpAtacNodes.getAsBigDecimal("ActualAmount").get(); + if (chargeIndicator.equals("true")) { + product.addCharge(new Charge(actual)); + setPrice(getPrice().subtract(actual)); // the gross price affects the net price, which is read, + // so if we do not ignore charges|allowances we have to re-compensate the net price + } else { + product.addAllowance(new Allowance(actual)); + setPrice(getPrice().add(actual)); + } + + } + }); + }); + + + /* + String chargeIndicator = gpptpNodes.getAsNodeMap("AppliedTradeAllowanceCharge").flatMap(acChargeIndicatorNodes -> acChargeIndicatorNodes.getAsString("ChargeIndicator")).get(); + if (chargeIndicator != null) { + BigDecimal actual = gpptpNodes.getAsNodeMap("AppliedTradeAllowanceCharge").flatMap(acChargeIndicatorNodes -> acChargeIndicatorNodes.getAsBigDecimal("ActualAmount")).get(); + if (actual != null) { + } + }*/ icnm.getAllNodes("AdditionalReferencedDocument").map(ReferencedDocument::fromNode). forEach(this::addReferencedDocument); }); - itemMap.getNode("SpecifiedTradeProduct").map(Product::new).ifPresent(this::setProduct);//CII - itemMap.getNode("SpecifiedTradeProduct").map(Product::new).ifPresent(this::setProduct);//UBL - // RequestedQuantity is for Order-X, BilledQuantity for FX and ZF itemMap.getAsNodeMap("SpecifiedLineTradeDelivery", "SpecifiedSupplyChainTradeDelivery") .flatMap(icnm -> icnm.getNode("BilledQuantity", "RequestedQuantity", "DespatchedQuantity")) @@ -182,7 +211,7 @@ public class Item implements IZUGFeRDExportableItem { } if (amountString != null) { izac.setTotalAmount(new BigDecimal(amountString)); - if (percentString!=null&&(!percentString.equals("0"))) { + if (percentString != null && (!percentString.equals("0"))) { izac.setTotalAmount(new BigDecimal(amountString).divide(getQuantity())); } } @@ -304,14 +333,16 @@ public class Item implements IZUGFeRDExportableItem { } @JsonIgnore - @Override public IZUGFeRDAllowanceCharge[] getAllowances() { // in JSON is already returned as itemAllowances (and only read from there) - IZUGFeRDAllowanceCharge[] izac=new IZUGFeRDAllowanceCharge[Allowances.size()]; + @Override + public IZUGFeRDAllowanceCharge[] getAllowances() { // in JSON is already returned as itemAllowances (and only read from there) + IZUGFeRDAllowanceCharge[] izac = new IZUGFeRDAllowanceCharge[Allowances.size()]; return Allowances.toArray(izac); } @JsonIgnore - @Override public IZUGFeRDAllowanceCharge[] getCharges() { // in JSON is already returned as itemAllowances (and only read from there) - IZUGFeRDAllowanceCharge[] izac=new IZUGFeRDAllowanceCharge[Charges.size()]; + @Override + public IZUGFeRDAllowanceCharge[] getCharges() { // in JSON is already returned as itemAllowances (and only read from there) + IZUGFeRDAllowanceCharge[] izac = new IZUGFeRDAllowanceCharge[Charges.size()]; return Charges.toArray(izac); } diff --git a/library/src/main/java/org/mustangproject/Product.java b/library/src/main/java/org/mustangproject/Product.java index 93e48a79..c394b7a4 100644 --- a/library/src/main/java/org/mustangproject/Product.java +++ b/library/src/main/java/org/mustangproject/Product.java @@ -406,6 +406,16 @@ public class Product implements IZUGFeRDExportableProduct { return this; } + + /*** + * Jackson courtesy function, please use addCharge if you have the choice + * @return array of or null, if none + */ + public Product setCharges(ArrayList charges) { + this.charges=charges; + return this; + } + /*** * returns the AppliedTradeAllowanceCharges of this product which are actually Charges * @return array of or null, if none @@ -432,5 +442,13 @@ public class Product implements IZUGFeRDExportableProduct { return allowances.toArray(allowanceArr); } + /*** + * Jackson courtesy function, please use addAllowance if you have the choice + * @return array of or null, if none + */ + public Product setAllowances(ArrayList allowances) { + this.allowances=allowances; + return this; + } } diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java index ebdcf643..f386d655 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/CalculationTest.java @@ -106,14 +106,14 @@ public class CalculationTest extends ResourceCase { Product product; Item item; - product = new Product("Pens", "", "H84", new BigDecimal(25)); + product = new Product("Pens", "", "H87", new BigDecimal(25)); product.addAllowance(new Allowance(new BigDecimal(1))); item = new Item(product, new BigDecimal("9.50"), new BigDecimal(25)); item.addCharge(new Charge(new BigDecimal(10)).setReasonCode("ZZZ").setReason("Zuschlag")); LineCalculator lc = new LineCalculator(item); assertEquals(new BigDecimal("222.50"), lc.getItemTotalNetAmount()); invoice.addItem(item); - product = new Product("Paper", "", "H84", new BigDecimal(25)); + product = new Product("Paper", "", "H87", new BigDecimal(25)); item = new Item(product, new BigDecimal("4.50"), new BigDecimal(15)); item.addAllowance(new Allowance().setPercent(new BigDecimal(5)).setReasonCode("ZZZ").setReason("Zuschlag")); lc = new LineCalculator(item); @@ -201,7 +201,7 @@ public class CalculationTest extends ResourceCase { Product product; Item item; - product = new Product("AAA", "", "H84", sales_tax_percent1).setSellerAssignedID("1AAA"); + product = new Product("AAA", "", "H87", sales_tax_percent1).setSellerAssignedID("1AAA"); item = new Item(product, new BigDecimal("4.750"), new BigDecimal(5.00)); // set values for additional charge and discount used for next lines @@ -218,19 +218,19 @@ public class CalculationTest extends ResourceCase { invoice.addItem(item); - product = new Product("BBB", "", "H84", sales_tax_percent1).setSellerAssignedID("2BBB"); + product = new Product("BBB", "", "H87", sales_tax_percent1).setSellerAssignedID("2BBB"); item = new Item(product, new BigDecimal("5.750"), new BigDecimal(4.00)); invoice.addItem(item); - product = new Product("CCC", "", "H84", sales_tax_percent1).setSellerAssignedID("3CCC"); + product = new Product("CCC", "", "H87", sales_tax_percent1).setSellerAssignedID("3CCC"); item = new Item(product, new BigDecimal("6.750"), new BigDecimal(3.00)); invoice.addItem(item); - product = new Product("DDD", "", "H84", sales_tax_percent1).setSellerAssignedID("4DDD"); + product = new Product("DDD", "", "H87", sales_tax_percent1).setSellerAssignedID("4DDD"); item = new Item(product, new BigDecimal("7.750"), new BigDecimal(2.00)); invoice.addItem(item); - product = new Product("EEE", "", "H84", sales_tax_percent1).setSellerAssignedID("5EEE"); + product = new Product("EEE", "", "H87", sales_tax_percent1).setSellerAssignedID("5EEE"); item = new Item(product, new BigDecimal("8.750"), new BigDecimal(1.00)); invoice.addItem(item); @@ -277,7 +277,7 @@ public class CalculationTest extends ResourceCase { Product product; Item item; - product = new Product("AAA", "", "H84", BigDecimal.ZERO); + product = new Product("AAA", "", "H87", BigDecimal.ZERO); item = new Item(product, new BigDecimal("1.10"), new BigDecimal(5.00)); item.addAllowance(new Allowance().setPercent(new BigDecimal(10)).setTaxPercent(BigDecimal.ZERO)); @@ -369,7 +369,7 @@ public class CalculationTest extends ResourceCase { Product product; Item item; - product = new Product("AAA", "", "H84", BigDecimal.ZERO); + product = new Product("AAA", "", "H87", BigDecimal.ZERO); item = new Item(product, new BigDecimal("1.00"), new BigDecimal(5.00)); item.addAllowance(new Allowance(new BigDecimal(1)).setTaxPercent(BigDecimal.ZERO)); diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java index f57c9520..c0ac9f3a 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/DeSerializationTest.java @@ -446,9 +446,8 @@ public class DeSerializationTest extends ResourceCase { } assertEquals(newInvoiceFromJSON.getBuyerOrderReferencedDocumentID(), "28934"); assertFalse(hasExceptions); - - } + public void testFromJSON() throws JsonProcessingException { String globalID = "4000001123452"; String globalIDScheme = "0088"; @@ -467,6 +466,16 @@ public class DeSerializationTest extends ResourceCase { assertEquals("sender@test.org", fromJSON.getSender().getEmail()); } + public void testGrossFromJSON() throws JsonProcessingException { + + String json="{ \"documentCode\": \"380\", \"number\": \"123\", \"currency\": \"EUR\", \"paymentTermDescription\": \"Please remit until 28.07.2025\", \"issueDate\": 1753653600000, \"dueDate\": 1753653600000, \"sender\": { \"name\": \"Test company\", \"zip\": \"55232\", \"street\": \"teststr\", \"location\": \"teststadt\", \"country\": \"DE\", \"taxID\": \"4711\", \"vatID\": \"DE0815\", \"vatid\": \"DE0815\" }, \"recipient\": { \"name\": \"Franz Müller\", \"zip\": \"55232\", \"street\": \"teststr.12\", \"location\": \"Entenhausen\", \"country\": \"DE\", \"contact\": { \"name\": \"contact testname\", \"phone\": \"123456\", \"email\": \"contact.testemail@example.org\", \"fax\": \"0911623562\" } }, \"totalPrepaidAmount\": 0.00, \"lineTotalAmount\": 29.00, \"duePayable\": 34.51, \"grandTotal\": 34.51, \"taxBasis\": 29.00, \"valid\": true, \"zfitems\": [ { \"price\": 3.0000, \"quantity\": 10.0000, \"basisQuantity\": 1.0000, \"id\": \"1\", \"product\": { \"unit\": \"H87\", \"name\": \"Testprodukt\", \"taxCategoryCode\": \"S\", \"allowances\": [ { \"totalAmount\": 0.1000, \"categoryCode\": \"S\" } ], \"vatpercent\": 19.00, \"intraCommunitySupply\": false, \"reverseCharge\": false }, \"value\": 3.0000 } ], \"ownVATID\": \"DE0815\", \"ownTaxID\": \"4711\", \"ownLocation\": \"teststadt\", \"ownZIP\": \"55232\", \"ownCountry\": \"DE\", \"ownStreet\": \"teststr\"}"; + + ObjectMapper mapper = new ObjectMapper(); + CalculatedInvoice fromJSON = mapper.readValue(json, CalculatedInvoice.class); + fromJSON.calculate(); + assertEquals(new BigDecimal("34.51"),fromJSON.getDuePayable()); + } + public void testDueDateRoundtrip() throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java index a72a7953..9ad9cd53 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java @@ -53,6 +53,7 @@ public class ZF2PushTest extends TestCase { final String TARGET_ALLOWANCESPDF = "./target/testout-ZF2PushAllowances.pdf"; final String TARGET_CREDITNOTEPDF = "./target/testout-ZF2PushCreditNote.pdf"; final String TARGET_CORRECTIONPDF = "./target/testout-ZF2PushCorrection.pdf"; + final String TARGET_ITEMGROSS = "./target/testout-ZF2PushGross.pdf"; final String TARGET_ITEMCHARGESALLOWANCESPDF = "./target/testout-ZF2PushItemChargesAllowances.pdf"; final String TARGET_CHARGESALLOWANCESPDF = "./target/testout-ZF2PushChargesAllowances.pdf"; final String TARGET_RELATIVECHARGESALLOWANCESPDF = "./target/testout-ZF2PushRelativeChargesAllowances.pdf"; @@ -159,7 +160,7 @@ public class ZF2PushTest extends TestCase { .setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addVATID("DE4711") .setContact(new Contact("Franz Müller", "01779999999", "franz@mueller.de", "teststr. 12", "55232", "Entenhausen", "DE"))) .setNumber(number) - .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(0)).setTaxExemptionReason("Kleinunternehmer gemäß §19 UStG").setTaxCategoryCode("E"), price, new BigDecimal(1.0)).addNote(theNote)) + .addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(0)).setTaxExemptionReason("Kleinunternehmer gemäß §19 UStG").setTaxCategoryCode("E"), price, new BigDecimal(1.0)).addNote(theNote)) ); String theXML = new String(ze.getProvider().getXML()); assertTrue(theXML.contains(" Date: Wed, 30 Jul 2025 10:18:13 +0200 Subject: [PATCH 30/30] corrected UBL behaviour (ignorance) --- .../main/java/org/mustangproject/Item.java | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/library/src/main/java/org/mustangproject/Item.java b/library/src/main/java/org/mustangproject/Item.java index d2add5d7..b8afa6be 100644 --- a/library/src/main/java/org/mustangproject/Item.java +++ b/library/src/main/java/org/mustangproject/Item.java @@ -123,10 +123,12 @@ public class Item implements IZUGFeRDExportableItem { itemMap.getAsString("Note") .ifPresent(this::addNote); - if (itemMap.getNode("SpecifiedTradeProduct").isPresent()) { - product = new Product(itemMap.getNode("SpecifiedTradeProduct").get()); - } else { - product = new Product(); + if (product==null) { // CII + if (itemMap.getNode("SpecifiedTradeProduct").isPresent()) { + product = new Product(itemMap.getNode("SpecifiedTradeProduct").get()); + } else { + product = new Product(); + } } itemMap.getAsNodeMap("SpecifiedLineTradeAgreement", "SpecifiedSupplyChainTradeAgreement").ifPresent(icnm -> { @@ -161,16 +163,6 @@ public class Item implements IZUGFeRDExportableItem { } }); }); - - - /* - String chargeIndicator = gpptpNodes.getAsNodeMap("AppliedTradeAllowanceCharge").flatMap(acChargeIndicatorNodes -> acChargeIndicatorNodes.getAsString("ChargeIndicator")).get(); - if (chargeIndicator != null) { - BigDecimal actual = gpptpNodes.getAsNodeMap("AppliedTradeAllowanceCharge").flatMap(acChargeIndicatorNodes -> acChargeIndicatorNodes.getAsBigDecimal("ActualAmount")).get(); - if (actual != null) { - } - }*/ - icnm.getAllNodes("AdditionalReferencedDocument").map(ReferencedDocument::fromNode). forEach(this::addReferencedDocument); }); @@ -250,7 +242,7 @@ public class Item implements IZUGFeRDExportableItem { }); }); - itemMap.getAllNodes("AllowanceCharge").map(NodeMap::new).forEach(stac -> { //UBL + itemMap.getAllNodes("AllowanceCharge").map(NodeMap::new).forEach(stac -> { //CII String isChargeString = stac.getAsString("ChargeIndicator").get(); String percentString = stac.getAsStringOrNull("MultiplierFactorNumeric");