From 337ed2e99ee2c3d91ad93d1fd625f6d7fa87250d Mon Sep 17 00:00:00 2001 From: Sebastian Sieber Date: Mon, 9 Nov 2020 15:34:43 +0100 Subject: [PATCH 01/16] Correct tax category code Use TaxCategoryCode instead of DocumentCode by creating VATPercentAmountMap. --- .../org/mustangproject/ZUGFeRD/TransactionCalculator.java | 2 +- .../java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java | 4 +--- .../java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java | 4 +--- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java index 414ef7bf..839c9b27 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java @@ -146,7 +146,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { BigDecimal percent = currentItem.getProduct().getVATPercent(); LineCalculator lc = new LineCalculator(currentItem); VATAmount itemVATAmount = new VATAmount(lc.getItemTotalNetAmount(), lc.getItemTotalVATAmount(), - trans.getDocumentCode()); + currentItem.getCategoryCode()); VATAmount current = hm.get(percent); if (current == null) { hm.put(percent, itemVATAmount); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java index 898c177d..9f954256 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java @@ -107,7 +107,6 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr this.calc=new TransactionCalculator(trans); boolean hasDueDate=false; - String taxCategoryCode=""; SimpleDateFormat germanDateFormat = new SimpleDateFormat("dd.MM.yyyy"); String exemptionReason=""; @@ -261,7 +260,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr + " VAT\n" + exemptionReason + " " + currencyFormat(amount.getBasis()) + "\n" // currencyID=\"EUR\" - + " "+taxCategoryCode+"\n" + + " "+amount.getCategoryCode()+"\n" + " " + vatFormat(currentTaxPercent) + "\n" + " \n"; //$NON-NLS-2$ @@ -318,7 +317,6 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr int lineID = 0; for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) { lineID++; - taxCategoryCode=currentItem.getProduct().getTaxCategoryCode(); if (currentItem.getProduct().getTaxExemptionReason() != null) { exemptionReason="" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + ""; } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index b9d96e5a..4895122f 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -218,7 +218,6 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { this.calc = new TransactionCalculator(trans); boolean hasDueDate = false; - String taxCategoryCode = ""; SimpleDateFormat germanDateFormat = new SimpleDateFormat("dd.MM.yyyy"); String exemptionReason = ""; @@ -298,7 +297,6 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { int lineID = 0; for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) { lineID++; - taxCategoryCode = currentItem.getProduct().getTaxCategoryCode(); if (currentItem.getProduct().getTaxExemptionReason() != null) { exemptionReason = "" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + ""; } @@ -492,7 +490,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { + " VAT\n" + exemptionReason + " " + currencyFormat(amount.getBasis()) + "\n" // currencyID=\"EUR\" - + " " + taxCategoryCode + "\n" + + " " + amount.getCategoryCode() + "\n" + " " + vatFormat(currentTaxPercent) + "\n" + " \n"; //$NON-NLS-2$ From 5b8d1c9c544ec3827944b24cfc1e163de4cc4b9e Mon Sep 17 00:00:00 2001 From: jstaerk Date: Tue, 10 Nov 2020 12:03:38 +0100 Subject: [PATCH 02/16] support document level charges/allowances in invoiceimporter --- History.md | 8 +-- Release_Notes.md | 2 +- .../ZUGFeRD/TransactionCalculator.java | 3 +- .../ZUGFeRD/ZUGFeRDInvoiceImporter.java | 71 +++++++++++++++++++ .../ZUGFeRD/ZF2ZInvoiceImporterTest.java | 43 ++++++++++- 5 files changed, 117 insertions(+), 10 deletions(-) diff --git a/History.md b/History.md index 4bfc257a..3927a8af 100644 --- a/History.md +++ b/History.md @@ -54,13 +54,11 @@ switch ### 2.0 still todo - dont show empty tax number field -- new sample invoice -- *visualizer to work with Extended profile -- *visualizer tests +- fail when no bankverbindung? +- fail when xr attrs missing? +- build xr skonto??? - *validator not to XR error on ZF files (only notices) -- *from A3 does not seem tow ork see mustangreaderwriteredgetest:testedgeexport - xmp errors may not show correctly in log -- finalize invoiceimporter Alpha3 2020-10-24 Alpha2 2020-09-15 Alpha1 2020-08-06 diff --git a/Release_Notes.md b/Release_Notes.md index f8c53e82..705bba7c 100644 --- a/Release_Notes.md +++ b/Release_Notes.md @@ -145,7 +145,7 @@ but there is also the new invoiceImporter assertEquals("Spielkreis", invoice.getRecipient().getLocation()); TransactionCalculator tc=new TransactionCalculator(invoice); - assertEquals(new BigDecimal("571.040000"),tc.getTotalGross()); + assertEquals(new BigDecimal("571.04"),tc.getTotalGross()); ``` diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java index 839c9b27..084b0156 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java @@ -1,6 +1,7 @@ package org.mustangproject.ZUGFeRD; import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.HashMap; /*** @@ -30,7 +31,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { VATAmount amount = VATPercentAmountMap.get(currentTaxPercent); res = res.add(amount.getCalculated()); } - return res; + return res.setScale(2, RoundingMode.HALF_UP); } /*** diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index 042398f9..2285bd8a 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -40,6 +40,13 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter { "//*[local-name()=\"ExchangedDocument\"]"); NodeList ExchangedDocumentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); + xpr = xpath.compile( + "//*[local-name()=\"GrandTotalAmount\"]"); + BigDecimal expectedGrandTotal = null; + NodeList totalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); + if (totalNodes.getLength() > 0) { + expectedGrandTotal = new BigDecimal(totalNodes.item(0).getTextContent()); + } Date issueDate = null; Date dueDate = null; @@ -192,6 +199,70 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter { } + // item level charges+allowances are not yet handled but a lower item price will be read, + // so the invoice remains arithmetically correct + // -> parse document level charges+allowances + xpr = xpath.compile( + "//*[local-name()=\"SpecifiedTradeAllowanceCharge\"]"); + NodeList chargeNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); + for (int i = 0; i < chargeNodes.getLength(); i++) { + NodeList chargeNodeChilds = chargeNodes.item(i).getChildNodes(); + boolean isCharge = true; + String chargeAmount = null; + String reason = null; + String taxPercent = null; + for (int chargeChildIndex = 0; chargeChildIndex < chargeNodeChilds.getLength(); chargeChildIndex++) { + if (chargeNodeChilds.item(chargeChildIndex).getNodeName().equals("ram:ChargeIndicator")) { + NodeList indicatorChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes(); + for (int indicatorChildIndex = 0; indicatorChildIndex < indicatorChilds.getLength(); indicatorChildIndex++) { + if (indicatorChilds.item(indicatorChildIndex).getNodeName().equals("udt:Indicator")) { + isCharge = indicatorChilds.item(indicatorChildIndex).getTextContent().equalsIgnoreCase("true"); + } + } + } else if (chargeNodeChilds.item(chargeChildIndex).getNodeName().equals("ram:ActualAmount")) { + chargeAmount = chargeNodeChilds.item(chargeChildIndex).getTextContent(); + } else if (chargeNodeChilds.item(chargeChildIndex).getNodeName().equals("ram:Reason")) { + reason = chargeNodeChilds.item(chargeChildIndex).getTextContent(); + } else if (chargeNodeChilds.item(chargeChildIndex).getNodeName().equals("ram:CategoryTradeTax")) { + NodeList taxChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes(); + for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) { + if (taxChilds.item(taxChildIndex).getNodeName().equals("ram:RateApplicablePercent")) { + taxPercent = taxChilds.item(taxChildIndex).getTextContent(); + } + } + } + + } + + + if (isCharge) { + Charge c = new Charge(new BigDecimal(chargeAmount)); + if (reason != null) { + c.setReason(reason); + } + if (taxPercent != null) { + c.setTaxPercent(new BigDecimal(taxPercent)); + } + + zpp.addCharge(c); + } else { + Allowance a = new Allowance(new BigDecimal(chargeAmount)); + if (reason != null) { + a.setReason(reason); + } + if (taxPercent != null) { + a.setTaxPercent(new BigDecimal(taxPercent)); + } + zpp.addAllowance(a); + } + + } + + TransactionCalculator tc = new TransactionCalculator(zpp); + String expectedStringTotalGross = tc.getTotalGross().toPlainString(); + if (!expectedStringTotalGross.equals(XMLTools.nDigitFormat(expectedGrandTotal, 2))) { + throw new ParseException("Could not reproduce the invoice, this could mean that it could not be read properly", 0); + } return zpp; } diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java index 01adc049..a56f637b 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2ZInvoiceImporterTest.java @@ -41,11 +41,10 @@ import java.util.GregorianCalendar; * used for this import, testout-ZF2New.pdf */ public class ZF2ZInvoiceImporterTest extends TestCase { - final String TARGET_PDF = "./target/testout-ZF2new.pdf"; public void testInvoiceImport() { - ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter(TARGET_PDF); + ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter("./target/testout-ZF2new.pdf"); boolean hasExceptions=false; Invoice invoice=null; @@ -82,7 +81,44 @@ public class ZF2ZInvoiceImporterTest extends TestCase { assertEquals("Stadthausen", invoice.getSender().getLocation()); TransactionCalculator tc=new TransactionCalculator(invoice); - assertEquals(new BigDecimal("571.040000"),tc.getTotalGross()); + assertEquals(new BigDecimal("571.04"),tc.getTotalGross()); + + + // name street location zip country, contact name phone email, total amount + + + + } + public void testItemAllowancesChargesImport() { + + ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter("./target/testout-ZF2PushItemChargesAllowances.pdf"); + + boolean hasExceptions=false; + Invoice invoice=null; + try { + invoice=zii.extractInvoice(); + } catch (XPathExpressionException | ParseException e) { + hasExceptions=true; + } + assertFalse(hasExceptions); + TransactionCalculator tc=new TransactionCalculator(invoice); + assertEquals(new BigDecimal("18.33"),tc.getTotalGross()); + } + + public void testAllowancesChargesImport() { + + ZUGFeRDInvoiceImporter zii=new ZUGFeRDInvoiceImporter("./target/testout-ZF2PushChargesAllowances.pdf"); + + boolean hasExceptions=false; + Invoice invoice=null; + try { + invoice=zii.extractInvoice(); + } catch (XPathExpressionException | ParseException e) { + hasExceptions=true; + } + assertFalse(hasExceptions); + TransactionCalculator tc=new TransactionCalculator(invoice); + assertEquals(new BigDecimal("11.07"),tc.getTotalGross()); // name street location zip country, contact name phone email, total amount @@ -91,4 +127,5 @@ public class ZF2ZInvoiceImporterTest extends TestCase { } + } From 040dc683dc22554e7691d9ed4e12068cbaeeaff8 Mon Sep 17 00:00:00 2001 From: Ulrich Zielhofer Date: Tue, 10 Nov 2020 13:05:37 +0100 Subject: [PATCH 03/16] shaded artifacts attached --- library/pom.xml | 1 + validator/pom.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/library/pom.xml b/library/pom.xml index c1eb159a..01e43ddc 100644 --- a/library/pom.xml +++ b/library/pom.xml @@ -131,6 +131,7 @@ maven-shade-plugin 3.2.1 + true org.mustangproject.toecount.Toecount diff --git a/validator/pom.xml b/validator/pom.xml index 92b475b4..77781d5a 100644 --- a/validator/pom.xml +++ b/validator/pom.xml @@ -171,6 +171,7 @@ maven-shade-plugin 2.4.3 + true false From e1881a3891c92fda5a9d6721e5ad7c5a42497f67 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Wed, 11 Nov 2020 21:15:49 +0100 Subject: [PATCH 04/16] closes #190 --- History.md | 1 + .../java/org/mustangproject/XMLTools.java | 136 +++++++++++++---- .../ZUGFeRD/ZUGFeRD1PullProvider.java | 138 +++++++++--------- .../ZUGFeRD/ZUGFeRD2PullProvider.java | 8 +- validator/pom.xml | 4 +- .../validator/ValidationResultItem.java | 1 + .../mustangproject/validator/XMLTools.java | 14 -- .../validator/XMLValidator.java | 9 +- .../validator/ZUGFeRDValidator.java | 20 ++- validator/src/test/resources/validV2.xml | 2 +- 10 files changed, 193 insertions(+), 140 deletions(-) delete mode 100644 validator/src/main/java/org/mustangproject/validator/XMLTools.java diff --git a/History.md b/History.md index 3927a8af..c8f90dc0 100644 --- a/History.md +++ b/History.md @@ -51,6 +51,7 @@ switch - contacts also for recipients - absolute and relative allowances and charges on item and document level #135, - support contact fax numbers + - closes #190 BOM not treated correctly on XML input file ### 2.0 still todo - dont show empty tax number field diff --git a/library/src/main/java/org/mustangproject/XMLTools.java b/library/src/main/java/org/mustangproject/XMLTools.java index cf3d6386..402a6dfd 100644 --- a/library/src/main/java/org/mustangproject/XMLTools.java +++ b/library/src/main/java/org/mustangproject/XMLTools.java @@ -1,9 +1,25 @@ package org.mustangproject; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.math.RoundingMode; +import java.util.logging.Level; +import java.util.logging.Logger; -public class XMLTools { +import org.dom4j.io.XMLWriter; +import org.mustangproject.ZUGFeRD.ZUGFeRD2PullProvider; + +public class XMLTools extends XMLWriter { + public String escapeAttributeEntities(String s) { + return super.escapeAttributeEntities(s); + } + + public String escapeElementEntities(String s) { + return super.escapeElementEntities(s); + + } public static String nDigitFormat(BigDecimal value, int scale) { @@ -28,42 +44,98 @@ public class XMLTools { public static String encodeXML(CharSequence s) { - StringBuilder sb = new StringBuilder(); - int len = s.length(); - for (int i=0;i= 0xd800 && c <= 0xdbff && i + 1 < len) { - c = ((c-0xd7c0)<<10) | (s.charAt(++i)&0x3ff); // UTF16 decode - } - if (c < 0x80) { // ASCII range: test most common case first - if (c < 0x20 && (c != '\t' && c != '\r' && c != '\n')) { - // Illegal XML character, even encoded. Skip or substitute - sb.append("�"); // Unicode replacement character - } else { - switch(c) { - case '&': sb.append("&"); break; - case '>': sb.append(">"); break; - case '<': sb.append("<"); break; - // Uncomment next two if encoding for an XML attribute + StringBuilder sb = new StringBuilder(); + int len = s.length(); + for (int i = 0; i < len; i++) { + int c = s.charAt(i); + if (c >= 0xd800 && c <= 0xdbff && i + 1 < len) { + c = ((c - 0xd7c0) << 10) | (s.charAt(++i) & 0x3ff); // UTF16 decode + } + if (c < 0x80) { // ASCII range: test most common case first + if (c < 0x20 && (c != '\t' && c != '\r' && c != '\n')) { + // Illegal XML character, even encoded. Skip or substitute + sb.append("�"); // Unicode replacement character + } else { + switch (c) { + case '&': + sb.append("&"); + break; + case '>': + sb.append(">"); + break; + case '<': + sb.append("<"); + break; + // Uncomment next two if encoding for an XML attribute // case '\'' sb.append("'"); break; // case '\"' sb.append("""); break; - // Uncomment next three if you prefer, but not required + // Uncomment next three if you prefer, but not required // case '\n' sb.append(" "); break; // case '\r' sb.append(" "); break; // case '\t' sb.append(" "); break; - default: sb.append((char)c); - } - } - } else if ((c >= 0xd800 && c <= 0xdfff) || c == 0xfffe || c == 0xffff) { - // Illegal XML character, even encoded. Skip or substitute - sb.append("�"); // Unicode replacement character - } else { - sb.append("&#x"); - sb.append(Integer.toHexString(c)); - sb.append(';'); - } - } - return sb.toString(); + default: + sb.append((char) c); + } + } + } else if ((c >= 0xd800 && c <= 0xdfff) || c == 0xfffe || c == 0xffff) { + // Illegal XML character, even encoded. Skip or substitute + sb.append("�"); // Unicode replacement character + } else { + sb.append("&#x"); + sb.append(Integer.toHexString(c)); + sb.append(';'); + } + } + return sb.toString(); } + + + /** + * Returns the Byte Order Mark size and thus allows to skips over a BOM + * at the beginning of the given ByteArrayInputStream, if one exists. + * + * @param is the ByteArrayInputStream used + * @throws IOException if can not be read from is + * @see Autodetection of Character Encodings + */ + public static int guessBOMSize(ByteArrayInputStream is) throws IOException { + byte[] pad = new byte[4]; + is.read(pad); + is.reset(); + int test2 = ((pad[0] & 0xFF) << 8) | (pad[1] & 0xFF); + int test3 = ((test2 & 0xFFFF) << 8) | (pad[2] & 0xFF); + int test4 = ((test3 & 0xFFFFFF) << 8) | (pad[3] & 0xFF); + // + if (test4 == 0x0000FEFF || test4 == 0xFFFE0000 || test4 == 0x0000FFFE || test4 == 0xFEFF0000) { + // UCS-4: BOM takes 4 bytes + return 4; + } else if (test3 == 0xEFBBFF) { + // UTF-8: BOM takes 3 bytes + return 3; + } else if (test2 == 0xFEFF || test2 == 0xFFFE) { + // UTF-16: BOM takes 2 bytes + return 2; + } + return 0; + } + + /*** + * removes utf8 byte order marks from byte arrays, in case one is there + * @param zugferdRaw + * @return the byte array without bom + */ + public static byte[] removeBOM(byte[] zugferdRaw) { + byte[] zugferdData; + if ((zugferdRaw[0] == (byte) 0xEF) && (zugferdRaw[1] == (byte) 0xBB) && (zugferdRaw[2] == (byte) 0xBF)) { + // I don't like BOMs, lets remove it + zugferdData = new byte[zugferdRaw.length - 3]; + System.arraycopy(zugferdRaw, 3, zugferdData, 0, zugferdRaw.length - 3); + } else { + zugferdData = zugferdRaw; + } + return zugferdData; + } + + } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java index 9f954256..2bc7319b 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java @@ -1,21 +1,23 @@ -/** ********************************************************************** - * +/** + * ********************************************************************* + *

* Copyright 2018 Jochen Staerk - * + *

* Use is subject to license terms. - * + *

* Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at http://www.apache.org/licenses/LICENSE-2.0. - * + *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * + *

* See the License for the specific language governing permissions and * limitations under the License. - * - *********************************************************************** */ + *

+ * ********************************************************************** + */ package org.mustangproject.ZUGFeRD; import org.dom4j.Document; @@ -43,7 +45,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr protected byte[] zugferdData; private String paymentTermsDescription; SimpleDateFormat zugferdDateFormat = new SimpleDateFormat("yyyyMMdd"); - protected Profile profile=Profiles.getByName("COMFORT",1); + protected Profile profile = Profiles.getByName("COMFORT", 1); /** @@ -52,6 +54,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr @Override public void setTest() { } + private String vatFormat(BigDecimal value) { return XMLTools.nDigitFormat(value, 2); } @@ -104,19 +107,19 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr @Override public void generateXML(IExportableTransaction trans) { this.trans = trans; - this.calc=new TransactionCalculator(trans); + this.calc = new TransactionCalculator(trans); - boolean hasDueDate=false; + boolean hasDueDate = false; SimpleDateFormat germanDateFormat = new SimpleDateFormat("dd.MM.yyyy"); - String exemptionReason=""; + String exemptionReason = ""; - if (trans.getPaymentTermDescription()!=null) { - paymentTermsDescription=trans.getPaymentTermDescription(); + if (trans.getPaymentTermDescription() != null) { + paymentTermsDescription = trans.getPaymentTermDescription(); } - if (paymentTermsDescription==null) { - paymentTermsDescription= "Zahlbar ohne Abzug bis " + germanDateFormat.format(trans.getDueDate()); - + if (paymentTermsDescription == null) { + paymentTermsDescription = "Zahlbar ohne Abzug bis " + germanDateFormat.format(trans.getDueDate()); + } String senderReg = ""; @@ -135,14 +138,14 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr } String subjectNote = ""; - if (trans.getSubjectNote()!=null) { + if (trans.getSubjectNote() != null) { subjectNote = "\n" + " " - + XMLTools.encodeXML(trans.getSubjectNote())+ "\n" + + XMLTools.encodeXML(trans.getSubjectNote()) + "\n" + "\n"; } - String typecode="380"; - if (trans.getDocumentCode()!=null) { - typecode=trans.getDocumentCode(); + String typecode = "380"; + if (trans.getDocumentCode() != null) { + typecode = trans.getDocumentCode(); } String xml = "\n" @@ -163,12 +166,12 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr + " \n" + " " + XMLTools.encodeXML(trans.getNumber()) + "\n" //$NON-NLS-2$ + " RECHNUNG\n" - + " "+typecode+"\n" + + " " + typecode + "\n" + " " + zugferdDateFormat.format(trans.getIssueDate()) + "\n" // date - // format - // was - // 20130605 + // format + // was + // 20130605 + subjectNote + rebateAgreement + senderReg @@ -181,14 +184,14 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr } xml = xml + " \n"; - xml+= getTradePartyAsXML(trans.getSender(), true); - xml+= " \n" + xml += getTradePartyAsXML(trans.getSender(), true); + xml += " \n" + " \n"; // + " GE2020211\n" // + " 4000001987658\n" - xml+= getTradePartyAsXML(trans.getRecipient(), false); - if ((trans.getOwnVATID()!=null)&&(trans.getOwnOrganisationName()!=null)) { + xml += getTradePartyAsXML(trans.getRecipient(), false); + if ((trans.getOwnVATID() != null) && (trans.getOwnOrganisationName() != null)) { xml = xml + " \n" + " " + XMLTools.encodeXML(trans.getOwnVATID()) + "\n" + " "; @@ -196,21 +199,21 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr xml += " \n"; - if (trans.getBuyerOrderReferencedDocumentID()!=null) { + if (trans.getBuyerOrderReferencedDocumentID() != null) { xml = xml + " \n" + " " + XMLTools.encodeXML(trans.getBuyerOrderReferencedDocumentID()) + "\n" + " \n"; } - xml = xml + " \n" - + " \n" ; - if (this.trans.getDeliveryAddress()!=null) { - xml += ""+ - getTradePartyAsXML(this.trans.getDeliveryAddress(), false)+ + xml = xml + " \n" + + " \n"; + if (this.trans.getDeliveryAddress() != null) { + xml += "" + + getTradePartyAsXML(this.trans.getDeliveryAddress(), false) + ""; } - xml+= " \n" + xml += " \n" + " "; if (trans.getDeliveryDate() != null) { @@ -231,21 +234,21 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr + " " + XMLTools.encodeXML(trans.getNumber()) + "\n" //$NON-NLS-2$ + " " + trans.getCurrency() + "\n"; - if (trans.getTradeSettlementPayment()!=null) { + if (trans.getTradeSettlementPayment() != null) { for (IZUGFeRDTradeSettlementPayment payment : trans.getTradeSettlementPayment()) { - if(payment!=null) { - hasDueDate=true; - xml+=payment.getSettlementXML(); + if (payment != null) { + hasDueDate = true; + xml += payment.getSettlementXML(); } } } - if (trans.getTradeSettlement()!=null) { + if (trans.getTradeSettlement() != null) { for (IZUGFeRDTradeSettlement payment : trans.getTradeSettlement()) { - if(payment!=null) { + if (payment != null) { if (payment instanceof IZUGFeRDTradeSettlementPayment) { - hasDueDate=true; + hasDueDate = true; } - xml+=payment.getSettlementXML(); + xml += payment.getSettlementXML(); } } } @@ -260,7 +263,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr + " VAT\n" + exemptionReason + " " + currencyFormat(amount.getBasis()) + "\n" // currencyID=\"EUR\" - + " "+amount.getCategoryCode()+"\n" + + " " + amount.getCategoryCode() + "\n" + " " + vatFormat(currentTaxPercent) + "\n" + " \n"; //$NON-NLS-2$ @@ -279,7 +282,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr } } - if (hasDueDate && (trans.getDueDate()!=null)) { + if (hasDueDate && (trans.getDueDate() != null)) { xml = xml + " " // $NON-NLS-2$ + zugferdDateFormat.format(trans.getDueDate()) + "\n";// 20130704 @@ -291,7 +294,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr } xml = xml + " \n" - + " " + currencyFormat(calc.getTotal()) + "\n" //$NON-NLS-2$ + + " " + currencyFormat(calc.getTotal()) + "\n" //$NON-NLS-2$ // currencyID=\"EUR\" + " 0.00\n" // currencyID=\"EUR\" + " 0.00\n" // @@ -317,8 +320,8 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr int lineID = 0; for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) { lineID++; - if (currentItem.getProduct().getTaxExemptionReason() != null) { - exemptionReason="" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + ""; + if (currentItem.getProduct().getTaxExemptionReason() != null) { + exemptionReason = "" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + ""; } @@ -329,10 +332,10 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr + " \n" + " \n" + " \n" - + " " + priceFormat(lc.getPriceGross()) + + " " + priceFormat(lc.getPriceGross()) + "\n" + " " + quantityFormat(currentItem.getBasisQuantity()) +"\n" + + "\">" + quantityFormat(currentItem.getBasisQuantity()) + "\n" // + " \n" // + " false\n" // + " 0.6667\n" @@ -340,10 +343,10 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr // + " \n" + " \n" + " \n" - + " " + priceFormat(currentItem.getPrice()) + + " " + priceFormat(currentItem.getPrice()) + "\n" + " " + quantityFormat(currentItem.getBasisQuantity()) +"\n" + + "\">" + quantityFormat(currentItem.getBasisQuantity()) + "\n" + " \n" + " \n" @@ -356,21 +359,21 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr + " \n" + " VAT\n" + exemptionReason - + " "+currentItem.getProduct().getTaxCategoryCode()+"\n" + + " " + currentItem.getProduct().getTaxCategoryCode() + "\n" + " " + vatFormat(currentItem.getProduct().getVATPercent()) + "\n" + " \n" + " \n" - + " " + currencyFormat(lc.getItemTotalNetAmount()) + + " " + currencyFormat(lc.getItemTotalNetAmount()) + "\n" + " \n"; - if (currentItem.getAdditionalReferencedDocumentID()!=null) { - xml=xml + " "+currentItem.getAdditionalReferencedDocumentID()+"130\n"; + if (currentItem.getAdditionalReferencedDocumentID() != null) { + xml = xml + " " + currentItem.getAdditionalReferencedDocumentID() + "130\n"; - } - xml=xml + " \n" - + " \n"; + } + xml = xml + " \n" + + " \n"; // + " 4012345001235\n" if (currentItem.getProduct().getSellerAssignedID() != null) { xml = xml + " " @@ -404,14 +407,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr byte[] zugferdRaw; try { zugferdRaw = xml.getBytes("UTF-8"); - - if ((zugferdRaw[0] == (byte) 0xEF) && (zugferdRaw[1] == (byte) 0xBB) && (zugferdRaw[2] == (byte) 0xBF)) { - // I don't like BOMs, lets remove it - zugferdData = new byte[zugferdRaw.length - 3]; - System.arraycopy(zugferdRaw, 3, zugferdData, 0, zugferdRaw.length - 3); - } else { - zugferdData = zugferdRaw; - } + zugferdData = XMLTools.removeBOM(zugferdRaw); } catch (UnsupportedEncodingException e) { Logger.getLogger(ZUGFeRD1PullProvider.class.getName()).log(Level.SEVERE, null, e); } @@ -419,7 +415,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr @Override public void setProfile(Profile p) { - profile=p; + profile = p; } private String buildPaymentTermsXml() { @@ -453,7 +449,7 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr paymentTermsXml += ""; paymentTermsXml += "" + zugferdDateFormat.format(baseDate) + ""; paymentTermsXml += ""; - + paymentTermsXml += "" + discountTerms.getBasePeriodMeasure() + ""; } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index 4895122f..c5e4f294 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -615,13 +615,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { try { zugferdRaw = xml.getBytes("UTF-8"); - if ((zugferdRaw[0] == (byte) 0xEF) && (zugferdRaw[1] == (byte) 0xBB) && (zugferdRaw[2] == (byte) 0xBF)) { - // I don't like BOMs, lets remove it - zugferdData = new byte[zugferdRaw.length - 3]; - System.arraycopy(zugferdRaw, 3, zugferdData, 0, zugferdRaw.length - 3); - } else { - zugferdData = zugferdRaw; - } + zugferdData=XMLTools.removeBOM(zugferdRaw); } catch (UnsupportedEncodingException e) { Logger.getLogger(ZUGFeRD2PullProvider.class.getName()).log(Level.SEVERE, null, e); } diff --git a/validator/pom.xml b/validator/pom.xml index 77781d5a..a7f839e3 100644 --- a/validator/pom.xml +++ b/validator/pom.xml @@ -161,8 +161,8 @@ - 1.7 - 1.7 + 8 + 8 diff --git a/validator/src/main/java/org/mustangproject/validator/ValidationResultItem.java b/validator/src/main/java/org/mustangproject/validator/ValidationResultItem.java index 27a4ca67..1f7d1343 100644 --- a/validator/src/main/java/org/mustangproject/validator/ValidationResultItem.java +++ b/validator/src/main/java/org/mustangproject/validator/ValidationResultItem.java @@ -1,5 +1,6 @@ package org.mustangproject.validator; +import org.mustangproject.XMLTools; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/validator/src/main/java/org/mustangproject/validator/XMLTools.java b/validator/src/main/java/org/mustangproject/validator/XMLTools.java deleted file mode 100644 index 13eb810f..00000000 --- a/validator/src/main/java/org/mustangproject/validator/XMLTools.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.mustangproject.validator; - -import org.dom4j.io.XMLWriter; - -public class XMLTools extends XMLWriter { - public String escapeAttributeEntities(String s) { - return super.escapeAttributeEntities(s); - } - public String escapeElementEntities(String s) { - return super.escapeElementEntities(s); - - } - -} diff --git a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java index 8b804425..5ac6e220 100644 --- a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java @@ -18,6 +18,7 @@ import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathFactory; +import org.mustangproject.XMLTools; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -61,7 +62,7 @@ public class XMLValidator extends Validator { // file existence must have been checked before try { - zfXML = new String(Files.readAllBytes(Paths.get(name))); + zfXML = new String(XMLTools.removeBOM(Files.readAllBytes(Paths.get(name)))); } catch (IOException e) { ValidationResultItem vri = new ValidationResultItem(ESeverity.exception, e.getMessage()).setSection(9) @@ -112,8 +113,6 @@ public class XMLValidator extends Validator { failedRules = 0; - ByteArrayInputStream xmlByteInputStream = new ByteArrayInputStream(zfXML.getBytes(StandardCharsets.UTF_8)); - if (zfXML.isEmpty()) { ValidationResultItem res = new ValidationResultItem(ESeverity.exception, "XML data not found in " + filename @@ -145,8 +144,8 @@ public class XMLValidator extends Validator { // document.getElementsByTagNameNS("*",... DocumentBuilder db = dbf.newDocumentBuilder(); - - Document doc = db.parse(xmlByteInputStream); + InputSource is = new InputSource(new StringReader(zfXML)); + Document doc = db.parse(is); Element root = doc.getDocumentElement(); diff --git a/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java b/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java index 69cef429..e8d976eb 100644 --- a/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java @@ -1,11 +1,7 @@ package org.mustangproject.validator; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.StringWriter; +import java.io.*; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.MessageDigest; @@ -22,11 +18,13 @@ import org.dom4j.DocumentException; import org.dom4j.DocumentHelper; import org.dom4j.io.OutputFormat; import org.dom4j.io.XMLWriter; +import org.mustangproject.XMLTools; import org.riversun.bigdoc.bin.BigFileSearcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.xml.sax.InputSource; import org.xml.sax.SAXParseException; //abstract class @@ -165,8 +163,12 @@ public class ZUGFeRDValidator { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); - - Document doc = db.parse(file); + + byte[] content=Files.readAllBytes(file.toPath()); + content= XMLTools.removeBOM(content); + String s=new String(content); + InputSource is = new InputSource(new StringReader(s)); + Document doc = db.parse(is); Element root = doc.getDocumentElement(); isXML=true;//no exception so far @@ -176,6 +178,8 @@ public class ZUGFeRDValidator { // probably no xml file, sth like SAXParseException content not allowed in prolog // ignore isXML is already false // in the tests, this may error-out anyway + //ex.printStackTrace(); + } if (isXML) { pdfValidity = true; diff --git a/validator/src/test/resources/validV2.xml b/validator/src/test/resources/validV2.xml index 3381bee2..3d129ea2 100644 --- a/validator/src/test/resources/validV2.xml +++ b/validator/src/test/resources/validV2.xml @@ -1,4 +1,4 @@ - + From 599320462d7970c9cfc95c387f529bdf8d0028ee Mon Sep 17 00:00:00 2001 From: jstaerk Date: Thu, 12 Nov 2020 06:48:55 +0100 Subject: [PATCH 05/16] Allow to specify customer ID --- .../main/java/org/mustangproject/TradeParty.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/library/src/main/java/org/mustangproject/TradeParty.java b/library/src/main/java/org/mustangproject/TradeParty.java index 125fb1ea..0105247a 100644 --- a/library/src/main/java/org/mustangproject/TradeParty.java +++ b/library/src/main/java/org/mustangproject/TradeParty.java @@ -1,8 +1,6 @@ package org.mustangproject; -import org.mustangproject.ZUGFeRD.IZUGFeRDExportableContact; -import org.mustangproject.ZUGFeRD.IZUGFeRDExportableTradeParty; -import org.mustangproject.ZUGFeRD.IZUGFeRDTradeSettlement; +import org.mustangproject.ZUGFeRD.*; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -12,6 +10,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { protected String name, zip, street, location, country; protected String taxID = null, vatID = null; + protected String ID = null; protected String additionalAddress = null; protected ArrayList bankDetails = new ArrayList(); protected Contact contact = null; @@ -88,6 +87,16 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { } + @Override + public String getID() { + return ID; + } + + public TradeParty setID(String ID) { + this.ID = ID; + return this; + } + public TradeParty setContact(Contact c) { this.contact = c; return this; @@ -202,4 +211,5 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { return this; } + } From 46c7e25ef94bf6c29ca97c823ddb5b7a835ee67a Mon Sep 17 00:00:00 2001 From: jstaerk Date: Thu, 12 Nov 2020 06:49:46 +0100 Subject: [PATCH 06/16] merged BOM functionality in XMLtools --- .../java/org/mustangproject/XMLTools.java | 4 +-- .../ZUGFeRD/ZUGFeRDImporter.java | 29 +------------------ 2 files changed, 3 insertions(+), 30 deletions(-) diff --git a/library/src/main/java/org/mustangproject/XMLTools.java b/library/src/main/java/org/mustangproject/XMLTools.java index 402a6dfd..51b80ec5 100644 --- a/library/src/main/java/org/mustangproject/XMLTools.java +++ b/library/src/main/java/org/mustangproject/XMLTools.java @@ -98,7 +98,7 @@ public class XMLTools extends XMLWriter { * @param is the ByteArrayInputStream used * @throws IOException if can not be read from is * @see Autodetection of Character Encodings - */ + * public static int guessBOMSize(ByteArrayInputStream is) throws IOException { byte[] pad = new byte[4]; is.read(pad); @@ -118,7 +118,7 @@ public class XMLTools extends XMLWriter { return 2; } return 0; - } + }*/ /*** * removes utf8 byte order marks from byte arrays, in case one is there diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java index 87a5c120..783f9000 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java @@ -171,7 +171,7 @@ public class ZUGFeRDImporter { xmlFact.setNamespaceAware(false); DocumentBuilder builder = xmlFact.newDocumentBuilder(); ByteArrayInputStream is = new ByteArrayInputStream(rawXML); - is.skip(guessBOMSize(is)); + /// is.skip(guessBOMSize(is)); document = builder.parse(is); } @@ -187,33 +187,6 @@ public class ZUGFeRDImporter { } - /** - * Skips over a BOM at the beginning of the given ByteArrayInputStream, if one exists. - * - * @param is the ByteArrayInputStream used - * @throws IOException if can not be read from is - * @see Autodetection of Character Encodings - */ - private int guessBOMSize(ByteArrayInputStream is) throws IOException { - byte[] pad = new byte[4]; - is.read(pad); - is.reset(); - int test2 = ((pad[0] & 0xFF) << 8) | (pad[1] & 0xFF); - int test3 = ((test2 & 0xFFFF) << 8) | (pad[2] & 0xFF); - int test4 = ((test3 & 0xFFFFFF) << 8) | (pad[3] & 0xFF); - // - if (test4 == 0x0000FEFF || test4 == 0xFFFE0000 || test4 == 0x0000FFFE || test4 == 0xFEFF0000) { - // UCS-4: BOM takes 4 bytes - return 4; - } else if (test3 == 0xEFBBFF) { - // UTF-8: BOM takes 3 bytes - return 3; - } else if (test2 == 0xFEFF || test2 == 0xFFFE) { - // UTF-16: BOM takes 2 bytes - return 2; - } - return 0; - } protected String extractString(String xpathStr) { From bb42381c67fb78e50ef8f425a947c7d1742e48e8 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Thu, 12 Nov 2020 06:50:17 +0100 Subject: [PATCH 07/16] test for customer IDs --- .../src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java index 6ff582e6..d0309a01 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java @@ -279,6 +279,8 @@ public class ZF2PushTest extends TestCase { // now check the contents (like MustangReaderTest) ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_PUSHEDGE); + assertTrue(zi.getUTF8().contains("0009845")); + assertTrue(zi.getUTF8().contains("0008734")); assertTrue(zi.getUTF8().contains("item level 1/1")); assertTrue(zi.getUTF8().contains("document level 2/2")); From b6aad03da2dd4840b347ecd9a86fc3a435bf6692 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Thu, 12 Nov 2020 08:06:53 +0100 Subject: [PATCH 08/16] updated some javadoc --- .../java/org/mustangproject/BankDetails.java | 16 ++ .../main/java/org/mustangproject/Contact.java | 64 +++++++- .../main/java/org/mustangproject/Invoice.java | 138 ++++++++++++------ .../main/java/org/mustangproject/Item.java | 24 +++ .../main/java/org/mustangproject/Product.java | 40 +++++ .../java/org/mustangproject/TradeParty.java | 72 +++++++++ .../ZUGFeRD/IExportableTransaction.java | 23 +-- .../ZUGFeRD/ZUGFeRD2PullProvider.java | 10 +- .../mustangproject/ZUGFeRD/ZF2PushTest.java | 18 ++- 9 files changed, 333 insertions(+), 72 deletions(-) diff --git a/library/src/main/java/org/mustangproject/BankDetails.java b/library/src/main/java/org/mustangproject/BankDetails.java index 24e4274c..108b98fa 100644 --- a/library/src/main/java/org/mustangproject/BankDetails.java +++ b/library/src/main/java/org/mustangproject/BankDetails.java @@ -2,6 +2,9 @@ package org.mustangproject; import org.mustangproject.ZUGFeRD.IZUGFeRDTradeSettlementPayment; +/** + * provides e.g. the IBAN to transfer money to :-) + */ public class BankDetails implements IZUGFeRDTradeSettlementPayment { protected String IBAN, BIC; @@ -14,6 +17,14 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment { return IBAN; } + /** + * Sets the IBAN "ID", which means that it only needs to be a way to uniquely + * identify the IBAN. Of course you will specify your own IBAN in full length but + * if you deduct from a customer's account you may e.g. leave out the first or last + * digits so that nobody spying on the invoice gets to know the complete number + * @param IBAN + * @return + */ public BankDetails setIBAN(String IBAN) { this.IBAN = IBAN; return this; @@ -28,12 +39,17 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment { return this; } + /*** + * getOwn... methods will be removed in the future in favor of Tradeparty (e.g. Sender) class + * */ @Override + @Deprecated public String getOwnBIC() { return getBIC(); } @Override + @Deprecated public String getOwnIBAN() { return getIBAN(); } diff --git a/library/src/main/java/org/mustangproject/Contact.java b/library/src/main/java/org/mustangproject/Contact.java index c782f574..cfab42b2 100644 --- a/library/src/main/java/org/mustangproject/Contact.java +++ b/library/src/main/java/org/mustangproject/Contact.java @@ -2,17 +2,39 @@ package org.mustangproject; import org.mustangproject.ZUGFeRD.IZUGFeRDExportableContact; +/*** + * a named contact person in an organisation + * for the organisation/company itsel please + * @see TradeParty + */ public class Contact implements IZUGFeRDExportableContact { protected String name,phone,email,zip,street,location,country; protected String fax=null; + /*** + * default constructor. + * Name, phone and email of sender contact person are e.g. required by XRechnung + * @param name + * @param phone + * @param email + */ public Contact(String name, String phone, String email) { this.name = name; this.phone = phone; this.email = email; - } + + /*** + * complete specification of a named contact with a different address + * @param name + * @param phone + * @param email + * @param street + * @param zip + * @param location + * @param country + */ public Contact(String name, String phone, String email, String street, String zip, String location, String country) { this.name = name; this.phone = phone; @@ -29,6 +51,11 @@ public class Contact implements IZUGFeRDExportableContact { return name; } + /** + * the first and last name of the contact + * @param name + * @return + */ public Contact setName(String name) { this.name = name; return this; @@ -39,6 +66,11 @@ public class Contact implements IZUGFeRDExportableContact { return phone; } + /*** + * complete phone number of the contact + * @param phone + * @return + */ public Contact setPhone(String phone) { this.phone = phone; return this; @@ -49,6 +81,11 @@ public class Contact implements IZUGFeRDExportableContact { return fax; } + /*** + * (optional) complete fax number + * @param fax + * @return + */ public Contact setFax(String fax) { this.fax = fax; return this; @@ -58,6 +95,11 @@ public class Contact implements IZUGFeRDExportableContact { return email; } + /*** + * personal email address of the contact person + * @param email + * @return + */ public Contact setEMail(String email) { this.email = email; return this; @@ -67,6 +109,11 @@ public class Contact implements IZUGFeRDExportableContact { return zip; } + /*** + * the postcode, if the address is different to the organisation + * @param zip + * @return + */ public Contact setZIP(String zip) { this.zip = zip; return this; @@ -77,6 +124,11 @@ public class Contact implements IZUGFeRDExportableContact { return street; } + /** + * street and number, if the address is different to the organisation + * @param street + * @return + */ public Contact setStreet(String street) { this.street = street; return this; @@ -87,6 +139,11 @@ public class Contact implements IZUGFeRDExportableContact { return location; } + /*** + * city of the contact person, if different from organisation + * @param location + * @return + */ public Contact setLocation(String location) { this.location = location; return this; @@ -97,6 +154,11 @@ public class Contact implements IZUGFeRDExportableContact { return country; } + /*** + * two-letter ISO country code of the contact, if different from organisation + * @param country + * @return + */ public Contact setCountry(String country) { this.country = country; return this; diff --git a/library/src/main/java/org/mustangproject/Invoice.java b/library/src/main/java/org/mustangproject/Invoice.java index 78b8b856..b0095dae 100644 --- a/library/src/main/java/org/mustangproject/Invoice.java +++ b/library/src/main/java/org/mustangproject/Invoice.java @@ -36,13 +36,13 @@ public class Invoice implements IExportableTransaction { protected String documentName = null, documentCode = null, number = null, ownOrganisationFullPlaintextInfo = null, referenceNumber = null, shipToOrganisationID = null, shipToOrganisationName = null, shipToStreet = null, shipToZIP = null, shipToLocation = null, shipToCountry = null, buyerOrderReferencedDocumentID = null, buyerOrderReferencedDocumentIssueDateTime = null, ownForeignOrganisationID = null, ownOrganisationName = null, currency = null, paymentTermDescription = null; protected Date issueDate = null, dueDate = null, deliveryDate = null; protected BigDecimal totalPrepaidAmount = null; - protected TradeParty sender=null, recipient = null, deliveryAddress = null; + protected TradeParty sender = null, recipient = null, deliveryAddress = null; protected ArrayList ZFItems = null; protected ArrayList notes = null; protected String contractReferencedDocument = null; - protected Date occurrenceDateFrom = null; - protected Date occurrenceDateTo = null; + protected Date detailedDeliveryDateStart = null; + protected Date detailedDeliveryPeriodEnd = null; protected ArrayList Allowances = new ArrayList(), Charges = new ArrayList(), LogisticsServiceCharges = new ArrayList(); @@ -59,7 +59,9 @@ public class Invoice implements IExportableTransaction { return documentName; } - public String getContractReferencedDocument() { return contractReferencedDocument; } + public String getContractReferencedDocument() { + return contractReferencedDocument; + } public Invoice setDocumentName(String documentName) { this.documentName = documentName; @@ -94,7 +96,7 @@ public class Invoice implements IExportableTransaction { */ public Invoice setCorrection(String number) { setBuyerOrderReferencedDocumentID(number); - documentCode= DocumentCodeTypeConstants.CORRECTEDINVOICE; + documentCode = DocumentCodeTypeConstants.CORRECTEDINVOICE; return this; } @@ -144,7 +146,6 @@ public class Invoice implements IExportableTransaction { } - public Invoice setShipToStreet(String shipToStreet) { this.shipToStreet = shipToStreet; return this; @@ -209,6 +210,7 @@ public class Invoice implements IExportableTransaction { @Deprecated /*** * @deprecated use TradeParty::addTaxID instead + * @see TradeParty */ public Invoice setOwnTaxID(String ownTaxID) { sender.addTaxID(ownTaxID); @@ -223,6 +225,7 @@ public class Invoice implements IExportableTransaction { @Deprecated /*** * @deprecated use TradeParty::addVATID instead + * @see TradeParty */ public Invoice setOwnVATID(String ownVATID) { sender.addVATID(ownVATID); @@ -234,6 +237,12 @@ public class Invoice implements IExportableTransaction { return ownForeignOrganisationID; } + + @Deprecated + /*** + * @deprecated use TradeParty instead + * @see TradeParty + */ public Invoice setOwnForeignOrganisationID(String ownForeignOrganisationID) { this.ownForeignOrganisationID = ownForeignOrganisationID; return this; @@ -244,6 +253,12 @@ public class Invoice implements IExportableTransaction { return ownOrganisationName; } + + @Deprecated + /*** + * @deprecated use senders' TradeParty's name instead + * @see TradeParty + */ public Invoice setOwnOrganisationName(String ownOrganisationName) { this.ownOrganisationName = ownOrganisationName; return this; @@ -273,7 +288,7 @@ public class Invoice implements IExportableTransaction { @Override public String[] getNotes() { - if (notes==null) { + if (notes == null) { return null; } return notes.toArray(new String[0]); @@ -344,6 +359,11 @@ public class Invoice implements IExportableTransaction { return sender; } + /*** + * sets a named sender contact + * @param ownContact + * @return + */ public Invoice setOwnContact(Contact ownContact) { this.sender.setContact(ownContact); return this; @@ -353,14 +373,26 @@ public class Invoice implements IExportableTransaction { return recipient; } + /** + * required. + * sets the invoice receiving institution = invoicee + * @param recipient + * @return + */ public Invoice setRecipient(TradeParty recipient) { this.recipient = recipient; return this; } + /** + * required. + * sets the invoicing institution = invoicer + * @param sender + * @return + */ public Invoice setSender(TradeParty sender) { this.sender = sender; - if ((sender.getBankDetails()!=null)&&(sender.getBankDetails().size()>0)) { + if ((sender.getBankDetails() != null) && (sender.getBankDetails().size() > 0)) { // convert bankdetails } @@ -372,7 +404,7 @@ public class Invoice implements IExportableTransaction { if (Allowances.isEmpty()) { return null; } else - return Allowances.toArray(new IZUGFeRDAllowanceCharge[0]); + return Allowances.toArray(new IZUGFeRDAllowanceCharge[0]); } @@ -397,11 +429,11 @@ public class Invoice implements IExportableTransaction { @Override public IZUGFeRDTradeSettlement[] getTradeSettlement() { - if (getSender()==null) { + if (getSender() == null) { return null; } - return ((TradeParty)getSender()).getAsTradeSettlement(); + return ((TradeParty) getSender()).getAsTradeSettlement(); } @@ -421,6 +453,11 @@ public class Invoice implements IExportableTransaction { return deliveryAddress; } + /*** + * if the delivery address is not the recipient address, it can be specified here + * @param deliveryAddress + * @return + */ public Invoice setDeliveryAddress(TradeParty deliveryAddress) { this.deliveryAddress = deliveryAddress; return this; @@ -431,14 +468,18 @@ public class Invoice implements IExportableTransaction { return ZFItems.toArray(new IZUGFeRDExportableItem[0]); } + /** + * required + * adds invoice "lines" :-) + * @param item + * @return + */ public Invoice addItem(IZUGFeRDExportableItem item) { ZFItems.add(item); return this; } - - /*** * checks if all required items are set in order to be able to export it * @return true if all required items are set @@ -454,61 +495,72 @@ public class Invoice implements IExportableTransaction { // this.country = country; } + /*** + * adds a document level addition to the price + * @param izac + * @return + */ public Invoice addCharge(IZUGFeRDAllowanceCharge izac) { Charges.add(izac); return this; } + + /*** + * adds a document level rebate + * @param izac + * @return + */ public Invoice addAllowance(IZUGFeRDAllowanceCharge izac) { Allowances.add(izac); return this; } + /*** + * adds the ID of a contract referenced in the invoice + * @param s + * @return + */ public Invoice setContractReferencedDocument(String s) { - contractReferencedDocument=s; + contractReferencedDocument = s; return this; } - public Invoice setOccurrenceDate(Date occur) { - occurrenceDateFrom=occur; - occurrenceDateTo=null; + + /*** + * sets a document level delivery period, + * which is optional additional to the mandatory deliverydate + * and which will become a BillingSpecifiedPeriod-Element + * @param start + * @param end + * @return fluent setter + */ + public Invoice setDetailedDeliveryPeriod(Date start, Date end) { + detailedDeliveryDateStart = start; + detailedDeliveryPeriodEnd = end; return this; } - public Invoice setOccurrencePeriod(Date start, Date end) { - occurrenceDateFrom=start; - occurrenceDateTo=end; - return this; + @Override + public Date getDetailedDeliveryPeriodFrom() { + return detailedDeliveryDateStart; } @Override - public Date getOccurrenceDate() { - - return occurrenceDateFrom; - } - - @Override - public Date getOccurrencePeriodFrom() { - if (occurrenceDateTo!=null) { - return occurrenceDateFrom; - } else { - return null; - } - } - @Override - public Date getOccurrencePeriodTo() { - if (occurrenceDateTo!=null) { - return occurrenceDateTo; - } else { - return null; - } + public Date getDetailedDeliveryPeriodTo() { + return detailedDeliveryPeriodEnd; } + /*** + * adds a free text paragraph, which will become a includedNote element + * @param text + * @return fluent setter + */ public Invoice addNote(String text) { - if (notes==null) { - notes=new ArrayList(); + if (notes == null) { + notes = new ArrayList(); } notes.add(text); return this; diff --git a/library/src/main/java/org/mustangproject/Item.java b/library/src/main/java/org/mustangproject/Item.java index 90f7bec9..b31c521c 100644 --- a/library/src/main/java/org/mustangproject/Item.java +++ b/library/src/main/java/org/mustangproject/Item.java @@ -6,6 +6,9 @@ import org.mustangproject.ZUGFeRD.IZUGFeRDExportableItem; import java.math.BigDecimal; import java.util.ArrayList; +/*** + * describes any invoice line + */ public class Item implements IZUGFeRDExportableItem { protected BigDecimal price, quantity, tax, grossPrice, lineTotalAmount; protected String id; @@ -14,6 +17,12 @@ public class Item implements IZUGFeRDExportableItem { protected ArrayList Allowances = new ArrayList(), Charges = new ArrayList(); + /*** + * default constructor + * @param product contains the products name, tax rate, and unit + * @param price the base price of one item the product + * @param quantity the number, dimensions or the weight of the delivered product or good in this context + */ public Item(Product product, BigDecimal price, BigDecimal quantity) { this.price = price; this.quantity = quantity; @@ -24,6 +33,11 @@ public class Item implements IZUGFeRDExportableItem { return lineTotalAmount; } + /** + * should only be set by calculator classes or maybe when reading from XML + * @param lineTotalAmount + * @return + */ public Item setLineTotalAmount(BigDecimal lineTotalAmount) { this.lineTotalAmount = lineTotalAmount; return this; @@ -33,6 +47,11 @@ public class Item implements IZUGFeRDExportableItem { return grossPrice; } + + /*** + * the list price without VAT (sic!), refer to EN16931-1 for definition + * @return + */ public Item setGrossPrice(BigDecimal grossPrice) { this.grossPrice = grossPrice; return this; @@ -125,6 +144,11 @@ public class Item implements IZUGFeRDExportableItem { return this; } + /*** + * adds item level freetext fields (includednote) + * @param text + * @return + */ public Item addNote(String text) { if (notes==null) { notes=new ArrayList(); diff --git a/library/src/main/java/org/mustangproject/Product.java b/library/src/main/java/org/mustangproject/Product.java index 9f5aa734..7a22f2a8 100644 --- a/library/src/main/java/org/mustangproject/Product.java +++ b/library/src/main/java/org/mustangproject/Product.java @@ -4,10 +4,20 @@ import org.mustangproject.ZUGFeRD.IZUGFeRDExportableProduct; import java.math.BigDecimal; +/*** + * describes a product, good or service used in an invoice item line + */ public class Product implements IZUGFeRDExportableProduct { protected String unit, name, description, sellerAssignedID, buyerAssignedID; protected BigDecimal VATPercent; + /*** + * default constructor + * @param name + * @param description + * @param unit a two/three letter UN/ECE rec 20 unit code, e.g. "C62" for piece + * @param VATPercent + */ public Product(String name, String description, String unit, BigDecimal VATPercent) { this.unit = unit; this.name = name; @@ -20,6 +30,11 @@ public class Product implements IZUGFeRDExportableProduct { return sellerAssignedID; } + /*** + * how the seller identifies this type of product + * @param sellerAssignedID + * @return + */ public Product setSellerAssignedID(String sellerAssignedID) { this.sellerAssignedID = sellerAssignedID; return this; @@ -29,6 +44,11 @@ public class Product implements IZUGFeRDExportableProduct { return buyerAssignedID; } + /*** + * if the buyer provided an ID how he refers to this product + * @param buyerAssignedID + * @return + */ public Product setBuyerAssignedID(String buyerAssignedID) { this.buyerAssignedID = buyerAssignedID; return this; @@ -39,6 +59,11 @@ public class Product implements IZUGFeRDExportableProduct { return unit; } + /*** + * sets a UN/ECE rec 20 or 21 code which unit the product ships in, e.g. C62=piece + * @param unit + * @return + */ public Product setUnit(String unit) { this.unit = unit; return this; @@ -49,6 +74,11 @@ public class Product implements IZUGFeRDExportableProduct { return name; } + /** + * name of the product + * @param name + * @return + */ public Product setName(String name) { this.name = name; return this; @@ -59,6 +89,11 @@ public class Product implements IZUGFeRDExportableProduct { return description; } + /** + * description of the product (required) + * @param description + * @return + */ public Product setDescription(String description) { this.description = description; return this; @@ -69,6 +104,11 @@ public class Product implements IZUGFeRDExportableProduct { return VATPercent; } + /**** + * VAT rate of the product + * @param VATPercent + * @return + */ public Product setVATPercent(BigDecimal VATPercent) { this.VATPercent = VATPercent; return this; diff --git a/library/src/main/java/org/mustangproject/TradeParty.java b/library/src/main/java/org/mustangproject/TradeParty.java index 0105247a..2774ac11 100644 --- a/library/src/main/java/org/mustangproject/TradeParty.java +++ b/library/src/main/java/org/mustangproject/TradeParty.java @@ -6,6 +6,9 @@ import org.w3c.dom.NodeList; import java.util.ArrayList; +/*** + * A organisation, i.e. usually a company + */ public class TradeParty implements IZUGFeRDExportableTradeParty { protected String name, zip, street, location, country; @@ -15,6 +18,14 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { protected ArrayList bankDetails = new ArrayList(); protected Contact contact = null; + /*** + * + * @param name of the company + * @param street street and number (use setAdditionalAddress for more parts) + * @param zip postcode of the company + * @param location city of the company + * @param country two letter ISO code + */ public TradeParty(String name, String street, String zip, String location, String country) { this.name = name; this.street = street; @@ -24,6 +35,10 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { } + /*** + * XML parsing constructor + * @param nodes + */ public TradeParty(NodeList nodes) { /** * @@ -92,16 +107,31 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { return ID; } + /** + * if it's a customer, this can e.g. be the customer ID + * @param ID + * @return + */ public TradeParty setID(String ID) { this.ID = ID; return this; } + /*** + * (optional) a named contact person + * @param c + * @return + */ public TradeParty setContact(Contact c) { this.contact = c; return this; } + /*** + * required (for senders, if payment is not debit): the BIC and IBAN + * @param s + * @return + */ public TradeParty addBankDetails(BankDetails s) { bankDetails.add(s); return this; @@ -111,11 +141,21 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { return bankDetails; } + /*** + * a general tax ID + * @param taxID + * @return + */ public TradeParty addTaxID(String taxID) { this.taxID = taxID; return this; } + /*** + * the USt-ID + * @param vatID + * @return + */ public TradeParty addVATID(String vatID) { this.vatID = vatID; return this; @@ -135,6 +175,11 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { return name; } + + /*** + * required, usually done in the constructor: the complete name of the organisation + * @return + */ public TradeParty setName(String name) { this.name = name; return this; @@ -145,6 +190,11 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { return zip; } + /*** + * usually set in the constructor, required for recipients in german invoices: postcode + * @param zip + * @return + */ public TradeParty setZIP(String zip) { this.zip = zip; return this; @@ -155,6 +205,11 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { return street; } + /*** + * usually set in constructor, required in germany, street and house number + * @param street + * @return + */ public TradeParty setStreet(String street) { this.street = street; return this; @@ -165,6 +220,11 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { return location; } + /*** + * usually set in constructor, usually required in germany, the city of the organisation + * @param location + * @return + */ public TradeParty setLocation(String location) { this.location = location; return this; @@ -175,6 +235,11 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { return country; } + /*** + * two-letter ISO code of the country + * @param country + * @return + */ public TradeParty setCountry(String country) { this.country = country; return this; @@ -206,6 +271,13 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { return additionalAddress; } + + /*** + * additional parts of the address, e.g. which floor. + * Street address will become "lineOne", this will become "lineTwo" + * @param additionalAddress + * @return + */ public TradeParty setAdditionalAddress(String additionalAddress) { this.additionalAddress = additionalAddress; return this; diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java index 60a1e6c5..e7ef193c 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java @@ -21,8 +21,8 @@ package org.mustangproject.ZUGFeRD; * Mustangproject's ZUGFeRD implementation * Neccessary interface for ZUGFeRD exporter * Licensed under the APLv2 - * @date 2014-05-10 to 2014-06-25 - * @version 1.2.0 + * @date 2014-05-10 to 2020-11-12 + * @version 2.0.0 * @author jstaerk * */ @@ -36,7 +36,7 @@ import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants; /*** * the interface of an transaction, e.g. an invoice, you want to create xml (potentially to be added to a PDF) * for - * @see Invoice if you want to use an object rather than an interface + * @see org.mustangproject.Invoice */ public interface IExportableTransaction { @@ -431,33 +431,26 @@ public interface IExportableTransaction { return null; } - /*** - * specify delivery date - * @return the delivery date - */ - default Date getOccurrenceDate() { - return null; - } /*** - * specify delivery period + * specifies the document level delivery period, will be included in a BillingSpecifiedPeriod element * @return the beginning of the delivery period */ - default Date getOccurrencePeriodFrom() { + default Date getDetailedDeliveryPeriodFrom() { return null; } /*** - * specify delivery period + * specifies the document level delivery period, will be included in a BillingSpecifiedPeriod element * @return the end of the delivery period */ - default Date getOccurrencePeriodTo() { + default Date getDetailedDeliveryPeriodTo() { return null; } /** * get additional referenced documents acccording to BG-24 XRechnung (Rechnungsbegruendende Unterlagen), - * i.e. in (only supported for zf2) + * i.e. ram:ApplicableHeaderTradeAgreement/ram:AdditionalReferencedDocument * * @return a array of objects from class FileAttachment */ diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index c5e4f294..cefaaf06 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -496,13 +496,13 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { } } - if ((trans.getOccurrencePeriodFrom() != null) || (trans.getOccurrencePeriodTo() != null)) { + if ((trans.getDetailedDeliveryPeriodFrom() != null) || (trans.getDetailedDeliveryPeriodTo() != null)) { xml = xml + ""; - if (trans.getOccurrencePeriodFrom() != null) { - xml = xml + "" + zugferdDateFormat.format(trans.getOccurrencePeriodFrom()) + ""; + if (trans.getDetailedDeliveryPeriodFrom() != null) { + xml = xml + "" + zugferdDateFormat.format(trans.getDetailedDeliveryPeriodFrom()) + ""; } - if (trans.getOccurrencePeriodTo() != null) { - xml = xml + "" + zugferdDateFormat.format(trans.getOccurrencePeriodTo()) + ""; + if (trans.getDetailedDeliveryPeriodTo() != null) { + xml = xml + "" + zugferdDateFormat.format(trans.getDetailedDeliveryPeriodTo()) + ""; } xml = xml + ""; diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java index d0309a01..198ad1d5 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java @@ -65,7 +65,7 @@ public class ZF2PushTest extends TestCase { .load(SOURCE_PDF)) { try { - ze.setTransaction(new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setContractReferencedDocument(contractID).setOccurrencePeriod(new SimpleDateFormat("yyyyMMdd").parse(occurenceFrom), new SimpleDateFormat("yyyyMMdd").parse(occurenceTo)).setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addBankDetails(new BankDetails("777666555", "DE4321"))).setOwnTaxID("4711").setOwnVATID("DE19990815").setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").setAdditionalAddress("Hinterhaus 3").setContact(new Contact("nameRep", "phoneRep", "emailRep@test.com"))).setNumber(number).addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, new BigDecimal(1.0)))); + ze.setTransaction(new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setContractReferencedDocument(contractID).setDetailedDeliveryPeriod(new SimpleDateFormat("yyyyMMdd").parse(occurenceFrom), new SimpleDateFormat("yyyyMMdd").parse(occurenceTo)).setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addBankDetails(new BankDetails("777666555", "DE4321"))).setOwnTaxID("4711").setOwnVATID("DE19990815").setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").setAdditionalAddress("Hinterhaus 3").setContact(new Contact("nameRep", "phoneRep", "emailRep@test.com"))).setNumber(number).addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, new BigDecimal(1.0)))); } catch (ParseException ex) { throw new RuntimeException("Parse exception"); } @@ -209,7 +209,7 @@ public class ZF2PushTest extends TestCase { .setCreator(System.getProperty("user.name")).setZUGFeRDVersion(2).setProfile(Profiles.getByName("en16931")).ignorePDFAErrors() .load(SOURCE_PDF)) { - ze.setTransaction(new Invoice().setCurrency("CHF").setDueDate(new Date()).setOccurrenceDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE")).setOwnTaxID("4711").setOwnVATID("DE0815").setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE")).setNumber(number) + ze.setTransaction(new Invoice().setCurrency("CHF").setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE")).setOwnTaxID("4711").setOwnVATID("DE0815").setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE")).setNumber(number) .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(1.0))) .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(1.0))) .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(1.0))) @@ -258,12 +258,14 @@ public class ZF2PushTest extends TestCase { SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd"); try { - ze.setTransaction(new Invoice().setCurrency("CHF").addNote("document level 1/2").addNote("document level 2/2").setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID(taxID)).setOwnVATID("DE0815").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) + ze.setTransaction(new Invoice().setCurrency("CHF").addNote("document level 1/2").addNote("document level 2/2").setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()) + .setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID(taxID).setID("0009845")) + .setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").setID("0008734").addVATID("DE4711").setContact(new Contact("Franz Müller", "01779999999", "franz@mueller.de", "teststr. 12", "55232", "Entenhausen", "DE"))) .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(16)), price, new BigDecimal(1.0)).addNote("item level 1/1").addAllowance(new Allowance(new BigDecimal(0.02)).setReason("item discount").setTaxPercent(new BigDecimal(16)))) .addCharge(new Charge(new BigDecimal(0.5)).setReason("quick delivery charge").setTaxPercent(new BigDecimal(16))) .addAllowance(new Allowance(new BigDecimal(0.2)).setReason("discount").setTaxPercent(new BigDecimal(16))) - .setOccurrenceDate(sdf.parse("2020-01-01")) - .setDeliveryDate(sdf.parse("2020-02-02")) + .setDetailedDeliveryPeriod(sdf.parse("2020-01-01"),sdf.parse("2020-01-31")) + .setDeliveryDate(sdf.parse("2020-02-02")).setOwnVATID("DE0815").setNumber(number) ); } catch (ParseException e) { e.printStackTrace(); @@ -298,7 +300,7 @@ public class ZF2PushTest extends TestCase { .setCreator(System.getProperty("user.name")).setZUGFeRDVersion(2).setProfile(Profiles.getByName("en16931")).ignorePDFAErrors() .load(SOURCE_PDF)) { - ze.setTransaction(new Invoice().setDueDate(new Date()).setOccurrenceDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE")).setOwnTaxID("4711").setOwnVATID("DE0815").setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE")).setNumber(number) + ze.setTransaction(new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE")).setOwnTaxID("4711").setOwnVATID("DE0815").setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE")).setNumber(number) .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), new BigDecimal(500.0), qty).addAllowance(new Allowance(new BigDecimal(300)).setTaxPercent(new BigDecimal(19)))) .addAllowance(new Allowance(new BigDecimal(600)).setTaxPercent(new BigDecimal(19))) @@ -341,7 +343,7 @@ public class ZF2PushTest extends TestCase { .setCreator(System.getProperty("user.name")).setZUGFeRDVersion(2).setProfile(Profiles.getByName("extended")).ignorePDFAErrors() .load(SOURCE_PDF)) { - ze.setTransaction(new Invoice().setCurrency("CHF").setDueDate(new Date()).setOccurrenceDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE")).setOwnTaxID("4711").setOwnVATID("DE0815").setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE")).setNumber(number) + ze.setTransaction(new Invoice().setCurrency("CHF").setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE")).setOwnTaxID("4711").setOwnVATID("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)))) @@ -388,7 +390,7 @@ public class ZF2PushTest extends TestCase { .setCreator(System.getProperty("user.name")).setZUGFeRDVersion(2).ignorePDFAErrors() .load(SOURCE_PDF)) { - Invoice i = new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setOccurrencePeriod(new Date(), 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").addVATID("DE0815")).setNumber(number) + Invoice i = new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDetailedDeliveryPeriod(new Date(), 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").addVATID("DE0815")).setNumber(number) .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, qty)) .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, qty)) .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), price, qty)).setCorrection("0815"); From 7ed6c33b33345edeb35e834c2ba09141601f09fc Mon Sep 17 00:00:00 2001 From: jstaerk Date: Thu, 12 Nov 2020 09:16:10 +0100 Subject: [PATCH 09/16] Javadoc amendments --- .../src/main/java/org/mustangproject/Invoice.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/library/src/main/java/org/mustangproject/Invoice.java b/library/src/main/java/org/mustangproject/Invoice.java index b0095dae..cc398081 100644 --- a/library/src/main/java/org/mustangproject/Invoice.java +++ b/library/src/main/java/org/mustangproject/Invoice.java @@ -90,7 +90,10 @@ public class Invoice implements IExportableTransaction { } /*** - * switch type to invoice correction and refer to document number + * switch type to invoice correction and refer to document number. + * Please note that the quantities need to be negative, if you + * e.g. delivered 100 and take 50 back the quantity should be -50 in the + * corrected invoice, which will result in negative VAT and a negative payment amount * @param number the invoice number to be corrected * @return this object (fluent setter) */ @@ -186,6 +189,11 @@ public class Invoice implements IExportableTransaction { return buyerOrderReferencedDocumentID; } + /*** + * usually the order number or in case of a correction the original invoice number + * @param buyerOrderReferencedDocumentID + * @return + */ public Invoice setBuyerOrderReferencedDocumentID(String buyerOrderReferencedDocumentID) { this.buyerOrderReferencedDocumentID = buyerOrderReferencedDocumentID; return this; @@ -196,6 +204,11 @@ public class Invoice implements IExportableTransaction { return buyerOrderReferencedDocumentIssueDateTime; } + /*** + * when the order (or whatever reference in BuyerOrderReferencedDocumentID) was issued + * @param buyerOrderReferencedDocumentIssueDateTime + * @return + */ public Invoice setBuyerOrderReferencedDocumentIssueDateTime(String buyerOrderReferencedDocumentIssueDateTime) { this.buyerOrderReferencedDocumentIssueDateTime = buyerOrderReferencedDocumentIssueDateTime; return this; From 98f29823241f17c711d7b81bc85977081f0fdf38 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Thu, 12 Nov 2020 16:39:33 +0100 Subject: [PATCH 10/16] updated history and release notes --- History.md | 9 +-------- Release_Notes.md | 4 ++-- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/History.md b/History.md index c8f90dc0..cee75993 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,4 @@ -## 2.0 +## 2.0.0 ### done - support for ZF 2.1.1, i.e. "Reference profile" Xrechnung @@ -53,13 +53,6 @@ switch - support contact fax numbers - closes #190 BOM not treated correctly on XML input file -### 2.0 still todo -- dont show empty tax number field -- fail when no bankverbindung? -- fail when xr attrs missing? -- build xr skonto??? -- *validator not to XR error on ZF files (only notices) -- xmp errors may not show correctly in log Alpha3 2020-10-24 Alpha2 2020-09-15 Alpha1 2020-08-06 diff --git a/Release_Notes.md b/Release_Notes.md index 705bba7c..fb803b51 100644 --- a/Release_Notes.md +++ b/Release_Notes.md @@ -1,7 +1,7 @@ ## Changes -On it's official website you can [download](https://www.mustangproject.org/files/Mustang-CLI-2.0.0-alpha3.jar) a alpha release of Mustang 2. +On it's official website you can [download](https://www.mustangproject.org/deploy/Mustang-CLI-2.0.0.jar) Mustang 2. * Factur-X/ZUGFeRD 2 is now the default. In Mustangproject 1.x one had to select ZUGFeRD version 2 if that was desired, in Mustangproject 2 one now has to select ZUGFeRD version 1 if version 2 is not appropriate. * Mustang is now available via Maven Central, which makes it even easier to use the Mustang library. Apart from making it easier in Maven because there is no longer the need to mention the custom repository, it also makes it possible to use Mustang in Gradle projects @@ -230,7 +230,7 @@ read/write ZUGFeRD-invoices like the (smaller) library module. org.mustangproject validator - 2.0.0-alpha3 + 2.0.0 From 9c990251e18fb28e72a9416be6f223c66061490b Mon Sep 17 00:00:00 2001 From: jstaerk Date: Thu, 12 Nov 2020 16:39:48 +0100 Subject: [PATCH 11/16] updated cli help text --- .../org/mustangproject/commandline/Main.java | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 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 987c8cc0..46a24899 100755 --- a/Mustang-CLI/src/main/java/org/mustangproject/commandline/Main.java +++ b/Mustang-CLI/src/main/java/org/mustangproject/commandline/Main.java @@ -47,21 +47,23 @@ public class Main { private static String getUsage() { return "Usage: --action metrics|combine|extract|a3only|validate|visualize [-d,--directory] [-l,--listfromstdin] [-i,--ignore fileextension, PDF/A errors] | [-h,--help] \r\n" - + "* merics\n" + " -d, --directory count ZUGFeRD files in directory to be scanned\n" + + " --action=metrics\n" + + + + " -d, --directory count ZUGFeRD files in directory to be scanned\n" + " If it is a directory, it will recurse.\n" - + " -l, --listfromstdin count ZUGFeRD files from a list of linefeed separated files on runtime.\n" + + " -l, --listfromstdin count ZUGFeRD files from a list of linefeed separated files on runtime.\n" + " It will start once a blank line has been entered.\n" + "\n" + " Additional parameter for both count operations\n" + " [-i, --ignorefileextension] Check for all files (*.*) instead of PDF files only (*.pdf) in metrics, ignore PDF/A input file errors in combine\n" - + "\n" + "* Merge actions\n" + " extract extract ZUGFeRD PDF to XML file\n" + + " --action=extract extract ZUGFeRD PDF to XML file\n" + " Additional parameters (optional - user will be prompted if not defined)\n" + " [--source=]: set input PDF file\n" + " [--out=]: set output XML file\n" - + " a3only upgrade from PDF/A1 to A3 only (no ZUGFeRD data attached)\n" + + " --action=a3only upgrade from PDF/A1 to A3 only (no ZUGFeRD data attached)\n" + " Additional parameters (optional - user will be prompted if not defined)\n" + " [--source=]: set input PDF file\n" + " [--out=]: set output PDF file\n" - + " combine combine XML and PDF file to ZUGFeRD PDF file\n" + + " --action=combine combine XML and PDF file to ZUGFeRD PDF file\n" + " Additional parameters (optional - user will be prompted if not defined)\n" + " [--source=]: set input PDF file\n" + " [--source-xml=]: set input XML file\n" @@ -70,25 +72,25 @@ public class Main { + " [--version <1|2>]: set ZUGFeRD version\n" + " [--profile <...>]: set ZUGFeRD profile\n" + " For ZUGFeRD v1: ASIC, OMFORT or EXENDED\n" - + " For ZUGFeRD v2: INIMUM, BASIC L, ASIC, IUS, N16931, Rechnung, EXENDED " - + " upgrade upgrade ZUGFeRD XML to ZUGFeRD 2 XML\n" + + " For ZUGFeRD v2: INIMUM, BASIC L, ASIC, IUS, N16931, Rechnung, EXENDED \n" + + " --action=upgrade upgrade ZUGFeRD XML to ZUGFeRD 2 XML\n" + " Additional parameters (optional - user will be prompted if not defined)\n" + " [--source ]: set input XML ZUGFeRD 1 file\n" + " [--out ]: set output XML ZUGFeRD 2 file\n" - + " validate validate XML or PDF file \n" + + " --action=validate validate XML or PDF file \n" + " [--no-notices]: refrain from reporting notices\n" + " [--logAppend=]: text to be added to log line\n" + " Additional parameters (optional - user will be prompted if not defined)\n" + " [--source=]: input PDF or XML file\n" - + " validateExpectValid validate directory expecting positive results \n" + + " --action=validateExpectValid validate directory expecting positive results \n" + " [--no-notices]: refrain from reporting notices\n" + " Additional parameters (optional - user will be prompted if not defined)\n" + " -d, --directory to check recursively \n" - + " validateExpectInvalid validate directory expecting negative results \n" + + " --action=validateExpectInvalid validate directory expecting negative results \n" + " [--no-notices]: refrain from reporting notices\n" + " Additional parameters (optional - user will be prompted if not defined)\n" + " -d, --directory to check recursively\n" - + " visualize convert XML to HTML \n" + + " --action=visualize convert XML to HTML \n" ; } From 8ddfd3aa57aa696f97dd24d054d16eb55ee6ff10 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Thu, 12 Nov 2020 18:17:37 +0100 Subject: [PATCH 12/16] updated history, readme and release notes --- History.md | 14 ++++++++++++-- README.md | 19 ++++--------------- Release_Notes.md | 32 +------------------------------- 3 files changed, 17 insertions(+), 48 deletions(-) diff --git a/History.md b/History.md index cee75993..1decda5a 100644 --- a/History.md +++ b/History.md @@ -1,5 +1,15 @@ -## 2.0.0 -### done + +### 2.0.1 todo +- dont show empty tax number field +- fail when no bankverbindung? +- fail when xr attrs missing? +- build xr skonto??? +- *validator not to XR error on ZF files (only notices) +- xmp errors may not show correctly in log + +2.0.0 +===== +2020-11-12 - support for ZF 2.1.1, i.e. "Reference profile" Xrechnung - ZF 2.1.1 now default (up to 1.7.8 ZF2 could be set but ZF1 was default) diff --git a/README.md b/README.md index b51bc5f9..c39a9eee 100644 --- a/README.md +++ b/README.md @@ -19,24 +19,13 @@ More information in [the mustang documentation](https://github.com/ZUGFeRD/musta Usage ----- -If you setup a Maven project, you can grab the artifacts from this maven repository. - - -```xml - - - mustang-mvn-repo - https://raw.github.com/ZUGFeRD/mustangproject/mvn-repo/ - - -``` - -As dependency use this +If you setup a Maven project, you can grab the artifacts using ```xml - org.mustangproject.ZUGFeRD - mustang + org.mustangproject + library + 2.0.0 ``` diff --git a/Release_Notes.md b/Release_Notes.md index fb803b51..838ec2ba 100644 --- a/Release_Notes.md +++ b/Release_Notes.md @@ -31,38 +31,8 @@ to for non-interactive (i.e., batch) processing. The source file parameter for validation changed -from `-f` (ZUV) to the usual `--source`. The following -result codes apply: +from `-f` (ZUV) to the usual `--source`. -| section | meaning | -|---|---| -| 1 | file not found | -| 2 | additional data schema validation fails | -| 3 | xml data not found | -| 4 | schematron rule failed | -| 5 | file too small | -| 6 | VeraPDFException | -| 7 | IOException PDF | -| 8 | File does not look like PDF nor XML (contains neither %PDF nor Date: Mon, 16 Nov 2020 17:34:49 +0100 Subject: [PATCH 13/16] Merge XMPMetadata and try making the PDF more compliant if it isn't already --- .../org/mustangproject/commandline/Main.java | 1 - .../ZUGFeRD/ZUGFeRDExporterFromA1.java | 1 - .../ZUGFeRD/ZUGFeRDExporterFromA3.java | 272 ++++++++++++++---- library/src/main/resources/sRGB.icc | Bin 0 -> 3024 bytes 4 files changed, 220 insertions(+), 54 deletions(-) create mode 100644 library/src/main/resources/sRGB.icc 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 46a24899..5221d9a8 100755 --- a/Mustang-CLI/src/main/java/org/mustangproject/commandline/Main.java +++ b/Mustang-CLI/src/main/java/org/mustangproject/commandline/Main.java @@ -48,7 +48,6 @@ public class Main { private static String getUsage() { return "Usage: --action metrics|combine|extract|a3only|validate|visualize [-d,--directory] [-l,--listfromstdin] [-i,--ignore fileextension, PDF/A errors] | [-h,--help] \r\n" + " --action=metrics\n" - + + " -d, --directory count ZUGFeRD files in directory to be scanned\n" + " If it is a directory, it will recurse.\n" + " -l, --listfromstdin count ZUGFeRD files from a list of linefeed separated files on runtime.\n" diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA1.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA1.java index e45dae0c..06680c5d 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA1.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA1.java @@ -84,7 +84,6 @@ public class ZUGFeRDExporterFromA1 extends ZUGFeRDExporterFromA3 implements IZUG } public ZUGFeRDExporterFromA1() { - ensurePDFisUpgraded = true; setZUGFeRDVersion(ZUGFeRDExporterFromA3.DefaultZUGFeRDVersion); } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java index 1bbd6607..888ff1a6 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java @@ -26,16 +26,19 @@ import org.apache.pdfbox.pdmodel.*; import org.apache.pdfbox.pdmodel.common.PDMetadata; import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; -import org.apache.pdfbox.preflight.PreflightDocument; -import org.apache.pdfbox.preflight.exception.ValidationException; -import org.apache.pdfbox.preflight.parser.PreflightParser; +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDMarkInfo; +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot; +import org.apache.pdfbox.pdmodel.graphics.color.PDOutputIntent; import org.apache.pdfbox.preflight.utils.ByteArrayDataSource; import org.apache.xmpbox.XMPMetadata; import org.apache.xmpbox.schema.AdobePDFSchema; import org.apache.xmpbox.schema.DublinCoreSchema; import org.apache.xmpbox.schema.PDFAIdentificationSchema; import org.apache.xmpbox.schema.XMPBasicSchema; +import org.apache.xmpbox.type.ArrayProperty; import org.apache.xmpbox.type.BadFieldValueException; +import org.apache.xmpbox.xml.DomXmpParser; +import org.apache.xmpbox.xml.XmpParsingException; import org.apache.xmpbox.xml.XmpSerializer; import org.mustangproject.FileAttachment; @@ -44,6 +47,7 @@ import javax.activation.FileDataSource; import javax.xml.transform.TransformerException; import java.io.*; import java.util.ArrayList; +import java.util.Calendar; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Map; @@ -56,8 +60,11 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte protected PDFAConformanceLevel conformanceLevel = PDFAConformanceLevel.UNICODE; protected ArrayList fileAttachments = new ArrayList(); - protected boolean ensurePDFisUpgraded = true; - + /** + * This flag controls whether or not the metadata is overwritten, or kind of merged. + * The merging probably needs to be overhauled, but for my purpose it was good enough. + */ + protected boolean overwrite = true; private boolean disableAutoClose; private boolean fileAttached = false; @@ -71,7 +78,6 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte * IZUGFeRDExportableTransaction for the XML to be populated. */ protected PDMetadata metadata = null; - protected PDFAIdentificationSchema pdfaid = null; protected XMPMetadata xmp = null; /** * Producer attribute for PDF @@ -86,6 +92,16 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte */ protected String creatorTool = "mustangproject"; + /** @deprecated author is never set yet */ + @Deprecated + protected String author; + /** @deprecated title is never set yet */ + @Deprecated + protected String title; + /** @deprecated subject is never set yet */ + @Deprecated + protected String subject; + private PDDocument doc; private HashMap additionalXMLs = new HashMap(); @@ -223,7 +239,6 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte public ZUGFeRDExporterFromA3() { super(); - ensurePDFisUpgraded = false; } public void attachFile(FileAttachment file) { @@ -460,46 +475,23 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte } protected void prepareDocument() throws IOException { - String fullProducer = producer + " (via mustangproject.org " + org.mustangproject.ZUGFeRD.Version.VERSION + ")"; PDDocumentCatalog cat = doc.getDocumentCatalog(); metadata = new PDMetadata(doc); cat.setMetadata(metadata); - xmp = XMPMetadata.createXMPMetadata(); + xmp = getXmpMetadata(); + writeAdobePDFSchema(xmp); + writePDFAIdentificationSchema(xmp); + writeDublinCoreSchema(xmp); + writeXMLBasicSchema(xmp); + writeDocumentInformation(); - pdfaid = new PDFAIdentificationSchema(xmp); + // the following three lines are intended to make the pdf more PDF/A conformant if it isn't already + addSRGBOutputIntend(); + setMarked(); + addStructureTreeRoot(); - xmp.addSchema(pdfaid); - - DublinCoreSchema dc = xmp.createAndAddDublinCoreSchema(); - - dc.addCreator(creator); - - XMPBasicSchema xsb = xmp.createAndAddXMPBasicSchema(); - - xsb.setCreatorTool(creatorTool); - xsb.setCreateDate(GregorianCalendar.getInstance()); - // PDDocumentInformation pdi=doc.getDocumentInformation(); - PDDocumentInformation pdi = new PDDocumentInformation(); - pdi.setProducer(fullProducer); - pdi.setAuthor(creator); - doc.setDocumentInformation(pdi); - - AdobePDFSchema pdf = xmp.createAndAddAdobePDFSchema(); - pdf.setProducer(fullProducer); - if (ensurePDFisUpgraded) { - try { - pdfaid.setConformance(conformanceLevel.getLetter()); - } catch (BadFieldValueException ex) { - // This should be impossible, because it would occur only if an illegal - // conformance level is supplied, - // however the enum enforces that the conformance level is valid. - throw new Error(ex); - } - - pdfaid.setPart(3); - } addXMP(xmp); /* * this is the only line where we do something Zugferd-specific, i.e. add PDF * metadata specifically for Zugferd, not generically for a embedded file @@ -544,23 +536,189 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte return this; } - protected byte[] serializeXmpMetadata(XMPMetadata xmpMetadata) throws TransformerException { - XmpSerializer serializer = new XmpSerializer(); - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + /** + * Reads the XMPMetadata from the PDDocument, if it exists. + * Otherwise creates XMPMetadata. + */ + protected XMPMetadata getXmpMetadata() { + PDMetadata meta = doc.getDocumentCatalog().getMetadata(); + if (meta != null) { + try { + DomXmpParser xmpParser = new DomXmpParser(); + return xmpParser.parse(meta.toByteArray()); + } catch (XmpParsingException | IOException e) { + // TODO use logging or handle the error somehow + } + } + return XMPMetadata.createXMPMetadata(); + } - String prefix = ""; - String suffix = ""; + protected byte[] serializeXmpMetadata(XMPMetadata xmpMetadata) throws TransformerException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + new XmpSerializer().serialize(xmpMetadata, buffer, true); // see https://github.com/ZUGFeRD/mustangproject/issues/44 + return buffer.toByteArray(); + } + + /** + * Sets the producer if the overwrite flag is set or the producer is not already set. + * Sets the PDFVersion to 1.4 if the field is empty. + */ + protected void writeAdobePDFSchema(XMPMetadata xmp) { + AdobePDFSchema pdf = getAdobePDFSchema(xmp); + if (overwrite || isEmpty(pdf.getProducer())) + pdf.setProducer(producer); + } + + /** + * Returns the AdobePDFSchema from the XMPMetadata if it exists. + * If the overwrite flag is set or no AdobePDFSchema exists in the XMPMetadata, it is created, added and returned. + */ + protected AdobePDFSchema getAdobePDFSchema(XMPMetadata xmp) { + AdobePDFSchema pdf = xmp.getAdobePDFSchema(); + if (pdf != null) + if (overwrite) + xmp.removeSchema(pdf); + else + return pdf; + return xmp.createAndAddAdobePDFSchema(); + } + + protected void writePDFAIdentificationSchema(XMPMetadata xmp) { + PDFAIdentificationSchema pdfaid = getPDFAIdentificationSchema(xmp); + if (overwrite || isEmpty(pdfaid.getConformance())) { + try { + pdfaid.setConformance(conformanceLevel.getLetter()); + } catch (BadFieldValueException ex) { + // This should be impossible, because it would occur only if an illegal + // conformance level is supplied, + // however the enum enforces that the conformance level is valid. + throw new Error(ex); + } + } + pdfaid.setPart(3); + } + + protected PDFAIdentificationSchema getPDFAIdentificationSchema(XMPMetadata xmp) { + PDFAIdentificationSchema pdfaid = xmp.getPDFIdentificationSchema(); + if (pdfaid != null) + if (overwrite) + xmp.removeSchema(pdfaid); + else + return pdfaid; + return xmp.createAndAddPFAIdentificationSchema(); + } + + protected void writeDublinCoreSchema(XMPMetadata xmp) { + DublinCoreSchema dc = getDublinCoreSchema(xmp); + if (dc.getFormat() == null) + dc.setFormat("application/pdf"); + if ((overwrite || dc.getCreators() == null || dc.getCreators().isEmpty()) && creator != null) + dc.addCreator(creator); + if ((overwrite || dc.getDates() == null || dc.getDates().isEmpty()) && creator != null) + dc.addDate(Calendar.getInstance()); + + ArrayProperty titleProperty = dc.getTitleProperty(); + if (titleProperty != null) { + if (overwrite && !isEmpty(title)) { + dc.removeProperty(titleProperty); + dc.setTitle(title); + } else if (titleProperty.getElementsAsString().stream().anyMatch("Untitled"::equalsIgnoreCase)) { + // remove unfitting ghostscript default + dc.removeProperty(titleProperty); + } + } else if (!isEmpty(title)) { + dc.setTitle(title); + } + } + + protected DublinCoreSchema getDublinCoreSchema(XMPMetadata xmp) { + DublinCoreSchema dc = xmp.getDublinCoreSchema(); + if (dc != null) + if (overwrite) + xmp.removeSchema(dc); + else + return dc; + return xmp.createAndAddDublinCoreSchema(); + } + + protected void writeXMLBasicSchema(XMPMetadata xmp) { + XMPBasicSchema xsb = getXmpBasicSchema(xmp); + if (overwrite || isEmpty(xsb.getCreatorTool()) || "UnknownApplication".equals(xsb.getCreatorTool())) + xsb.setCreatorTool(creatorTool); + if (overwrite || xsb.getCreateDate() == null) + xsb.setCreateDate(GregorianCalendar.getInstance()); + } + + protected XMPBasicSchema getXmpBasicSchema(XMPMetadata xmp) { + XMPBasicSchema xsb = xmp.getXMPBasicSchema(); + if (xsb != null) + if (overwrite) + xmp.removeSchema(xsb); + else + return xsb; + return xmp.createAndAddXMPBasicSchema(); + } + + protected void writeDocumentInformation() { + String fullProducer = producer + " (via mustangproject.org " + Version.VERSION + ")"; + PDDocumentInformation info = doc.getDocumentInformation(); + if (overwrite || info.getCreationDate() == null) + info.setCreationDate(Calendar.getInstance()); + if (overwrite || info.getModificationDate() == null) + info.setModificationDate(Calendar.getInstance()); + if (overwrite || (isEmpty(info.getAuthor()) && !isEmpty(author))) + info.setAuthor(author); + if (overwrite || (isEmpty(info.getProducer()) && !isEmpty(fullProducer))) + info.setProducer(fullProducer); + if (overwrite || (isEmpty(info.getCreator()) && !isEmpty(creator))) + info.setCreator(creator); + if (overwrite || (isEmpty(info.getTitle()) && !isEmpty(title))) + info.setTitle(title); + if (overwrite || (isEmpty(info.getSubject()) && !isEmpty(subject))) + info.setSubject(subject); + } + + /** + * Adds an OutputIntent and the sRGB color profile if no OutputIntent exist + */ + protected void addSRGBOutputIntend() { + if (!doc.getDocumentCatalog().getOutputIntents().isEmpty()) { + return; + } try { - buffer.write(prefix.getBytes("UTF-8")); // see https://github.com/ZUGFeRD/mustangproject/issues/44 - serializer.serialize(xmpMetadata, buffer, false); - buffer.write(suffix.getBytes("UTF-8")); - } catch (UnsupportedEncodingException e) { - throw new TransformerException(e); + InputStream colorProfile = Thread.currentThread().getContextClassLoader().getResourceAsStream("sRGB.icc"); + if (colorProfile != null) { + PDOutputIntent intent = new PDOutputIntent(doc, colorProfile); + intent.setInfo("sRGB IEC61966-2.1"); + intent.setOutputCondition("sRGB IEC61966-2.1"); + intent.setOutputConditionIdentifier("sRGB IEC61966-2.1"); + intent.setRegistryName("http://www.color.org"); + doc.getDocumentCatalog().addOutputIntent(intent); + } } catch (IOException e) { - throw new TransformerException(e); + // TODO use logging or handle the error somehow + } + } + + /** + * Adds a MarkInfo element to the PDF if it doesn't already exist and sets it as marked. + */ + protected void setMarked() { + PDDocumentCatalog catalog = doc.getDocumentCatalog(); + if (catalog.getMarkInfo() == null) { + catalog.setMarkInfo(new PDMarkInfo(doc.getPages().getCOSObject())); + } + catalog.getMarkInfo().setMarked(true); + } + + /** + * Adds a StructureTreeRoot element to the PDF if it doesn't already exist. + */ + protected void addStructureTreeRoot() { + if (doc.getDocumentCatalog().getStructureTreeRoot() == null) { + doc.getDocumentCatalog().setStructureTreeRoot(new PDStructureTreeRoot()); } - return buffer.toByteArray(); } @@ -601,4 +759,14 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte return this; } + + /** + * Utility method inspired by apache commons-lang3 StringUtils. + * + * @param string the string to test + * @return true if the string is null or empty + */ + private boolean isEmpty(String string) { + return string == null || string.isEmpty(); + } } diff --git a/library/src/main/resources/sRGB.icc b/library/src/main/resources/sRGB.icc new file mode 100644 index 0000000000000000000000000000000000000000..49afbfef10f22a1832590b68369d2f248ea553b9 GIT binary patch literal 3024 zcmb`Jc{r5o8^@pboqe;-klom~#=Z=)?<7n1RL0C;EQ4W?v`H$Qlq6e;oU(N2=!6`p zq_j9fq0&N*O8IqkN}I~>9j@P{b6vkb&vRYx^M3C8x$pP6pZoda{Q^K51jvAqCy}2f z2yl0zhlYjIaZeGKxM&3c7CSY0nf@_DE7pfmuw>n3hyBge}>zEF^|hhw$p<`Vm43NktlHVq|Q#Wc`bi=uVbDr*Q%R@mv7f?y!Y| z^kpAf^uhola$__g2b6(2&;bl!0xW?IZ~(5r3;2RS5C%2@Hi!j@Kmam8HrNI7Kmj-i zj(`eK4eCGxXa=pI9dv;!;5xVs2Ehmz2NPf#yasdN16Y6{2nSIhDkKM~K$?&~WCAfE zJIEDU3k5)7P$U!s@gX6U4ef>spkk;3s(~7yU!e=o73d~31U-Nzp&96J=nIU3$uJF8 zg0)~nm^L%}Fw^fA^LPfRE#29trw!<1r9Va{W&VMZ|1m=9PiRtBq$wZwX0 z!?1DKt=K~BF>DL=GIj_%g`LOYaB?_(oGs25$HJxI@^Iz2Gq_8*VcazC6P|=u!JFXS z@ZoqqJ_lclZ^U=whw(4)3j_&*Cc&EEOW+W;5Q+$OgigX8!ZcxlC`r^N+7bhaal~E3 zGGa6F8u1bF9f?FzBUzFBNj%a{QW@zi=>}<%^qDM0)+0NUBgjJX0rF|`W%2{^I|_xO zMRA~nQ_?60C=HaWlqZx=VpK5$F;6j$*bcEuu{N<`u{YubaZPbY@lE1c;-%u}#P5jD zN)RNpB%CE!65AyzB`!#eNz6-9C5m zB-1K0D)VKP(kjPQ+*SKmHLn_8^-)$q);f{g-OAzz_Y;h`d|sHYg9xK;6_V!z_NlCqM!QnFIH(p9BdWf^4$ z67?SSISmyJAB}8{CXI)h1Wl%9tmaY8KFyC>+FBu6d$roNUTVu~dunHC zH)%i8q3GD_r0CS@+|$MCGIis1kLeET!FuL;v3iwycl2R>3w@scG5w*{nAKLR`KxPJ zk1@y$M@BlMi7{y)W3bjB$DrNdjiH8NxZxqgKEv-u=0*udbw=aHQpR4!ImVsFf1Bu; zuuUpW?wL|d-As3wc9_03(>LRq9XGpgPBr&2-)r7u{>{SDLSWHsF=MG=8EIK%ImV{PDV}wr}Iu9ovod>IbU``xwyOJy9~HW zxdypbxIS@HbBl3na+`BEci-xM*#qO@?QzIs%u~se?b+Zt=Vj@&&8yd&?7iN*!u#1; zy|se1oj$OSm(O9JN9#1#@z=Hc0$)$x!@iIGwEa^2e)q@v`}tS;KMybt$PVaRPhG!x zedGEMflh%%f#X3sLBgP(VDaFH;D+FjAub`sArqm7q1!@lhslTW!aBln;lbgj!sj=* zZaA`GI>J06FJg3~_QuSOH#f;|O4xL9v-oD#=5vvl$dJg!$geD4RxN8j$}_4eYL4y9 zKFWU0ap072X1KQ8V(yD*+vwuxmoc_6hht`9?PE)0XL-)N3f|i|kGSf%kMX|or{fnB zLK0dM@rjX%7x+^Acz$n^a#Ci}P_lklKhhQM>Ze1S!z~VeUx}qcyyv{ZCOXTM)|?=uNAQsBb82-EmewD`>Q@4 z;~X14?r^-hTB*9A`pXI4iTgF~HEp$8wWTMqC(}uM0h$Hl62xH~9T@mugq#Md^!0-Nf$P?!`-4 zm*y`gU!J`Zb7iV$bIqdq~gGTRGoA)2d|5UxvdGp&}4uAE}h0aaC6}(;i zyYQXdyVLK@-uKM=%|H2&_+jB={wKLl^`Dua`@V#Hd9jf375BC5o9?&H@7~`ZEha85 z{-8k&JYAjX7RFW<77P=HG2Mk5%@QW0(M8J6IVmAYD4?%TX0f?+23;gpmIcJWHm~TE zsB!?>_W&UKaK(pgBT{F`Sk`1q_=ApIvi~>1Kja-poFc8Ycg2@f3jlK-0Mx-$UJPB7 zEaT{Co~CjhDoy^Z4|Cv`LizZ;q8ZSF~{&Hxtp1 zNS#T^TLiqA*fhE)KaDHkvqTlK5|(a9AgVDnNsz`9Ca$I)0svP6z_+5s#f6&1#cxP2P~!kx7XBBF2+<<| literal 0 HcmV?d00001 From 510198884c5a77db86dcc91c73c509d60c8bec7b Mon Sep 17 00:00:00 2001 From: Stefan Klinke Date: Mon, 16 Nov 2020 18:23:59 +0100 Subject: [PATCH 14/16] Added a test to validate the exported PDF is identified as A3 --- .../ZUGFeRD/MustangReaderWriterEdgeTest.java | 77 +++++++++++++++---- 1 file changed, 60 insertions(+), 17 deletions(-) diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterEdgeTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterEdgeTest.java index 6b6dd7ed..56ba9542 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterEdgeTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterEdgeTest.java @@ -20,13 +20,21 @@ package org.mustangproject.ZUGFeRD; import junit.framework.Test; import junit.framework.TestSuite; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.common.PDMetadata; +import org.apache.xmpbox.XMPMetadata; +import org.apache.xmpbox.schema.PDFAIdentificationSchema; +import org.apache.xmpbox.xml.DomXmpParser; import org.junit.FixMethodOrder; import org.junit.runners.MethodSorters; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; @@ -199,24 +207,10 @@ public class MustangReaderWriterEdgeTest extends MustangReaderTestCase { public void testEdgeExport() { final String TARGET_PDF = "./target/testout-MustangGnuaccountingBeispielRE-20170509_505newEdge.pdf"; - // the writing part + final String SOURCE_PDF = "/MustangGnuaccountingBeispielRE-20170509_505PDFA3.pdf"; - try (InputStream SOURCE_PDF = - this.getClass().getResourceAsStream("/MustangGnuaccountingBeispielRE-20170509_505PDFA3.pdf"); - ) { - ZUGFeRDExporterFromA3 ze = new ZUGFeRDExporterFromA3() - .setProducer("My Application") - .setCreator(System.getProperty("user.name")) - .setZUGFeRDVersion(1) - .load(SOURCE_PDF); - ze.setTransaction(this); - String theXML = new String(ze.getProvider().getXML()); - assertTrue(theXML.contains(" Date: Tue, 17 Nov 2020 11:10:50 +0100 Subject: [PATCH 15/16] updated javadoc --- .../java/org/mustangproject/BankDetails.java | 4 +- .../main/java/org/mustangproject/Contact.java | 52 +++++++++--------- .../main/java/org/mustangproject/Invoice.java | 53 ++++++++++--------- .../main/java/org/mustangproject/Item.java | 11 ++-- .../main/java/org/mustangproject/Product.java | 30 +++++------ .../java/org/mustangproject/TradeParty.java | 46 ++++++++-------- .../java/org/mustangproject/XMLTools.java | 2 +- .../org/mustangproject/ZUGFeRD/Profiles.java | 2 +- .../mustangproject/ZUGFeRD/XMLUpgrader.java | 8 +-- 9 files changed, 108 insertions(+), 100 deletions(-) diff --git a/library/src/main/java/org/mustangproject/BankDetails.java b/library/src/main/java/org/mustangproject/BankDetails.java index 108b98fa..0b4298cb 100644 --- a/library/src/main/java/org/mustangproject/BankDetails.java +++ b/library/src/main/java/org/mustangproject/BankDetails.java @@ -22,8 +22,8 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment { * identify the IBAN. Of course you will specify your own IBAN in full length but * if you deduct from a customer's account you may e.g. leave out the first or last * digits so that nobody spying on the invoice gets to know the complete number - * @param IBAN - * @return + * @param IBAN the "IBAN ID", i.e. the IBAN or parts of it + * @return fluent setter */ public BankDetails setIBAN(String IBAN) { this.IBAN = IBAN; diff --git a/library/src/main/java/org/mustangproject/Contact.java b/library/src/main/java/org/mustangproject/Contact.java index cfab42b2..cfbfc1e9 100644 --- a/library/src/main/java/org/mustangproject/Contact.java +++ b/library/src/main/java/org/mustangproject/Contact.java @@ -15,9 +15,9 @@ public class Contact implements IZUGFeRDExportableContact { /*** * default constructor. * Name, phone and email of sender contact person are e.g. required by XRechnung - * @param name - * @param phone - * @param email + * @param name full name of the contact + * @param phone full phone number + * @param email email address of the contact */ public Contact(String name, String phone, String email) { this.name = name; @@ -27,13 +27,13 @@ public class Contact implements IZUGFeRDExportableContact { /*** * complete specification of a named contact with a different address - * @param name - * @param phone - * @param email - * @param street - * @param zip - * @param location - * @param country + * @param name full name + * @param phone full phone number + * @param email full email + * @param street street+number + * @param zip postcode + * @param location city + * @param country two-letter iso code */ public Contact(String name, String phone, String email, String street, String zip, String location, String country) { this.name = name; @@ -53,8 +53,8 @@ public class Contact implements IZUGFeRDExportableContact { /** * the first and last name of the contact - * @param name - * @return + * @param name first and last name + * @return fluent setter */ public Contact setName(String name) { this.name = name; @@ -68,8 +68,8 @@ public class Contact implements IZUGFeRDExportableContact { /*** * complete phone number of the contact - * @param phone - * @return + * @param phone the complete phone number + * @return fluent setter */ public Contact setPhone(String phone) { this.phone = phone; @@ -83,8 +83,8 @@ public class Contact implements IZUGFeRDExportableContact { /*** * (optional) complete fax number - * @param fax - * @return + * @param fax complete fax number of the contact + * @return fluent setter */ public Contact setFax(String fax) { this.fax = fax; @@ -97,8 +97,8 @@ public class Contact implements IZUGFeRDExportableContact { /*** * personal email address of the contact person - * @param email - * @return + * @param email the email address of the contact + * @return fluent setter */ public Contact setEMail(String email) { this.email = email; @@ -111,8 +111,8 @@ public class Contact implements IZUGFeRDExportableContact { /*** * the postcode, if the address is different to the organisation - * @param zip - * @return + * @param zip the postcode of the contact + * @return fluent setter */ public Contact setZIP(String zip) { this.zip = zip; @@ -126,8 +126,8 @@ public class Contact implements IZUGFeRDExportableContact { /** * street and number, if the address is different to the organisation - * @param street - * @return + * @param street street and number of the contact + * @return fluent setter */ public Contact setStreet(String street) { this.street = street; @@ -141,8 +141,8 @@ public class Contact implements IZUGFeRDExportableContact { /*** * city of the contact person, if different from organisation - * @param location - * @return + * @param location city + * @return fluent setter */ public Contact setLocation(String location) { this.location = location; @@ -156,8 +156,8 @@ public class Contact implements IZUGFeRDExportableContact { /*** * two-letter ISO country code of the contact, if different from organisation - * @param country - * @return + * @param country two-letter iso code + * @return fluent setter */ public Contact setCountry(String country) { this.country = country; diff --git a/library/src/main/java/org/mustangproject/Invoice.java b/library/src/main/java/org/mustangproject/Invoice.java index cc398081..68c49339 100644 --- a/library/src/main/java/org/mustangproject/Invoice.java +++ b/library/src/main/java/org/mustangproject/Invoice.java @@ -191,8 +191,8 @@ public class Invoice implements IExportableTransaction { /*** * usually the order number or in case of a correction the original invoice number - * @param buyerOrderReferencedDocumentID - * @return + * @param buyerOrderReferencedDocumentID string with number + * @return fluent setter */ public Invoice setBuyerOrderReferencedDocumentID(String buyerOrderReferencedDocumentID) { this.buyerOrderReferencedDocumentID = buyerOrderReferencedDocumentID; @@ -205,9 +205,9 @@ public class Invoice implements IExportableTransaction { } /*** - * when the order (or whatever reference in BuyerOrderReferencedDocumentID) was issued - * @param buyerOrderReferencedDocumentIssueDateTime - * @return + * when the order (or whatever reference in BuyerOrderReferencedDocumentID) was issued (@todo switch to date?) + * @param buyerOrderReferencedDocumentIssueDateTime IssueDateTime in format CCYY-MM-DDTHH:MM:SS + * @return fluent setter */ public Invoice setBuyerOrderReferencedDocumentIssueDateTime(String buyerOrderReferencedDocumentIssueDateTime) { this.buyerOrderReferencedDocumentIssueDateTime = buyerOrderReferencedDocumentIssueDateTime; @@ -374,8 +374,10 @@ public class Invoice implements IExportableTransaction { /*** * sets a named sender contact - * @param ownContact - * @return + * @deprecated use setSender + * @see Contact + * @param ownContact the sender contact + * @return fluent setter */ public Invoice setOwnContact(Contact ownContact) { this.sender.setContact(ownContact); @@ -389,8 +391,8 @@ public class Invoice implements IExportableTransaction { /** * required. * sets the invoice receiving institution = invoicee - * @param recipient - * @return + * @param recipient the invoicee organisation + * @return fluent setter */ public Invoice setRecipient(TradeParty recipient) { this.recipient = recipient; @@ -400,8 +402,8 @@ public class Invoice implements IExportableTransaction { /** * required. * sets the invoicing institution = invoicer - * @param sender - * @return + * @param sender the invoicer + * @return fluent setter */ public Invoice setSender(TradeParty sender) { this.sender = sender; @@ -468,8 +470,8 @@ public class Invoice implements IExportableTransaction { /*** * if the delivery address is not the recipient address, it can be specified here - * @param deliveryAddress - * @return + * @param deliveryAddress the goods receiving organisation + * @return fluent setter */ public Invoice setDeliveryAddress(TradeParty deliveryAddress) { this.deliveryAddress = deliveryAddress; @@ -484,8 +486,9 @@ public class Invoice implements IExportableTransaction { /** * required * adds invoice "lines" :-) - * @param item - * @return + * @see Item + * @param item the invoice line + * @return fluent setter */ public Invoice addItem(IZUGFeRDExportableItem item) { ZFItems.add(item); @@ -510,8 +513,9 @@ public class Invoice implements IExportableTransaction { /*** * adds a document level addition to the price - * @param izac - * @return + * @see Charge + * @param izac the charge to be applied + * @return fluent setter */ public Invoice addCharge(IZUGFeRDAllowanceCharge izac) { Charges.add(izac); @@ -520,8 +524,9 @@ public class Invoice implements IExportableTransaction { /*** * adds a document level rebate - * @param izac - * @return + * @see Allowance + * @param izac the allowance to be applied + * @return fluent setter */ public Invoice addAllowance(IZUGFeRDAllowanceCharge izac) { Allowances.add(izac); @@ -530,8 +535,8 @@ public class Invoice implements IExportableTransaction { /*** * adds the ID of a contract referenced in the invoice - * @param s - * @return + * @param s the contract number + * @return fluent setter */ public Invoice setContractReferencedDocument(String s) { contractReferencedDocument = s; @@ -543,8 +548,8 @@ public class Invoice implements IExportableTransaction { * sets a document level delivery period, * which is optional additional to the mandatory deliverydate * and which will become a BillingSpecifiedPeriod-Element - * @param start - * @param end + * @param start the date of first delivery + * @param end the date of last delivery * @return fluent setter */ public Invoice setDetailedDeliveryPeriod(Date start, Date end) { @@ -568,7 +573,7 @@ public class Invoice implements IExportableTransaction { /*** * adds a free text paragraph, which will become a includedNote element - * @param text + * @param text freeform UTF8 plain text * @return fluent setter */ public Invoice addNote(String text) { diff --git a/library/src/main/java/org/mustangproject/Item.java b/library/src/main/java/org/mustangproject/Item.java index b31c521c..7ba5c359 100644 --- a/library/src/main/java/org/mustangproject/Item.java +++ b/library/src/main/java/org/mustangproject/Item.java @@ -35,8 +35,8 @@ public class Item implements IZUGFeRDExportableItem { /** * should only be set by calculator classes or maybe when reading from XML - * @param lineTotalAmount - * @return + * @param lineTotalAmount price*quantity of this line + * @return fluent setter */ public Item setLineTotalAmount(BigDecimal lineTotalAmount) { this.lineTotalAmount = lineTotalAmount; @@ -50,7 +50,8 @@ public class Item implements IZUGFeRDExportableItem { /*** * the list price without VAT (sic!), refer to EN16931-1 for definition - * @return + * @param grossPrice the list price without VAT + * @return fluent setter */ public Item setGrossPrice(BigDecimal grossPrice) { this.grossPrice = grossPrice; @@ -146,8 +147,8 @@ public class Item implements IZUGFeRDExportableItem { /*** * adds item level freetext fields (includednote) - * @param text - * @return + * @param text UTF8 plain text + * @return fluent setter */ public Item addNote(String text) { if (notes==null) { diff --git a/library/src/main/java/org/mustangproject/Product.java b/library/src/main/java/org/mustangproject/Product.java index 7a22f2a8..241e82d8 100644 --- a/library/src/main/java/org/mustangproject/Product.java +++ b/library/src/main/java/org/mustangproject/Product.java @@ -13,10 +13,10 @@ public class Product implements IZUGFeRDExportableProduct { /*** * default constructor - * @param name - * @param description + * @param name product short name + * @param description product long name * @param unit a two/three letter UN/ECE rec 20 unit code, e.g. "C62" for piece - * @param VATPercent + * @param VATPercent product vat rate */ public Product(String name, String description, String unit, BigDecimal VATPercent) { this.unit = unit; @@ -32,8 +32,8 @@ public class Product implements IZUGFeRDExportableProduct { /*** * how the seller identifies this type of product - * @param sellerAssignedID - * @return + * @param sellerAssignedID a unique String + * @return fluent setter */ public Product setSellerAssignedID(String sellerAssignedID) { this.sellerAssignedID = sellerAssignedID; @@ -46,8 +46,8 @@ public class Product implements IZUGFeRDExportableProduct { /*** * if the buyer provided an ID how he refers to this product - * @param buyerAssignedID - * @return + * @param buyerAssignedID a string the buyer provided + * @return fluent setter */ public Product setBuyerAssignedID(String buyerAssignedID) { this.buyerAssignedID = buyerAssignedID; @@ -61,8 +61,8 @@ public class Product implements IZUGFeRDExportableProduct { /*** * sets a UN/ECE rec 20 or 21 code which unit the product ships in, e.g. C62=piece - * @param unit - * @return + * @param unit 2-3 letter UN/ECE rec 20 or 21 + * @return fluent setter */ public Product setUnit(String unit) { this.unit = unit; @@ -76,8 +76,8 @@ public class Product implements IZUGFeRDExportableProduct { /** * name of the product - * @param name - * @return + * @param name short name + * @return fluent setter */ public Product setName(String name) { this.name = name; @@ -91,8 +91,8 @@ public class Product implements IZUGFeRDExportableProduct { /** * description of the product (required) - * @param description - * @return + * @param description long name + * @return fluent setter */ public Product setDescription(String description) { this.description = description; @@ -106,8 +106,8 @@ public class Product implements IZUGFeRDExportableProduct { /**** * VAT rate of the product - * @param VATPercent - * @return + * @param VATPercent vat rate of the product + * @return fluent setter */ public Product setVATPercent(BigDecimal VATPercent) { this.VATPercent = VATPercent; diff --git a/library/src/main/java/org/mustangproject/TradeParty.java b/library/src/main/java/org/mustangproject/TradeParty.java index 2774ac11..df1b4a02 100644 --- a/library/src/main/java/org/mustangproject/TradeParty.java +++ b/library/src/main/java/org/mustangproject/TradeParty.java @@ -37,7 +37,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { /*** * XML parsing constructor - * @param nodes + * @param nodes the nodelist returned e.g. from xpath */ public TradeParty(NodeList nodes) { /** @@ -109,8 +109,8 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { /** * if it's a customer, this can e.g. be the customer ID - * @param ID - * @return + * @param ID customer/seller number + * @return fluent setter */ public TradeParty setID(String ID) { this.ID = ID; @@ -119,8 +119,9 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { /*** * (optional) a named contact person - * @param c - * @return + * @see Contact + * @param c the named contact person + * @return fluent setter */ public TradeParty setContact(Contact c) { this.contact = c; @@ -129,8 +130,8 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { /*** * required (for senders, if payment is not debit): the BIC and IBAN - * @param s - * @return + * @param s bank credentials + * @return fluent setter */ public TradeParty addBankDetails(BankDetails s) { bankDetails.add(s); @@ -143,8 +144,8 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { /*** * a general tax ID - * @param taxID - * @return + * @param taxID tax number of the organisation + * @return fluent setter */ public TradeParty addTaxID(String taxID) { this.taxID = taxID; @@ -153,8 +154,8 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { /*** * the USt-ID - * @param vatID - * @return + * @param vatID Ust-ID + * @return fluent setter */ public TradeParty addVATID(String vatID) { this.vatID = vatID; @@ -178,7 +179,8 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { /*** * required, usually done in the constructor: the complete name of the organisation - * @return + * @param name complete legal name + * @return fluent setter */ public TradeParty setName(String name) { this.name = name; @@ -192,8 +194,8 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { /*** * usually set in the constructor, required for recipients in german invoices: postcode - * @param zip - * @return + * @param zip postcode + * @return fluent setter */ public TradeParty setZIP(String zip) { this.zip = zip; @@ -207,8 +209,8 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { /*** * usually set in constructor, required in germany, street and house number - * @param street - * @return + * @param street street name and number + * @return fluent setter */ public TradeParty setStreet(String street) { this.street = street; @@ -222,8 +224,8 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { /*** * usually set in constructor, usually required in germany, the city of the organisation - * @param location - * @return + * @param location city + * @return fluent setter */ public TradeParty setLocation(String location) { this.location = location; @@ -237,8 +239,8 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { /*** * two-letter ISO code of the country - * @param country - * @return + * @param country two-letter-code + * @return fluent setter */ public TradeParty setCountry(String country) { this.country = country; @@ -275,8 +277,8 @@ public class TradeParty implements IZUGFeRDExportableTradeParty { /*** * additional parts of the address, e.g. which floor. * Street address will become "lineOne", this will become "lineTwo" - * @param additionalAddress - * @return + * @param additionalAddress additional address description + * @return fluent setter */ public TradeParty setAdditionalAddress(String additionalAddress) { this.additionalAddress = additionalAddress; diff --git a/library/src/main/java/org/mustangproject/XMLTools.java b/library/src/main/java/org/mustangproject/XMLTools.java index 51b80ec5..c33e5f7d 100644 --- a/library/src/main/java/org/mustangproject/XMLTools.java +++ b/library/src/main/java/org/mustangproject/XMLTools.java @@ -122,7 +122,7 @@ public class XMLTools extends XMLWriter { /*** * removes utf8 byte order marks from byte arrays, in case one is there - * @param zugferdRaw + * @param zugferdRaw the CII XML * @return the byte array without bom */ public static byte[] removeBOM(byte[] zugferdRaw) { diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/Profiles.java b/library/src/main/java/org/mustangproject/ZUGFeRD/Profiles.java index 1d9c0779..a2930b31 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/Profiles.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/Profiles.java @@ -33,7 +33,7 @@ public class Profiles { {"BASIC", new Profile("BASIC", "urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:basic")}, {"EN16931", new Profile("EN16931", "urn:cen.eu:en16931:2017")}, {"EXTENDED", new Profile("EXTENDED", "urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended")}, - {"XRECHNUNG", new Profile("XRECHNUNG", "XRECHNUNG")} + {"XRECHNUNG", new Profile("XRECHNUNG", "urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.0")} }).collect(Collectors.toMap(data -> (String) data[0], data -> (Profile) data[1])); static Map zf1Map = Stream.of(new Object[][]{ diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/XMLUpgrader.java b/library/src/main/java/org/mustangproject/ZUGFeRD/XMLUpgrader.java index 0537c868..9ea78955 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/XMLUpgrader.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/XMLUpgrader.java @@ -27,11 +27,11 @@ public class XMLUpgrader { /*** * Takes a filename of a ZF1 XML file and returns the string of ZF2 XML - * @param xmlFilename + * @param xmlFilename the filename of the source * @return String the updated XML - * @throws FileNotFoundException - * @throws TransformerException - * @throws UnsupportedEncodingException + * @throws FileNotFoundException if the source could not be found + * @throws TransformerException if the source could not be transformed + * @throws UnsupportedEncodingException if the source was not utf8 */ public String migrateFromV1ToV2(String xmlFilename) throws FileNotFoundException, TransformerException, UnsupportedEncodingException { /** From 48dc209f944f7a3f7552563cb39269c8a00c3bc5 Mon Sep 17 00:00:00 2001 From: jstaerk Date: Tue, 17 Nov 2020 11:49:20 +0100 Subject: [PATCH 16/16] closes #172, fixes zero rated products, added some lines to the test of the XRechnung, have the XRechnung test actually wriote to file and have the XR test file validated automatically --- History.md | 5 +++- .../org/mustangproject/commandline/Main.java | 1 - .../ZUGFeRD/IZUGFeRDExportableItem.java | 6 ----- .../ZUGFeRD/IZUGFeRDExportableProduct.java | 7 +++--- .../ZUGFeRD/TransactionCalculator.java | 2 +- .../org/mustangproject/ZUGFeRD/XRTest.java | 25 ++++++++++++++----- .../mustangproject/validator/LibraryTest.java | 24 ++++++++++++++++++ 7 files changed, 52 insertions(+), 18 deletions(-) diff --git a/History.md b/History.md index 1decda5a..d1269a22 100644 --- a/History.md +++ b/History.md @@ -1,12 +1,15 @@ ### 2.0.1 todo +- confirm that VAT category code switches from S to Z on 0%VAT +- 2.1 support kleinunternehmer, reverse charge? - dont show empty tax number field - fail when no bankverbindung? - fail when xr attrs missing? - build xr skonto??? - *validator not to XR error on ZF files (only notices) - xmp errors may not show correctly in log - +- XR test now includes guideline ID #172 +- support zero-rated goods 2.0.0 ===== 2020-11-12 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 46a24899..5221d9a8 100755 --- a/Mustang-CLI/src/main/java/org/mustangproject/commandline/Main.java +++ b/Mustang-CLI/src/main/java/org/mustangproject/commandline/Main.java @@ -48,7 +48,6 @@ public class Main { private static String getUsage() { return "Usage: --action metrics|combine|extract|a3only|validate|visualize [-d,--directory] [-l,--listfromstdin] [-i,--ignore fileextension, PDF/A errors] | [-h,--help] \r\n" + " --action=metrics\n" - + + " -d, --directory count ZUGFeRD files in directory to be scanned\n" + " If it is a directory, it will recurse.\n" + " -l, --listfromstdin count ZUGFeRD files from a list of linefeed separated files on runtime.\n" diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java index 0522b2c7..8f542274 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java @@ -26,8 +26,6 @@ package org.mustangproject.ZUGFeRD; * @author jstaerk * */ -import org.mustangproject.ZUGFeRD.model.TaxCategoryCodeTypeConstants; - import java.math.BigDecimal; public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{ @@ -74,10 +72,6 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{ return BigDecimal.ONE.setScale(4); } - default String getCategoryCode() { - return TaxCategoryCodeTypeConstants.STANDARDRATE; - } - /*** * the ID of an additionally referenced document for this item * @return the id as string diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableProduct.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableProduct.java index 197bbac9..ae5d6bbd 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableProduct.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableProduct.java @@ -106,11 +106,12 @@ public interface IZUGFeRDExportableProduct { } default String getTaxCategoryCode() { - if (isIntraCommunitySupply()) { + if (getVATPercent().equals(new BigDecimal(0))) { + return "Z"; // zero rated goods + } else if (isIntraCommunitySupply()) { return "K"; } else { - return "S"; - + return "S"; // one of the "standard" rates (not neccessarily a default rate, even a deducted VAT is standard calculation) } } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java index 084b0156..af5b5807 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/TransactionCalculator.java @@ -147,7 +147,7 @@ public class TransactionCalculator implements IAbsoluteValueProvider { BigDecimal percent = currentItem.getProduct().getVATPercent(); LineCalculator lc = new LineCalculator(currentItem); VATAmount itemVATAmount = new VATAmount(lc.getItemTotalNetAmount(), lc.getItemTotalVATAmount(), - currentItem.getCategoryCode()); + currentItem.getProduct().getTaxCategoryCode()); VATAmount current = hm.get(percent); if (current == null) { hm.put(percent, itemVATAmount); diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java index a6c4990e..4f05c23e 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/XRTest.java @@ -25,9 +25,11 @@ import junit.framework.TestCase; import org.mustangproject.*; import org.junit.FixMethodOrder; import org.junit.runners.MethodSorters; -import org.mustangproject.ZUGFeRD.ZUGFeRD2PullProvider; +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; import java.math.BigDecimal; import java.util.Date; @@ -36,8 +38,8 @@ import static org.xmlunit.assertj.XmlAssert.assertThat; @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class XRTest extends TestCase { - - public void testPushExport() { + final String TARGET_XML = "./target/testout-XR.xml"; + public void testXRExport() { // the writing part @@ -45,10 +47,15 @@ public class XRTest extends TestCase { String number = "123"; String amountStr = "1.00"; BigDecimal amount = new BigDecimal(amountStr); - - Invoice i = new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setSender(new TradeParty(orgname,"teststr","55232","teststadt","DE")).setOwnTaxID("4711").setOwnVATID("0815").setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE")).setNumber(number).addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(0)), amount, new BigDecimal(1.0))); + Invoice i = new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()) + .setSender(new TradeParty(orgname,"teststr","55232","teststadt","DE").addTaxID("DE4711").addVATID("DE0815").setContact(new Contact("Hans Test","+49123456789","test@example.org")).addBankDetails(new BankDetails("DE12500105170648489890","COBADEFXXX"))) + .setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE")) + .setReferenceNumber("991-01484-64")//leitweg-id + // not using any VAT, this is also a test of zero-rated goods: + .setNumber(number).addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(0)), amount, new BigDecimal(1.0))); ZUGFeRD2PullProvider zf2p = new ZUGFeRD2PullProvider(); + zf2p.setProfile(Profiles.getByName("XRechnung")); zf2p.generateXML(i); String theXML = new String(zf2p.getXML()); assertTrue(theXML.contains("