diff --git a/History.md b/History.md index c122d89b..edf47c76 100644 --- a/History.md +++ b/History.md @@ -1,7 +1,63 @@ -- #678 -- #679 -- #681 -- #689 +- #819 + +2.16.4 +======= +2025-04-19 +- #722 extend ValidationLogVisualizer to not use only file system +- #774 disable XML parsing entities +- #742/#753 "Adresszusatz 1" (LineTwo) showing up as "Postfach" in HTML visualization +- #778 Added XEE Protection features (that were missing according to Stackoverflow) +- #759 Use the dedicated class instead of var type +- #614, #770 Exemption reason text should not be reused +- #728 Invoice setCorrection causes duplicate XML output +- #776 Fix potential resource leaks in core file processing classes +- #741 read position accountingReference +- #782/771 prevent NullPointerException on Product Description +- #772 TradeParty Name should be optional for ShipToTradeParty +- #775 not deleted tmp files +- #802 fix: capital letter for ID in listID +- #809 invoice reader to support multiple charges per item +- #812 fileattachment relation should have a default +- #818 need exceptions from files validated with validateExpectValid + + +2.16.3 +======= +2025-03-03 +- #558 ZUGFeRDInvoiceImporter does not read BankDetails.accountName +- #686 Item: add BillingSpecifiedPeriod +- #739 also parse invoiceperiod from ubl +- #745 be able to specify legalorganisation id without schema +- #747 correct profile detection +- #710 Validation Error due to empty elements +- #712 Correct bracket setting on condition for output of allowance reason. +- #725 Unable to perform XML-oriented attacks +- #685 Security Issue: XXE Vulnerability in ZUGFeRDInvoiceImporter (PR #725) +- #761 Allow to set item allowance/charges from JSON + +2.16.2 +======= +2025-02-04 +- #705 specifiedLogisticsCharge is not imported +- #707 invoiceimporter may fail if certain values are not set +- #708 embedded files cannot be determined +- #709 ZUGFeRDInvoiceImporter ignored "first" embedded file in list of pdf attachments +- #607 Enable flexible PaymentReference and a DocumentName. +- #649 Reuse toPDF method to work without any dependencies to the file system +- #650 Add net.sf.offo:fop-hyph +- #665 Fix #632: Return ubl_creditnote as Standard for CreditNotes +- #684 Optimize validation-report to pdf functionality +- #703 Fill TaxExemptionReason during InvoiceImport. +- #701 Ensure Base64 decoding can handle newlines when decoding a FileAttachment +- #691 Fix current check failures. + +2.16.1 +======= +2025-01-21 +- #678 some ubl creditnote attributes are not parsed +- #679 validation of a XR does not ignore whitespace +- #681 IBAN assigned to invoice sender not recipient on direct debit +- #689 incorrect element order when both charge reason and reasoncode are specified - be able to set detailedDeliveryPeriodFrom, detailedDeliveryPeriodTo MS188 - updated verapdf from 1.26.1 to 1.26.2 - cashDiscount JSON now corrently ignores values for cii and xr methods diff --git a/Mustang-CLI/pom.xml b/Mustang-CLI/pom.xml index 9d83688f..77b79394 100644 --- a/Mustang-CLI/pom.xml +++ b/Mustang-CLI/pom.xml @@ -3,7 +3,7 @@ org.mustangproject core - 2.16.1-SNAPSHOT + 2.17.0-SNAPSHOT 4.0.0 org.mustangproject @@ -12,7 +12,7 @@ should also work for XRechnung/CII. jar - 2.16.1-SNAPSHOT + 2.17.0-SNAPSHOT UTF-8 11 @@ -23,7 +23,7 @@ org.mustangproject validator - 2.16.1-SNAPSHOT + 2.17.0-SNAPSHOT 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 275da445..9cdb54e2 100755 --- a/Mustang-CLI/src/main/java/org/mustangproject/commandline/Main.java +++ b/Mustang-CLI/src/main/java/org/mustangproject/commandline/Main.java @@ -73,10 +73,10 @@ public class Main { + " For ZUGFeRD v2: INIMUM, BASIC L, ASIC, IUS, N16931, Rechnung, EXENDED\n" + " [--attachments ]: list of file attachments (passing a single empty file name prevents prompting)\n" + " [--no-additional-attachments]: prevent prompting for attachments\n" - + " --action ubl convert UN/CEFACT 2016b CII XML to UBL XML\n" + + " --action ubl convert UN/CEFACT 2016b CII XML to UBL XML\n" + " [--source ]: set input XML file\n" + " [--out ]: set output XML file\n" - + " --action upgrade upgrade ZUGFeRD XML to ZUGFeRD 2 XML\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" @@ -86,14 +86,18 @@ public class Main { + " Additional parameters (optional - user will be prompted if not defined)\n" + " [--source ]: input PDF or XML file\n" + " [--log-as-pdf]: save log output as pdf\n" - + " --action validateExpectInvalid validate directory expecting negative results \n" + + " --action validateExpectInvalid validate directory recursively expecting negative results \n" + " [--no-notices]: refrain from reporting notices\n" - + " Additional parameters (optional - user will be prompted if not defined)\n" + + " Additional parameters (user will be prompted if not defined)\n" + " -d, --directory to check recursively\n" - + " --action validateExpectValid validate directory expecting positive results \n" + + " Additional parameters (optional)\n" + + " --exclude: comma-separated list of filenames to ignore\n" + + " --action validateExpectValid validate directory recursively expecting positive results \n" + " [--no-notices]: refrain from reporting notices\n" - + " Additional parameters (optional - user will be prompted if not defined)\n" + + " Additional parameters (user will be prompted if not defined)\n" + " -d, --directory to check recursively \n" + + " Additional parameters (user will be prompted if not defined)\n" + + " --exclude: comma-separated list of filenames to ignore\n" + " --action visualize convert XML to HTML \n" + " [--language ]: set output lang (en, fr or de)\n" + " [--source ]: set input XML file\n" @@ -307,12 +311,13 @@ public class Main { // Plain Java // based on https://mkyong.com/java/how-to-convert-inputstream-to-string-in-java/ private static String convertInputStreamToString(InputStream is) { - int DEFAULT_BUFFER_SIZE = 8192; - ByteArrayOutputStream result = new ByteArrayOutputStream(); - byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; - int length; - try { - while ((length = is.read(buffer)) != -1) { + try (InputStream inputStream = is) { + int DEFAULT_BUFFER_SIZE = 8192; + ByteArrayOutputStream result = new ByteArrayOutputStream(); + byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; + int length; + + while ((length = inputStream.read(buffer)) != -1) { result.write(buffer, 0, length); } @@ -320,11 +325,10 @@ public class Main { return result.toString(StandardCharsets.UTF_8.name()); } catch (IOException e) { e.printStackTrace(); + return null; + // Java 10 + // return result.toString(StandardCharsets.UTF_8); } - return null; - // Java 10 - // return result.toString(StandardCharsets.UTF_8); - } /*** @@ -347,8 +351,11 @@ public class Main { Option attachmentOpt = new Option("attachments", "attachments", true, "File attachments"); attachmentOpt.setValueSeparator(','); attachmentOpt.setArgs(Option.UNLIMITED_VALUES); - options.addOption(attachmentOpt); + Option excludeOpt = new Option("exclude", "exclude", true, "Files to exclude from recursive directory traversal"); + excludeOpt.setValueSeparator(','); + excludeOpt.setArgs(Option.UNLIMITED_VALUES); + options.addOption(excludeOpt); options.addOption(new Option("source", "source", true, "which source file to use")); options.addOption(new Option("source-xml", "source-xml", true, "which source file to use")); options.addOption(new Option("language", "language", true, "output language (en, de or fr)")); @@ -389,6 +396,7 @@ public class Main { String zugferdProfile = cmd.getOptionValue("profile"); String[] attachmentFilenames = cmd.hasOption("attachments") ? cmd.getOptionValues("attachments") : null; + String[] excludedFilenames = cmd.hasOption("exclude") ? cmd.getOptionValues("exclude") : null; ArrayList attachments = new ArrayList<>(); @@ -433,9 +441,9 @@ public class Main { } else if ((action != null) && (action.equals("validate"))) { optionsRecognized = performValidate(sourceName, noNotices, cmd.getOptionValue("logAppend"), LogAsPDF); } else if ((action != null) && (action.equals("validateExpectValid"))) { - optionsRecognized = performValidateExpect(true, directoryName); + optionsRecognized = performValidateExpect(true, directoryName, excludedFilenames); } else if ((action != null) && (action.equals("validateExpectInvalid"))) { - optionsRecognized = performValidateExpect(false, directoryName); + optionsRecognized = performValidateExpect(false, directoryName, excludedFilenames); } } catch (UnrecognizedOptionException ex) { @@ -487,8 +495,8 @@ public class Main { return optionsRecognized; } - private static boolean performValidateExpect(boolean valid, String dirName) { - ValidatorFileWalker zfWalk = new ValidatorFileWalker(valid); + private static boolean performValidateExpect(boolean valid, String dirName, String[] excludedFiles) { + ValidatorFileWalker zfWalk = new ValidatorFileWalker(valid, excludedFiles); Path startingDir = Paths.get(dirName); try { Files.walkFileTree(startingDir, zfWalk); diff --git a/Mustang-CLI/src/main/java/org/mustangproject/commandline/ValidatorFileWalker.java b/Mustang-CLI/src/main/java/org/mustangproject/commandline/ValidatorFileWalker.java index 5b5d8340..a368baaa 100644 --- a/Mustang-CLI/src/main/java/org/mustangproject/commandline/ValidatorFileWalker.java +++ b/Mustang-CLI/src/main/java/org/mustangproject/commandline/ValidatorFileWalker.java @@ -1,7 +1,6 @@ package org.mustangproject.commandline; - import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.FileVisitResult; @@ -11,25 +10,29 @@ import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Date; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.mustangproject.validator.ZUGFeRDValidator; import static org.xmlunit.assertj.XmlAssert.assertThat; -public class ValidatorFileWalker - extends SimpleFileVisitor { +public class ValidatorFileWalker + extends SimpleFileVisitor { private static final Logger LOGGER = LoggerFactory.getLogger(ValidatorFileWalker.class.getCanonicalName()); // log protected PathMatcher matcher; protected ZUGFeRDValidator zul; - protected int fileCount=1; - protected boolean expectValid=true; - protected boolean allValid=true; + protected int fileCount = 1; + protected boolean expectValid = true; + protected boolean allValid = true; + protected String[] excludedFiles = {}; - public ValidatorFileWalker(boolean expectValid) { + public ValidatorFileWalker(boolean expectValid, String[] excludedFiles) { this.zul = new ZUGFeRDValidator(); - this.expectValid=expectValid; + this.expectValid = expectValid; + this.excludedFiles = excludedFiles; matcher = FileSystems.getDefault().getPathMatcher("glob:*.{pdf,xml}"); } @@ -37,54 +40,58 @@ public class ValidatorFileWalker public boolean getResult() { return allValid; } - // Print information about - // each type of file. - @Override - public FileVisitResult visitFile(Path file, - BasicFileAttributes attr) { - DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - //get current date time with Date() - Date date = new Date(); - String expectedString="valid"; - if (!expectValid) { - expectedString="invalid"; - } - if ((attr!=null)&&(attr.isRegularFile())) { - if (matcher.matches(file.getFileName())) { - String thisResultString=" valid"; - try { - assertThat(zul.validate(file.toAbsolutePath().toString())).valueByXPath("/validation/summary/@status") - .asString() - .isEqualTo(expectedString); - - } catch (AssertionError ae) { - thisResultString="invalid"; - allValid=false; - } - LOGGER.info(String.format("\n@%s Testing file %d: %s (%s)", dateFormat.format(date), fileCount++, thisResultString, file)); - - } - } - return FileVisitResult.CONTINUE; - } - // Print each directory visited. - @Override - public FileVisitResult postVisitDirectory(Path dir, - IOException exc) { - LOGGER.info("\nDirectory: %s%n", dir); - return FileVisitResult.CONTINUE; - } + // Print information about + // each type of file. + @Override + public FileVisitResult visitFile(Path file, + BasicFileAttributes attr) { + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + //get current date time with Date() + Date date = new Date(); + String expectedString = "valid"; + if (!expectValid) { + expectedString = "invalid"; + } + if ((attr != null) && (attr.isRegularFile())) { + if (matcher.matches(file.getFileName())) { + // I could have extended the path matcher but an exclusion list is quite simple + if ((excludedFiles == null) || (!Arrays.asList(excludedFiles).contains(file.getFileName().toString()))) { - // If there is some error accessing - // the file, let the user know. - // If you don't override this method - // and an error occurs, an IOException - // is thrown. - @Override - public FileVisitResult visitFileFailed(Path file, - IOException exc) { - LOGGER.error(exc.getMessage(),exc); - return FileVisitResult.CONTINUE; - } + String thisResultString = " valid"; + try { + assertThat(zul.validate(file.toAbsolutePath().toString())).valueByXPath("/validation/summary/@status") + .asString() + .isEqualTo(expectedString); + + } catch (AssertionError ae) { + thisResultString = "invalid"; + allValid = false; + } + LOGGER.info(String.format("\n@%s Testing file %d: %s (%s) ", dateFormat.format(date), fileCount++, thisResultString, file)); + } + } + } + return FileVisitResult.CONTINUE; + } + + // Print each directory visited. + @Override + public FileVisitResult postVisitDirectory(Path dir, + IOException exc) { + LOGGER.info(String.format("\nDirectory: %s \n", dir)); + return FileVisitResult.CONTINUE; + } + + // If there is some error accessing + // the file, let the user know. + // If you don't override this method + // and an error occurs, an IOException + // is thrown. + @Override + public FileVisitResult visitFileFailed(Path file, + IOException exc) { + LOGGER.error(exc.getMessage(), exc); + return FileVisitResult.CONTINUE; + } } diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..38dd608d --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,29 @@ +# Security Policy + +## Supported Versions + +The following versions are currently being supported with security updates. + +| Version | Supported | +| ------- | ------------------ | +| 2.x.x | :white_check_mark: | +| < 2.0 | :x: | + +## Reporting a Vulnerability + +Feel free to submit issues to info at mustangproject.org with [security] indicated in the subject. +We may ask back questions but we usually open (or communicate about) an issue (potentially in a private location you would be provided with access to) and decide on the severity within two working days. + +Please indicate +* a proof of concept, if possible +* If any of the information you submit, e.g. an invoice which can not be [anonymized](https://github.com/ZUGFeRD/einvoice-anonymizer), is confidential +* A quick justification why you require a fix in a older version than he most up to date one, if you can not update to the most recent version +* If you require encrypted communication (our GPG fingerprint will likely be 68F4 2269 8165 F0F5 63CA A13B 7CB7 1548 B596 66A3) + + +## After your Report + +We try to fix critical issues in less than a week, and release a fixed version in less than two weeks. + +Thank you for keeping our software safe! + diff --git a/library/pom.xml b/library/pom.xml index 81a1716f..e84707f9 100644 --- a/library/pom.xml +++ b/library/pom.xml @@ -3,13 +3,13 @@ org.mustangproject core - 2.16.1-SNAPSHOT + 2.17.0-SNAPSHOT 4.0.0 org.mustangproject library - 2.16.1-SNAPSHOT + 2.17.0-SNAPSHOT jar Library to write, read and validate e-invoices (Factur-X, ZUGFeRD, Order-X, XRechnung/CII) FOSS Java library to read, write and validate european electronic invoices and orders in the UN/CEFACT @@ -59,6 +59,12 @@ slf4j-api 2.0.9 + + net.sf.offo + fop-hyph + 2.0 + runtime + net.sf.saxon diff --git a/library/src/main/java/org/mustangproject/Charge.java b/library/src/main/java/org/mustangproject/Charge.java index 6232611d..1b3320bf 100644 --- a/library/src/main/java/org/mustangproject/Charge.java +++ b/library/src/main/java/org/mustangproject/Charge.java @@ -134,7 +134,10 @@ public class Charge implements IZUGFeRDAllowanceCharge { if (totalAmount!=null) { return totalAmount; } else { - throw new RuntimeException("totalAmount must be set"); + if (percent==null) { + throw new RuntimeException("totalAmount must be set"); + } + return null; } } diff --git a/library/src/main/java/org/mustangproject/FileAttachment.java b/library/src/main/java/org/mustangproject/FileAttachment.java index ed1fe39f..99a60419 100644 --- a/library/src/main/java/org/mustangproject/FileAttachment.java +++ b/library/src/main/java/org/mustangproject/FileAttachment.java @@ -9,7 +9,7 @@ public class FileAttachment { protected String filename; protected String mimetype; - protected String relation; + protected String relation = "Unspecified"; protected String description; protected byte[] data; @@ -29,6 +29,13 @@ public class FileAttachment { this.description = "Additional file attachment"; } + public FileAttachment(String filename, String mimetype, byte[] data) { + this.filename = filename; + this.mimetype = mimetype; + this.data = data; + this.description = "Additional file attachment"; + } + public String getDescription() { return description; } @@ -60,6 +67,28 @@ public class FileAttachment { return relation; } + /*** + * only needed when embedded in PDF described + * + * values + * - Source shall be used if this file specification is the original + * source material for the associated content. + * - Data shall be used if this file specification represents information + * used to derive a visual presentation, such as for a table or a + * graph. + * - Alternative shall be used if this file specification is an alternative + * representation of content, for example audio. + * - Supplement shall be used if this file specification represents a + * supplemental representation of the original source or data that + * may be more easily consumable (e.g. A MathML version of an + * equation). + * - Unspecified shall be used when the relationship is not known + * or cannot be described using one of the other values. + * @param relation String: either : Source, Data or Alternative. Usually Data, except source if the file attachment + * is the basis for the pdf (xrechnung2fx) or Alternative if it contains the same content (e.g. the + * factur-x.xml file in a factur-x PDF) + * @return fluent setter + */ public FileAttachment setRelation(String relation) { this.relation = relation; return this; diff --git a/library/src/main/java/org/mustangproject/Invoice.java b/library/src/main/java/org/mustangproject/Invoice.java index 94b3c643..e3123b7a 100644 --- a/library/src/main/java/org/mustangproject/Invoice.java +++ b/library/src/main/java/org/mustangproject/Invoice.java @@ -69,6 +69,7 @@ public class Invoice implements IExportableTransaction { protected String vatDueDateTypeCode = null; protected String creditorReferenceID; // required when direct debit is used. private BigDecimal roundingAmount=null; + private String paymentReference; // Remittance information / Verwendungszweck, BT-83 public Invoice() { ZFItems = new ArrayList<>(); @@ -124,7 +125,11 @@ public class Invoice implements IExportableTransaction { * @return fluent setter */ public Invoice setAdditionalReferencedDocuments(FileAttachment[] fileArr) { - xmlEmbeddedFiles = new ArrayList<>(Arrays.asList(fileArr)); + if (fileArr!=null) { + xmlEmbeddedFiles = new ArrayList<>(Arrays.asList(fileArr)); + } else { + xmlEmbeddedFiles = new ArrayList<>(); + } return this; } @@ -153,7 +158,6 @@ public class Invoice implements IExportableTransaction { */ public Invoice setCorrection(String number) { setInvoiceReferencedDocumentID(number); - addInvoiceReferencedDocument(new ReferencedDocument(number)); documentCode = DocumentCodeTypeConstants.CORRECTEDINVOICE; return this; } @@ -679,6 +683,15 @@ public class Invoice implements IExportableTransaction { } + public String getPaymentReference() { + return paymentReference; + } + + public Invoice setPaymentReference(String paymentReference) { + this.paymentReference = paymentReference; + return this; + } + @Override public TradeParty getDeliveryAddress() { return deliveryAddress; diff --git a/library/src/main/java/org/mustangproject/Item.java b/library/src/main/java/org/mustangproject/Item.java index 8fefa87f..956979fd 100644 --- a/library/src/main/java/org/mustangproject/Item.java +++ b/library/src/main/java/org/mustangproject/Item.java @@ -42,6 +42,7 @@ public class Item implements IZUGFeRDExportableItem { protected ArrayList Allowances = new ArrayList<>(); protected ArrayList Charges = new ArrayList<>(); protected List includedNotes = null; + protected String accountingReference; //protected HashMap attributes = new HashMap<>(); /*** @@ -172,7 +173,10 @@ public class Item implements IZUGFeRDExportableItem { icnm.getAsNodeMap("ApplicableTradeTax") .flatMap(cnm -> cnm.getAsBigDecimal("RateApplicablePercent", "ApplicablePercent")) .ifPresent(product::setVATPercent); - icnm.getAsNodeMap("SpecifiedTradeAllowanceCharge").ifPresent(stac -> { + icnm.getAsNodeMap("ApplicableTradeTax") + .flatMap(cnm -> cnm.getAsString("ExemptionReason")) + .ifPresent(product::setTaxExemptionReason); + icnm.getAllNodes("SpecifiedTradeAllowanceCharge").map(NodeMap::new).forEach(stac -> { stac.getAsNodeMap("ChargeIndicator").ifPresent(ci -> { String isChargeString=ci.getAsString("Indicator").get(); String percentString=stac.getAsStringOrNull("CalculationPercent"); @@ -187,8 +191,12 @@ public class Item implements IZUGFeRDExportableItem { if (amountString!=null) { izac.setTotalAmount(new BigDecimal(amountString)); } + if(percentString!=null) { izac.setPercent(new BigDecimal(percentString)); + } + if(reason!=null) { izac.setReason(reason); + } if (isChargeString.equalsIgnoreCase("false")) { addAllowance(izac); @@ -206,6 +214,14 @@ public class Item implements IZUGFeRDExportableItem { } icnm.getAllNodes("AdditionalReferencedDocument").map(ReferencedDocument::fromNode).forEach(this::addAdditionalReference); + + icnm.getAsString("ReceivableSpecifiedTradeAccountingAccount").ifPresent(s -> this.accountingReference = s == null ? null : s.trim()); + + icnm.getAsNodeMap("BillingSpecifiedPeriod").ifPresent(periodNode -> { + Date start = periodNode.getAsNodeMap("StartDateTime").flatMap(dateTimeNode -> dateTimeNode.getNode("DateTimeString")).map(dts -> XMLTools.tryDate(dts)).orElse(null); + Date end = periodNode.getAsNodeMap("EndDateTime").flatMap(dateTimeNode -> dateTimeNode.getNode("DateTimeString")).map(dts -> XMLTools.tryDate(dts)).orElse(null); + setDetailedDeliveryPeriod(start, end); + }); }); itemMap.getAsNodeMap("AssociatedDocumentLineDocument").ifPresent(adld -> { @@ -353,6 +369,29 @@ public class Item implements IZUGFeRDExportableItem { return Allowances.toArray(new IZUGFeRDAllowanceCharge[0]); } + /*** + * jackson convenience method + */ + public void setItemAllowances(ArrayList theAllowances) { + if (theAllowances!=null) { + Allowances.clear(); + for (Allowance theAllowance : theAllowances) { + Allowances.add(theAllowance); + } + } + } + /*** + * jackson convenience method + */ + public void setItemCharges(ArrayList theCharges) { + if (theCharges!=null) { + Charges.clear(); + for (Charge theCharge : theCharges) { + Charges.add(theCharge); + } + } + } + @Override public IZUGFeRDAllowanceCharge[] getItemCharges() { if (Charges.isEmpty()) { @@ -525,4 +564,9 @@ public class Item implements IZUGFeRDExportableItem { public List getNotesWithSubjectCode() { return includedNotes; } + + @Override + public String getAccountingReference() { + return accountingReference; + } } diff --git a/library/src/main/java/org/mustangproject/LegalOrganisation.java b/library/src/main/java/org/mustangproject/LegalOrganisation.java index 705420c2..d5b6d42f 100644 --- a/library/src/main/java/org/mustangproject/LegalOrganisation.java +++ b/library/src/main/java/org/mustangproject/LegalOrganisation.java @@ -23,6 +23,10 @@ public class LegalOrganisation implements IZUGFeRDLegalOrganisation { this.schemedID = new SchemedID(scheme, ID); } + public LegalOrganisation(String ID) { + this.schemedID = new SchemedID(null, ID); + } + public LegalOrganisation(SchemedID schemedID, String tradingBusinessName) { this.schemedID = schemedID; this.tradingBusinessName=tradingBusinessName; diff --git a/library/src/main/java/org/mustangproject/Product.java b/library/src/main/java/org/mustangproject/Product.java index 4bbde404..4f0397ee 100644 --- a/library/src/main/java/org/mustangproject/Product.java +++ b/library/src/main/java/org/mustangproject/Product.java @@ -69,7 +69,7 @@ public class Product implements IZUGFeRDExportableProduct { nodeMap.getAsString("Description").ifPresent(this::setDescription); - nodeMap.getAsNodeMap("ApplicableProductCharacteristic").ifPresent(apcNodes -> { + nodeMap.getAllNodes("ApplicableProductCharacteristic").map(NodeMap::new).forEach(apcNodes -> { // ApplicableProductCharacteristic is 0 .. unbounded String key = apcNodes.getAsStringOrNull("Description"); String value = apcNodes.getAsStringOrNull("Value"); if (key != null && value != null) { @@ -80,7 +80,6 @@ public class Product implements IZUGFeRDExportableProduct { } }); - //UBL nodeMap.getAsNodeMap("AdditionalItemProperty").ifPresent(aipNodes -> { String name = aipNodes.getAsStringOrNull("Name"); diff --git a/library/src/main/java/org/mustangproject/XMLTools.java b/library/src/main/java/org/mustangproject/XMLTools.java index d890c3d1..5905fdae 100644 --- a/library/src/main/java/org/mustangproject/XMLTools.java +++ b/library/src/main/java/org/mustangproject/XMLTools.java @@ -129,7 +129,16 @@ public class XMLTools extends XMLWriter { * @return a util.Date, or null, if not parseable */ public static Date tryDate(String toParse) { - final SimpleDateFormat formatter = ZUGFeRDDateFormat.DATE.getFormatter(); + SimpleDateFormat formatter = null; + if (toParse==null) { + return null; + } + if (toParse.contains("-")) { + // from ubl + formatter = new SimpleDateFormat("yyyy-MM-dd"); + } else { + formatter = ZUGFeRDDateFormat.DATE.getFormatter(); + } try { return formatter.parse(toParse); } catch (final Exception e) { @@ -211,7 +220,8 @@ public class XMLTools extends XMLWriter { } public static byte[] getBytesFromStream(InputStream fileinput) throws IOException { - return IOUtils.toByteArray (fileinput); + // Stream closing responsibility is with the caller + return IOUtils.toByteArray(fileinput); } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java index edc7f538..9ce55ef1 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java @@ -313,6 +313,10 @@ public interface IExportableTransaction { return null; } + default String getPaymentReference() { + return null; + } + /** * Get payment terms for the EXTENDED profile (multiple terms are allowed) * @return diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java index 45c210ea..6d68cdff 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java @@ -172,4 +172,8 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{ default List getNotesWithSubjectCode() { return null; } + + default String getAccountingReference() { + return null; + } } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java index 2f1c7903..f6329d52 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java @@ -32,6 +32,7 @@ import java.util.Map; import org.mustangproject.EStandard; import org.mustangproject.FileAttachment; +import org.mustangproject.ReferencedDocument; import org.mustangproject.XMLTools; public class OXPullProvider extends ZUGFeRD2PullProvider { @@ -460,7 +461,7 @@ public class OXPullProvider extends ZUGFeRD2PullProvider { xml += ""; } if (trans.getInvoiceReferencedDocuments() != null) { - for (var doc : trans.getInvoiceReferencedDocuments()) { + for (ReferencedDocument doc : trans.getInvoiceReferencedDocuments()) { xml += "" + "" + XMLTools.encodeXML(doc.getIssuerAssignedID()) + ""; diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/PDFBoxUpdateMitigation.java b/library/src/main/java/org/mustangproject/ZUGFeRD/PDFBoxUpdateMitigation.java index 652051b6..252cd7ba 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/PDFBoxUpdateMitigation.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/PDFBoxUpdateMitigation.java @@ -13,14 +13,14 @@ import org.apache.pdfbox.preflight.parser.PreflightParser; import jakarta.activation.DataSource; -// Copied from PDFBox preflight 2.0.x +// Copied from PDFBox preflight 2.0.x final class ByteArrayDataSource implements DataSource { private ByteArrayOutputStream data; private String type = null; private String name = null; - public ByteArrayDataSource (InputStream is) throws IOException + public ByteArrayDataSource (final InputStream is) throws IOException { data = new ByteArrayOutputStream (); IOUtils.copy (is, data); @@ -36,7 +36,7 @@ final class ByteArrayDataSource implements DataSource * @param type * the type to set */ - public void setType (String type) + public void setType (final String type) { this.type = type; } @@ -45,7 +45,7 @@ final class ByteArrayDataSource implements DataSource * @param name * the name to set */ - public void setName (String name) + public void setName (final String name) { this.name = name; } @@ -70,12 +70,13 @@ final class ByteArrayDataSource implements DataSource // Try to create an API similar to the 2.x one final class PreflightParserHelper { - private static File createTmpFile (InputStream input) throws IOException + private static File createTmpFile (final InputStream input) throws IOException { FileOutputStream fos = null; try { - File tmpFile = File.createTempFile ("mustang-pdf", ".pdf"); + final File tmpFile = File.createTempFile ("mustang-pdf", ".pdf"); + tmpFile.deleteOnExit (); fos = new FileOutputStream (tmpFile); IOUtils.copy (input, fos); return tmpFile; @@ -87,7 +88,7 @@ final class PreflightParserHelper } } - public static PreflightParser createPreflightParser (DataSource dataSource) throws IOException + public static PreflightParser createPreflightParser (final DataSource dataSource) throws IOException { return new PreflightParser (createTmpFile (dataSource.getInputStream ())); } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ValidationLogVisualizer.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ValidationLogVisualizer.java index 686daa7c..7e7d8a17 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ValidationLogVisualizer.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ValidationLogVisualizer.java @@ -1,5 +1,13 @@ package org.mustangproject.ZUGFeRD; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.StringReader; import org.apache.fop.apps.*; import org.apache.fop.apps.io.ResourceResolverFactory; import org.apache.fop.configuration.Configuration; @@ -10,11 +18,12 @@ import org.mustangproject.ClasspathResolverURIAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.xml.XMLConstants; import javax.xml.transform.*; import javax.xml.transform.sax.SAXResult; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; -import java.io.*; + import java.nio.charset.StandardCharsets; public class ValidationLogVisualizer { @@ -70,7 +79,7 @@ public class ValidationLogVisualizer { return baos.toString(StandardCharsets.UTF_8); } - public void toPDF(String xmlLogfileContent, String pdfFilename) { + public byte[] createPDFBytes(String xmlLogfileContent) { // the writing part @@ -111,13 +120,19 @@ public class ValidationLogVisualizer { // Step 2: Set up output stream. // Note: Using BufferedOutputStream for performance reasons (helpful with FileOutputStreams). - try (OutputStream out = new BufferedOutputStream(new FileOutputStream(pdfFilename))) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (OutputStream out = new BufferedOutputStream(baos)) { // Step 3: Construct fop with desired output format Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, userAgent, out); // Step 4: Setup JAXP using identity transformer TransformerFactory factory = TransformerFactory.newInstance(); + + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + factory.setFeature("http://xml.org/sax/features/external-general-entities", false); + factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); Transformer transformer = factory.newTransformer(); // identity transformer // Step 5: Setup input and output for XSLT transformation @@ -133,6 +148,20 @@ public class ValidationLogVisualizer { } catch (FOPException | IOException | TransformerException e) { LOGGER.error("Failed to create PDF", e); } + return baos.toByteArray(); + } + + public byte[] toPDF(String xmlLogfileContent) { + return createPDFBytes(xmlLogfileContent); + } + + public void toPDF(String xmlLogfileContent, String pdfFilename) { + byte[] pdfData = createPDFBytes(xmlLogfileContent); + try (FileOutputStream fos = new FileOutputStream(pdfFilename)) { + fos.write(pdfData); + } catch (IOException e) { + LOGGER.error("Failed to write PDF to file", e); + } } private static class ClasspathResourceURIResolver implements URIResolver { diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index 166802f6..c0674e61 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -45,6 +45,7 @@ import org.dom4j.io.OutputFormat; import org.dom4j.io.XMLWriter; import org.mustangproject.FileAttachment; import org.mustangproject.IncludedNote; +import org.mustangproject.ReferencedDocument; import org.mustangproject.XMLTools; import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants; import org.slf4j.Logger; @@ -139,7 +140,9 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { xml += "" + XMLTools.encodeXML(party.getGlobalID()) + ""; } - xml += "" + XMLTools.encodeXML(party.getName()) + ""; + if (party.getName() != null && !party.getName().isEmpty()) { + xml += "" + XMLTools.encodeXML(party.getName()) + ""; + } if (party.getDescription() != null) { xml += "" + XMLTools.encodeXML(party.getDescription()) + ""; } @@ -149,7 +152,12 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { if (profile == Profiles.getByName("Minimum")) { xml += "" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getID()) + ""; } else { - xml += "" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getID()) + ""; + String schemeAttribute=""; + if ((party.getLegalOrganisation().getSchemedID().getScheme()!=null)&&(party.getLegalOrganisation().getSchemedID().getScheme().length()>0)) { + schemeAttribute="schemeID=\"" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getScheme())+"\""; + + } + xml += "" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getID()) + ""; } } if (party.getLegalOrganisation().getTradingBusinessName() != null) { @@ -277,7 +285,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { } String reason = ""; - if ((allowance.getReason() != null) && (profile == Profiles.getByName("Extended") || profile == Profiles.getByName("XRechnung")) || profile == Profiles.getByName("EN16931")) { + if ((allowance.getReason() != null) && (profile == Profiles.getByName("Extended") || profile == Profiles.getByName("XRechnung") || profile == Profiles.getByName("EN16931"))) { reason = "" + XMLTools.encodeXML(allowance.getReason()) + ""; } String reasonCode = ""; @@ -337,8 +345,6 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { boolean hasDueDate = trans.getDueDate() != null; final SimpleDateFormat germanDateFormat = new SimpleDateFormat("dd.MM.yyyy"); - String exemptionReason = ""; - if (trans.getPaymentTermDescription() != null) { paymentTermsDescription = XMLTools.encodeXML(trans.getPaymentTermDescription()); } @@ -388,14 +394,13 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { + "" + "" + "" - + "" + XMLTools.encodeXML(trans.getNumber()) + "" - // + "RECHNUNG" - // + "380" - + "" + typecode + "" - + "" - + DATE.udtFormat(trans.getIssueDate()) + "" // date + + "" + XMLTools.encodeXML(trans.getNumber()) + ""; + if (profile == Profiles.getByName("Extended") && trans.getDocumentName() != null) { + xml += "" + XMLTools.encodeXML(trans.getDocumentName()) + ""; + } + xml += "" + typecode + "" + + "" + DATE.udtFormat(trans.getIssueDate()) + "" // date + buildNotes(trans) - + "" + ""; int lineID = 0; @@ -405,9 +410,6 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { if (currentItem.getId()!=null) { lineIDStr=currentItem.getId(); } - if (currentItem.getProduct().getTaxExemptionReason() != null) { - exemptionReason = "" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + ""; - } final LineCalculator lc = new LineCalculator(currentItem); if ((getProfile() != Profiles.getByName("Minimum")) && (getProfile() != Profiles.getByName("BasicWL"))) { xml += "" + @@ -450,7 +452,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { } xml += "" + XMLTools.encodeXML(currentItem.getProduct().getName()) + ""; - if (currentItem.getProduct().getDescription().length() > 0) { + if (currentItem.getProduct().getDescription() != null && currentItem.getProduct().getDescription().length() > 0) { xml += "" + XMLTools.encodeXML(currentItem.getProduct().getDescription()) + ""; @@ -458,7 +460,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { if (currentItem.getProduct().getClassifications() != null && currentItem.getProduct().getClassifications().length > 0) { for (IDesignatedProductClassification classification : currentItem.getProduct().getClassifications()) { xml += "" - + "" + "" + "" - + "VAT" - + exemptionReason - + "" + currentItem.getProduct().getTaxCategoryCode() + "" + + "VAT"; + + if (currentItem.getProduct().getTaxExemptionReason() != null) { + xml += "" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + ""; + } + + xml += "" + currentItem.getProduct().getTaxCategoryCode() + "" + "" + vatFormat(currentItem.getProduct().getVATPercent()) + "" + ""; @@ -662,8 +668,8 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { if ((trans.getCreditorReferenceID() != null) && (getProfile() != Profiles.getByName("Minimum"))) { xml += "" + XMLTools.encodeXML(trans.getCreditorReferenceID()) + ""; } - if ((trans.getNumber() != null) && (getProfile() != Profiles.getByName("Minimum"))) { - xml += "" + XMLTools.encodeXML(trans.getNumber()) + ""; + if ((trans.getPaymentReference() != null) && (getProfile() != Profiles.getByName("Minimum"))) { + xml += "" + XMLTools.encodeXML(trans.getPaymentReference()) + ""; } xml += "" + trans.getCurrency() + ""; if (this.trans.getPayee() != null) { @@ -894,7 +900,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { xml += ""; } if (trans.getInvoiceReferencedDocuments() != null) { - for (var doc : trans.getInvoiceReferencedDocuments()) { + for (ReferencedDocument doc : trans.getInvoiceReferencedDocuments()) { xml += "" + "" + XMLTools.encodeXML(doc.getIssuerAssignedID()) + ""; diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java index dea69a6f..87137411 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java @@ -58,6 +58,7 @@ import org.apache.pdfbox.pdmodel.font.PDCIDFontType2; import org.apache.pdfbox.pdmodel.font.PDFont; import org.apache.pdfbox.pdmodel.font.PDFontDescriptor; import org.apache.pdfbox.pdmodel.font.PDType0Font; +import org.apache.pdfbox.pdmodel.graphics.PDXObject; import org.apache.pdfbox.pdmodel.graphics.color.PDOutputIntent; import org.apache.xmpbox.XMPMetadata; import org.apache.xmpbox.schema.AdobePDFSchema; @@ -559,6 +560,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte // https://github.com/ZUGFeRD/mustangproject/issues/249 COSName cidSet = COSName.getPDFName("CIDSet"); + COSName resources = COSName.getPDFName("Resources"); // iterate over all pdf pages @@ -567,29 +569,45 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte PDPage page = (PDPage) object; PDResources res = page.getResources(); - for (COSName fontName : res.getFontNames()) { - try { - PDFont pdFont = res.getFont(fontName); - if (pdFont instanceof PDType0Font) { - PDType0Font typedFont = (PDType0Font) pdFont; - if (typedFont.getDescendantFont() instanceof PDCIDFontType2) { - @SuppressWarnings("unused") - PDCIDFontType2 f = (PDCIDFontType2) typedFont.getDescendantFont(); - PDFontDescriptor fontDescriptor = pdFont.getFontDescriptor(); - - fontDescriptor.getCOSObject().removeItem(cidSet); - } - } - } catch (IOException e) { - throw e; + // Check for fonts in PDXObjects: + for (COSName xObjectName : res.getXObjectNames()) { + PDXObject xObject = res.getXObject(xObjectName); + COSDictionary d = xObject.getCOSObject().getCOSDictionary(resources); + if (d != null) { + PDResources xr = new PDResources(d); + removeCIDSetFromPDResources(cidSet, xr); } - // do stuff with the font } + + // Check for fonts in document-resources: + removeCIDSetFromPDResources(cidSet, res); } } } + private void removeCIDSetFromPDResources(COSName cidSet, PDResources res) throws IOException { + for (COSName fontName : res.getFontNames()) { + try { + PDFont pdFont = res.getFont(fontName); + if (pdFont instanceof PDType0Font) { + PDType0Font typedFont = (PDType0Font) pdFont; + + if (typedFont.getDescendantFont() instanceof PDCIDFontType2) { + @SuppressWarnings("unused") + PDCIDFontType2 f = (PDCIDFontType2) typedFont.getDescendantFont(); + PDFontDescriptor fontDescriptor = pdFont.getFontDescriptor(); + + fontDescriptor.getCOSObject().removeItem(cidSet); + } + } + } catch (IOException e) { + throw e; + } + // do stuff with the font + } + } + protected void prepareDocument() throws IOException { PDDocumentCatalog cat = doc.getDocumentCatalog(); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromPDFA.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromPDFA.java index c3b9299b..e128f0a6 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromPDFA.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromPDFA.java @@ -90,9 +90,10 @@ public class ZUGFeRDExporterFromPDFA implements IZUGFeRDExporter { protected byte[] inputstreamToByteArray(InputStream fileInputStream) throws IOException { byte[] bytes = new byte[fileInputStream.available()]; - DataInputStream dataInputStream = new DataInputStream(fileInputStream); - dataInputStream.readFully(bytes); - return bytes; + try (DataInputStream dataInputStream = new DataInputStream(fileInputStream)) { + dataInputStream.readFully(bytes); + return bytes; + } } /*** diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java index 76abd748..d138bf15 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java @@ -86,6 +86,7 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { case "urn:factur-x.eu:1p0:minimum": return "MINIMUM"; case "urn:ferd:CrossIndustryDocument:invoice:1p0:extended": + case "urn:cen.eu:en16931:2017#conformant#urn:zugferd.de:2p0:extended": case "urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended": return "EXTENDED"; default: diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index 9115feb4..3015f91c 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -1,5 +1,6 @@ package org.mustangproject.ZUGFeRD; +import javax.xml.XMLConstants; import org.apache.commons.io.IOUtils; import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; @@ -137,9 +138,9 @@ public class ZUGFeRDInvoiceImporter { return; } - final InputStream XMP = doc.getDocumentCatalog().getMetadata().exportXMPMetadata(); - - xmpString = new String(XMLTools.getBytesFromStream(XMP), StandardCharsets.UTF_8); + try (final InputStream XMP = doc.getDocumentCatalog().getMetadata().exportXMPMetadata()) { + xmpString = new String(XMLTools.getBytesFromStream(XMP), StandardCharsets.UTF_8); + } final PDEmbeddedFilesNameTreeNode etn = names.getEmbeddedFiles(); if (etn == null) { @@ -258,9 +259,25 @@ public class ZUGFeRDInvoiceImporter { } private void setDocument() throws ParserConfigurationException, IOException, SAXException, ParseException { - final DocumentBuilderFactory xmlFact = DocumentBuilderFactory.newInstance(); - xmlFact.setNamespaceAware(true); - final DocumentBuilder builder = xmlFact.newDocumentBuilder(); + final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + //REDHAT + //https://www.blackhat.com/docs/us-15/materials/us-15-Wang-FileCry-The-New-Age-Of-XXE-java-wp.pdf + dbf.setAttribute(XMLConstants.FEATURE_SECURE_PROCESSING, true); + dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + + //OWASP + //https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); + dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + // Disable external DTDs as well + dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks" + dbf.setXIncludeAware(false); + dbf.setExpandEntityReferences(false); + dbf.setNamespaceAware(true); + final DocumentBuilder builder = dbf.newDocumentBuilder(); final ByteArrayInputStream is = new ByteArrayInputStream(rawXML); /// is.skip(guessBOMSize(is)); document = builder.parse(is); @@ -288,6 +305,7 @@ public class ZUGFeRDInvoiceImporter { public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseException { String number = ""; + String documentName = null; String typeCode = null; String deliveryPeriodStart = null; String deliveryPeriodEnd = null; @@ -496,6 +514,9 @@ public class ZUGFeRDInvoiceImporter { if ((item.getLocalName() != null) && (item.getLocalName().equals("ID"))) { number = XMLTools.trimOrNull(item); } + if ((item.getLocalName() != null) && (item.getLocalName().equals("Name"))) { + documentName = XMLTools.trimOrNull(item); + } if ((item.getLocalName() != null) && (item.getLocalName().equals("TypeCode"))) { typeCode = XMLTools.trimOrNull(item); } @@ -556,13 +577,13 @@ public class ZUGFeRDInvoiceImporter { } zpp.addNotes(includedNotes); String rootNode = extractString("local-name(/*)"); - if (rootNode.equals("Invoice")||rootNode.equals("CreditNote")) { + if (rootNode.equals("Invoice") || rootNode.equals("CreditNote")) { // UBL... // //*[local-name()="Invoice" or local-name()="CreditNote"] number = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"ID\"]").trim(); typeCode = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"InvoiceTypeCode\"]").trim(); String issueDateStr = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"IssueDate\"]").trim(); - if (issueDateStr.length()>0) { + if (issueDateStr.length() > 0) { issueDate = new SimpleDateFormat("yyyy-MM-dd").parse(issueDateStr); } String dueDt = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"DueDate\"]").trim(); @@ -651,7 +672,7 @@ public class ZUGFeRDInvoiceImporter { zpp.setCurrency(currency); String paymentTermsDescription = extractString("//*[local-name()=\"SpecifiedTradePaymentTerms\"]/*[local-name()=\"Description\"]|//*[local-name()=\"PaymentTerms\"]/*[local-name()=\"Note\"]"); - if ((paymentTermsDescription!=null)&&(!paymentTermsDescription.isEmpty())) { + if ((paymentTermsDescription != null) && (!paymentTermsDescription.isEmpty())) { zpp.setPaymentTermDescription(paymentTermsDescription); } @@ -661,6 +682,7 @@ public class ZUGFeRDInvoiceImporter { List bankDetails = new ArrayList<>(); String directDebitMandateID = null; String IBAN = null, BIC = null, paymentMeansCode = null, paymentMeansInformation = null; + String accountName = null; for (int i = 0; i < headerTradeSettlementNodes.getLength(); i++) { // XMLTools.trimOrNull(nodes.item(i)))) { @@ -668,6 +690,12 @@ public class ZUGFeRDInvoiceImporter { NodeList headerTradeSettlementChilds = headerTradeSettlementNode.getChildNodes(); for (int settlementChildIndex = 0; settlementChildIndex < headerTradeSettlementChilds.getLength(); settlementChildIndex++) { + if ((headerTradeSettlementChilds.item(settlementChildIndex).getLocalName() != null) + && (headerTradeSettlementChilds.item(settlementChildIndex).getLocalName().equals("PaymentReference"))) { + String paymentReference = headerTradeSettlementChilds.item(settlementChildIndex).getTextContent(); + zpp.setPaymentReference(paymentReference); + } + if ((headerTradeSettlementChilds.item(settlementChildIndex).getLocalName() != null) && (headerTradeSettlementChilds.item(settlementChildIndex).getLocalName().equals("SpecifiedTradePaymentTerms"))) { NodeList paymentTermChilds = headerTradeSettlementChilds.item(settlementChildIndex).getChildNodes(); @@ -712,6 +740,9 @@ public class ZUGFeRDInvoiceImporter { if ((accountChilds.item(accountChildIndex).getLocalName() != null) && (accountChilds.item(accountChildIndex).getLocalName().equals("IBANID"))) {//CII IBAN = XMLTools.trimOrNull(accountChilds.item(accountChildIndex)); } + if ((accountChilds.item(accountChildIndex).getLocalName() != null) && (accountChilds.item(accountChildIndex).getLocalName().equals("AccountName"))) {//CII + accountName = XMLTools.trimOrNull(accountChilds.item(accountChildIndex)); + } } } if ((paymentMeansChilds.item(paymentMeansChildIndex).getLocalName() != null) && (paymentMeansChilds.item(paymentMeansChildIndex).getLocalName().equals("PayeeSpecifiedCreditorFinancialInstitution"))) { @@ -729,6 +760,9 @@ public class ZUGFeRDInvoiceImporter { if (BIC != null) { bd.setBIC(BIC); } + if (accountName!=null) { + bd.setAccountName(accountName); + } bankDetails.add(bd); } } @@ -758,6 +792,21 @@ public class ZUGFeRDInvoiceImporter { } } + xpr = xpath.compile("/*[local-name()=\"Invoice\"]/*[local-name()=\"InvoicePeriod\"]/*"); //UBL only + NodeList periodNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); + + for (int periodChildIndex = 0; periodChildIndex < periodNodes.getLength(); periodChildIndex++) { + String localName=periodNodes.item(periodChildIndex).getLocalName(); + if ((localName != null) && (periodNodes.item(periodChildIndex).getLocalName().equals("StartDate"))) { + deliveryPeriodStart = XMLTools.trimOrNull(periodNodes.item(periodChildIndex)); + } + if ((localName != null) && (periodNodes.item(periodChildIndex).getLocalName().equals("EndDate"))) { + deliveryPeriodEnd = XMLTools.trimOrNull(periodNodes.item(periodChildIndex)); + } + + } + + if ((deliveryPeriodStart != null) && (deliveryPeriodEnd != null)) { zpp.setDetailedDeliveryPeriod(XMLTools.tryDate(deliveryPeriodStart), XMLTools.tryDate(deliveryPeriodEnd)); } else if (deliveryPeriodStart != null) { @@ -776,12 +825,12 @@ public class ZUGFeRDInvoiceImporter { && (paymentMeansChilds.item(meansChildIndex).getLocalName().equals("PayeeFinancialAccount"))) { NodeList paymentTermChilds = paymentMeansChilds.item(meansChildIndex).getChildNodes(); for (int paymentTermChildIndex = 0; paymentTermChildIndex < paymentTermChilds.getLength(); paymentTermChildIndex++) { + + if ((paymentTermChilds.item(paymentTermChildIndex).getLocalName() != null) && (paymentTermChilds.item(paymentTermChildIndex).getLocalName().equals("Name"))) { + accountName = XMLTools.trimOrNull(paymentTermChilds.item(paymentTermChildIndex)); + } if ((paymentTermChilds.item(paymentTermChildIndex).getLocalName() != null) && (paymentTermChilds.item(paymentTermChildIndex).getLocalName().equals("ID"))) { IBAN = XMLTools.trimOrNull(paymentTermChilds.item(paymentTermChildIndex)); - if (IBAN != null) { - BankDetails bd = new BankDetails(IBAN); - bankDetails.add(bd); - } } } } @@ -799,10 +848,18 @@ public class ZUGFeRDInvoiceImporter { } } + if (IBAN != null) { + BankDetails bd = new BankDetails(IBAN); + if (accountName!=null) { + bd.setAccountName(accountName); + } + bankDetails.add(bd); + } + } - zpp.setIssueDate(issueDate).setDueDate(dueDate).setDeliveryDate(deliveryDate).setSender(new TradeParty(SellerNodes)).setRecipient(new TradeParty(BuyerNodes)).setNumber(number).setDocumentCode(typeCode); + zpp.setIssueDate(issueDate).setDueDate(dueDate).setDeliveryDate(deliveryDate).setSender(new TradeParty(SellerNodes)).setRecipient(new TradeParty(BuyerNodes)).setNumber(number).setDocumentName(documentName).setDocumentCode(typeCode); if ((directDebitMandateID != null) && (IBAN != null)) { DirectDebit d = new DirectDebit(IBAN, directDebitMandateID); @@ -848,15 +905,14 @@ public class ZUGFeRDInvoiceImporter { Node currentItemNode = nodes.item(i); ReferencedDocument doc = ReferencedDocument.fromNode(currentItemNode); - if (doc != null - && (!Objects.equals(zpp.getInvoiceReferencedDocumentID(), doc.getIssuerAssignedID()) - || !Objects.equals(zpp.getInvoiceReferencedIssueDate(), doc.getFormattedIssueDateTime()))) - { + if (doc != null + && (!Objects.equals(zpp.getInvoiceReferencedDocumentID(), doc.getIssuerAssignedID()) + || !Objects.equals(zpp.getInvoiceReferencedIssueDate(), doc.getFormattedIssueDateTime()))) { zpp.addInvoiceReferencedDocument(doc); } } } - + zpp.setOwnOrganisationName(extractString("//*[local-name()=\"SellerTradeParty\"]/*[local-name()=\"Name\"]|//*[local-name()=\"AccountingSupplierParty\"]/*[local-name()=\"Party\"]/*[local-name()=\"PartyName\"]").trim()); String rounding = extractString("//*[local-name()=\"SpecifiedTradeSettlementHeaderMonetarySummation\"]/*[local-name()=\"RoundingAmount\"]|//*[local-name()=\"LegalMonetaryTotal\"]/*[local-name()=\"Party\"]/*[local-name()=\"PayableRoundingAmount\"]"); @@ -890,7 +946,7 @@ public class ZUGFeRDInvoiceImporter { xpr = xpath.compile("//*[local-name()=\"AttachmentBinaryObject\"]|//*[local-name()=\"EmbeddedDocumentBinaryObject\"]"); NodeList attachmentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); for (int i = 0; i < attachmentNodes.getLength(); i++) { - FileAttachment fa = new FileAttachment(attachmentNodes.item(i).getAttributes().getNamedItem("filename").getNodeValue(), attachmentNodes.item(i).getAttributes().getNamedItem("mimeCode").getNodeValue(), "Data", Base64.getDecoder().decode(XMLTools.trimOrNull(attachmentNodes.item(i)))); + FileAttachment fa = new FileAttachment(attachmentNodes.item(i).getAttributes().getNamedItem("filename").getNodeValue(), attachmentNodes.item(i).getAttributes().getNamedItem("mimeCode").getNodeValue(), "Data", Base64.getMimeDecoder().decode(XMLTools.trimOrNull(attachmentNodes.item(i)))); zpp.embedFileInXML(fa); // filename = "Aufmass.png" mimeCode = "image/png" //EmbeddedDocumentBinaryObject cbc:EmbeddedDocumentBinaryObject mimeCode="image/png" filename="Aufmass.png" @@ -976,6 +1032,38 @@ public class ZUGFeRDInvoiceImporter { } } + xpr = xpath.compile("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]/*[local-name()=\"SpecifiedLogisticsServiceCharge\"]");// UBL unknown + chargeNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); + for (int i = 0; i < chargeNodes.getLength(); i++) { + NodeList chargeNodeChilds = chargeNodes.item(i).getChildNodes(); + String chargeAmount = null; + String taxPercent = null; + for (int chargeChildIndex = 0; chargeChildIndex < chargeNodeChilds.getLength(); chargeChildIndex++) { + String chargeChildName = chargeNodeChilds.item(chargeChildIndex).getLocalName(); + if (chargeChildName != null) { + if (chargeChildName.equals("AppliedAmount")) { + chargeAmount = XMLTools.trimOrNull(chargeNodeChilds.item(chargeChildIndex)); + } else if (chargeChildName.equals("AppliedTradeTax")) { + NodeList taxChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes(); + for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) { + String taxItemName = taxChilds.item(taxChildIndex).getLocalName(); + if ((taxItemName != null) && (taxItemName.equals("RateApplicablePercent"))) { + taxPercent = XMLTools.trimOrNull(taxChilds.item(taxChildIndex)); + } + } + } + } + //appliedAmount + //AppliedTradeTax + } + if (chargeAmount != null) { + Charge c = new Charge(new BigDecimal(chargeAmount)); + if (taxPercent != null) { + c.setTaxPercent(new BigDecimal(taxPercent)); + } + zpp.addCharge(c); + } + } TransactionCalculator tc = new TransactionCalculator(zpp); @@ -1040,7 +1128,7 @@ public class ZUGFeRDInvoiceImporter { } else if (rootNode.equals("Invoice")) { return EStandard.ubl; } else if (rootNode.equals("CreditNote")) { - return EStandard.ubl; + return EStandard.ubl_creditnote; } else if (rootNode.equals("CrossIndustryInvoice")) { return EStandard.facturx; } else if (rootNode.equals("SCRDMCCBDACIDAMessageStructure")) { @@ -1085,11 +1173,17 @@ public class ZUGFeRDInvoiceImporter { * * @return the file attachments embedded in XML (using base64) decoded as byte array, * for PDF embedded files in FX use getFileAttachmentsPDF() + * may return empty array * @deprecated use invoice.getAdditionalReferencedDocuments */ @Deprecated public List getFileAttachmentsXML() { - return new ArrayList<>(Arrays.asList(importedInvoice.getAdditionalReferencedDocuments())); + if (importedInvoice.getAdditionalReferencedDocuments()!=null) { + return new ArrayList<>(Arrays.asList(importedInvoice.getAdditionalReferencedDocuments())); + } else { + return new ArrayList<>(); + } + } /*** diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDVisualizer.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDVisualizer.java index 9b5c1e73..d4f280a9 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDVisualizer.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDVisualizer.java @@ -21,6 +21,8 @@ package org.mustangproject.ZUGFeRD; import com.helger.commons.io.stream.StreamHelper; +import javax.xml.XMLConstants; +import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.io.IOUtils; import org.apache.fop.apps.*; import org.apache.fop.apps.io.ResourceResolverFactory; @@ -45,6 +47,9 @@ import javax.xml.transform.stream.StreamSource; import java.io.*; import java.nio.charset.StandardCharsets; import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Supplier; public class ZUGFeRDVisualizer { @@ -87,7 +92,8 @@ public class ZUGFeRDVisualizer { * @param fis inputstream (will be consumed) * @return (facturx = cii) */ - private EStandard findOutStandardFromRootNode(InputStream fis) { + private EStandard findOutStandardFromRootNode(InputStream fis) + throws ParserConfigurationException { String zf1Signature = "CrossIndustryDocument"; String zf2Signature = "CrossIndustryInvoice"; @@ -96,6 +102,22 @@ public class ZUGFeRDVisualizer { String cioSignature = "SCRDMCCBDACIOMessageStructure"; DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + //REDHAT + //https://www.blackhat.com/docs/us-15/materials/us-15-Wang-FileCry-The-New-Age-Of-XXE-java-wp.pdf + dbf.setAttribute(XMLConstants.FEATURE_SECURE_PROCESSING, true); + dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + + //OWASP + //https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); + dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + // Disable external DTDs as well + dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks" + dbf.setXIncludeAware(false); + dbf.setExpandEntityReferences(false); dbf.setNamespaceAware(true); try { DocumentBuilder db = dbf.newDocumentBuilder(); @@ -118,12 +140,15 @@ public class ZUGFeRDVisualizer { return null; } - public String visualize(String xmlFilename, Language lang) throws IOException, TransformerException { - FileInputStream fis = new FileInputStream(xmlFilename); - return visualize(fis, lang); + public String visualize(String xmlFilename, Language lang) + throws IOException, TransformerException, ParserConfigurationException { + try (FileInputStream fis = new FileInputStream(xmlFilename)) { + return visualize(fis, lang); + } } - public String visualize(InputStream inputXml, Language lang) throws IOException, TransformerException { + public String visualize(InputStream inputXml, Language lang) + throws IOException, TransformerException, ParserConfigurationException { initTemplates(lang); String fileContent = new String(IOUtils.toByteArray(inputXml), StandardCharsets.UTF_8); @@ -208,13 +233,15 @@ public class ZUGFeRDVisualizer { } protected String toFOP(String xmlFilename) - throws IOException, TransformerException { - - FileInputStream fis = new FileInputStream(xmlFilename); - EStandard theStandard = findOutStandardFromRootNode(fis); - fis = new FileInputStream(xmlFilename);//rewind :-( - - return toFOP(fis, theStandard); + throws IOException, TransformerException, ParserConfigurationException { + EStandard theStandard; + try (FileInputStream fis = new FileInputStream(xmlFilename)) { + theStandard = findOutStandardFromRootNode(fis); + } + + try (FileInputStream fis = new FileInputStream(xmlFilename)) { + return toFOP(fis, theStandard); + } } protected String toFOP(InputStream is, EStandard theStandard) @@ -254,16 +281,61 @@ public class ZUGFeRDVisualizer { // the writing part File XMLinputFile = new File(xmlFilename); - String result = null; + String fopInput = null; /* remove file endings so that tests can also pass after checking out from git with arbitrary options (which may include CSRF changes) */ try { - result = this.toFOP(XMLinputFile.getAbsolutePath()); - } catch (TransformerException | IOException e) { + fopInput = this.toFOP(XMLinputFile.getAbsolutePath()); + } catch (TransformerException | IOException | ParserConfigurationException e) { LOGGER.error("Failed to apply FOP", e); } + + toPDFfromFOP(fopInput, () -> { + try { + return new FileOutputStream(pdfFilename); + } catch (FileNotFoundException e) { + LOGGER.error("Failed to create PDF", e); + } + return null; + }, (OutputStream out) -> {}); + } + + public byte[] toPDF(String xmlContent) { + + String fopInput = null; + + /* remove file endings so that tests can also pass after checking + out from git with arbitrary options (which may include CSRF changes) + */ + try { + ByteArrayInputStream fis = new ByteArrayInputStream(xmlContent.getBytes(StandardCharsets.UTF_8)); + EStandard theStandard = findOutStandardFromRootNode(fis); + fis = new ByteArrayInputStream(xmlContent.getBytes(StandardCharsets.UTF_8));//rewind :-( + + fopInput = toFOP(fis, theStandard); + } catch (TransformerException | IOException | ParserConfigurationException e) { + LOGGER.error("Failed to apply FOP", e); + } + + AtomicReference byteHolder = new AtomicReference<>(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + toPDFfromFOP(fopInput, () -> new BufferedOutputStream(os), (OutputStream out) -> { + + try { + out.flush(); + } catch (IOException e) { + LOGGER.error("Failed to create PDF", e); + } + byteHolder.set(os.toByteArray()); + }); + + return byteHolder.get(); + } + + private void toPDFfromFOP(String fopInput, Supplier outputStreamDelegate, Consumer consumerDelegate) { + DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder(); Configuration cfg = null; @@ -291,24 +363,27 @@ public class ZUGFeRDVisualizer { // Step 2: Set up output stream. // Note: Using BufferedOutputStream for performance reasons (helpful with FileOutputStreams). - try (OutputStream out = new BufferedOutputStream(new FileOutputStream(pdfFilename))) { + try (OutputStream out = new BufferedOutputStream(outputStreamDelegate.get())) { // Step 3: Construct fop with desired output format Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, userAgent, out); // Step 4: Setup JAXP using identity transformer TransformerFactory factory = TransformerFactory.newInstance(); + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); Transformer transformer = factory.newTransformer(); // identity transformer // Step 5: Setup input and output for XSLT transformation // Setup input stream - Source src = new StreamSource(new ByteArrayInputStream(result.getBytes(StandardCharsets.UTF_8))); + Source src = new StreamSource(new ByteArrayInputStream(fopInput.getBytes(StandardCharsets.UTF_8))); // Resulting SAX events (the generated FO) must be piped through to FOP Result res = new SAXResult(fop.getDefaultHandler()); // Step 6: Start XSLT transformation and FOP processing transformer.transform(src, res); + + consumerDelegate.accept(out); } catch (FOPException | IOException | TransformerException e) { LOGGER.error("Failed to create PDF", e); diff --git a/library/src/main/resources/stylesheets/cii-xr.xsl b/library/src/main/resources/stylesheets/cii-xr.xsl index f741c903..cf03f74e 100644 --- a/library/src/main/resources/stylesheets/cii-xr.xsl +++ b/library/src/main/resources/stylesheets/cii-xr.xsl @@ -130,7 +130,7 @@ + select="./rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeDelivery"/> @@ -1146,18 +1146,18 @@ + match="/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeDelivery"> - + - + select="ram:ShipToTradeParty/ram:ID[empty(following-sibling::ram:GlobalID/@schemeID)]"/> + + select="ram:ActualDeliverySupplyChainEvent/ram:OccurrenceDateTime/udt:DateTimeString[@format='102']"/> - + diff --git a/library/src/main/resources/stylesheets/result-pdf.xsl b/library/src/main/resources/stylesheets/result-pdf.xsl index 0dfa7c95..f3e2e0d4 100644 --- a/library/src/main/resources/stylesheets/result-pdf.xsl +++ b/library/src/main/resources/stylesheets/result-pdf.xsl @@ -28,6 +28,14 @@ red + + + Das XML ist valide. + + + Das XML ist nicht valide. + + green @@ -37,18 +45,26 @@ - + Das ZUGFeRD-PDF ist valide. - + Das ZUGFeRD-PDF ist nicht valide. + + + green + + + red + + - + Es wird empfohlen, das Dokument anzunehmen und es weiterzuverarbeiten. - + Es wird empfohlen, das Dokument zurückzuweisen. @@ -118,6 +134,7 @@ + + + + + + + + + Beispielgeschäftsprozess + + + urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended + + + + 47110815 + RECHNUNG + 380 + + 20241115 + + + Mitglieder der Geschäftsleitung + H. Meier Geschäftsführer + T. Müller Prokurist + HRB Braunschweig 12345 + REG + + + Vom 17. Dezember 2024 bis 6. Januar 2025 haben wir Betriebsferien. + AAI + + + Aus konzern-internen Gründen wird der Steuerbetrag sowohl in der Rechungswährung (EUR) als auch in der Buchwährung (GBP) ausgegeben. + TXD + + + + + + 1 + + Materialzertifikat X-234 gem ISO XYZ. + Ware bleibt bis zur vollständigen Bezahlung unser Eigentum. + + + + + CO-123/V2A + Toolbox 0815 + Stahlcoil + + DE + + + + + ORDER84359 + 1 + + + 100.00 + 1 + + + 100 + 1 + + + + 10 + + + + VAT + S + 19 + + + + false + + 10 + 1000 + 100 + 64 + Lagerware + + + + false + + 1000 + 50 + 70 + Direktbelieferung + + + 850 + + + + + + 12345676 + Rohstoff AG Salzgitter + + 38226 + Marktstr. 153 + Salzgitter + DE + + + DE123456789 + + + + 75969813 + Metallbau Leipzig GmbH & Co. KG + + 12345 + Pappelallee 15 + Hof 3 + Leipzig + DE + + + 04 0 11 000 - 12345 12345 - 35 + + + + Global Supplies Financial Services + + 12345 + Friedrichstraße 165 + Berlin + DE + + + DE1334567 + + + + + + 75969815 + Metallbau Leipzig GmbH & Co. KG + + 12347 + Eichenpromenade 37 + Tor 1 + Metallstadt + DE + + + 999999999 + + + + + 20241111 + + + + + EUR + GBP + + 432156789 + Global Supplies Financial Services + + 12345 + Friedrichstraße 165 + Berlin + DE + + + + GBP + EUR + 1.12244 + + 20181031 + + + + 58 + + DE77 3707 0060 0321 9870 00 + Global Supplies Financial Services + + + + 163.16 + VAT + 858.75 + 850 + 8.75 + S + 19 + + + + 20181001 + + + 20181031 + + + + + true + + 30 + ABK + Einwegverpackung + + VAT + S + 19 + + + + + false + + 2.5 + 850 + 21.25 + 102 + Stammkundenrabatt + + VAT + S + 19 + + + + Zahlbar ohne Abschlag bis + + 20241201 + + + + Zahlbar mit 2% Skonto bis + + 20241120 + + + + 850 + 30 + 21.25 + 858.75 + 163.16 + 183.14 + 1021.91 + 500 + 521.91 + + + + diff --git a/library/src/test/resources/cii/extended_warenrechnung.xml b/library/src/test/resources/cii/extended_warenrechnung.xml new file mode 100644 index 00000000..46665e1a --- /dev/null +++ b/library/src/test/resources/cii/extended_warenrechnung.xml @@ -0,0 +1,567 @@ + + + + + + + + + + true + + + urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended + + + + R87654321012345 + WARENRECHNUNG + 380 + + 20180806 + + + ST3 + Es bestehen Rabatt- oder Bonusvereinbarungen. + AAK + + + EEV + Der Verkäufer bleibt Eigentümer der Waren bis zu vollständigen Erfüllung der Kaufpreisforderung. + AAJ + + + MUSTERLIEFERANT GMBH +BAHNHOFSTRASSE 99 +99199 MUSTERHAUSEN +Geschäftsführung: +Max Mustermann +USt-IdNr: DE123456789 +Telefon: +49 932 431 0 +www.musterlieferant.de +HRB Nr. 372876 +Amtsgericht Musterstadt +GLN 4304171000002 +WEEE-Reg-Nr.: DE87654321 + + REG + + + Leergutwert: 46,50 + + + Wichtige Information: Bei Bestellungen bis zum 19.12. ist die Auslieferung bis spätestens 23.12. garantiert. + + + + + + 1 + + + 4123456000014 + ZS997 + Zitronensäure 100ml + + Verpackungsart + BO + + + + + 1.0000 + + + 1.0000 + + + + 100.0000 + 4.0000 + + + + VAT + S + 19.00 + + + 100.00 + + + + + + 2 + + + 4123456000021 + GZ250 + Gelierzucker Extra 250g + + + + 1.5000 + + + false + + 0.0300 + Artikelrabatt 1 + + + + false + + 0.0200 + Artikelrabatt 2 + + + + 1.4500 + + + + 50.0000 + 1.0000 + + + + VAT + S + 7.00 + + + 72.50 + + + + + + 3 + + + 4123456000021 + GZ250 + Gelierzucker Extra 250g + Artikel wie vereinbart ohne Berechnung + + + + 0.0000 + + + 0.0000 + + + + 10.0000 + 1.0000 + + + + VAT + S + 7.00 + + + 0.00 + + + + + + 4 + + + 4100130013294 + 2031 + + Bierbrau Pils 20/0500 + EAN-VKE: 4100130913297 + + Verpackung + Kiste + + + + + 12.0000 + + + 12.0000 + + + + 15.0000 + 20.0000 + + + + VAT + S + 19.00 + + + 180.00 + + + + + + 5 + + + 2001015001325 + 1805 + + Leergutpfand 20 x 0,5l + + Verpackung + unverpackt + + + + + 3.1000 + + + 3.1000 + + + + 15.0000 + 1.0000 + + + + VAT + S + 19.00 + + + 46.50 + + + + + + 6 + + + 4123456000038 + MP107 + Mischpalette Joghurt Karton 3 x 20 + + Verpackung + Karton + + + 4123456001035 + JOG103 + Erdbeer 20 x 150g Becher + 20.0000 + + + 4123456002032 + JOG203 + Banane 20 x 150g Becher + 20.0000 + + + 4123456003039 + JOG303 + Schoko 20 x 150g Becher + 20.0000 + + + + + 30.0000 + + + false + + 0.9000 + Artikelrabatt 1 + + + + 29.1000 + + + + 2.0000 + 1.0000 + + + + VAT + S + 7.00 + + + 58.20 + + + + + + 549910 + 4333741000005 + MUSTERLIEFERANT GMBH + + + +49 932 431 500 + + + max.mustermann@musterlieferant.de + + + + 99199 + BAHNHOFSTRASSE 99 + MUSTERHAUSEN + DE + + + DE123456789 + + + + 009420 + 4304171000002 + MUSTER-KUNDE GMBH + + 40235 + KUNDENWEG 88 + DUESSELDORF + DE + + + + B123456789 + + + A456123 + 130 + + + + + 4304171088093 + MUSTER-MARKT + + 8211 + + + 31157 + HAUPTSTRASSE 44 + SARSTEDT + DE + + + + + 20180805 + + + + L87654321012345 + + + + EUR + + 009420 + 4304171000002 + MUSTER-KUNDE GMBH + + 40235 + KUNDENWEG 88 + DUESSELDORF + DE + + + + 61.07 + VAT + 321.40 + 326.50 + -5.10 + S + 19.00 + + + 8.93 + VAT + 127.59 + 130.70 + -3.11 + S + 7.00 + + + + false + + 2.00 + 280.00 + 5.60 + Rechnungsrabatt 1 + + VAT + S + 19.00 + + + + + false + + 2.00 + 130.70 + 2.61 + Rechnungsrabatt 1 + + VAT + S + 7.00 + + + + + false + + 280.00 + 2.50 + Rechnungsrabatt 2 + + VAT + S + 19.00 + + + + + false + + 130.70 + 0.50 + Rechnungsrabatt 2 + + VAT + S + 7.00 + + + + Transportkosten + 3.00 + + VAT + S + 19.00 + + + + Bei Zahlung innerhalb 14 Tagen gewähren wir 2,0% Skonto. + + 14 + 2.00 + + + + 457.20 + 3.00 + 11.21 + 448.99 + 70.00 + 518.99 + 0.00 + 518.99 + + + + \ No newline at end of file diff --git a/library/src/test/resources/factur-x-vis-extended.de.html b/library/src/test/resources/factur-x-vis-extended.de.html index ed5a147d..4ef990b5 100644 --- a/library/src/test/resources/factur-x-vis-extended.de.html +++ b/library/src/test/resources/factur-x-vis-extended.de.html @@ -762,7 +762,7 @@
KUNDENWEG 88
-
Postfach:
+
Adresszusatz:
@@ -826,7 +826,7 @@
BAHNHOFSTRASSE 99
-
Postfach:
+
Adresszusatz:
@@ -2203,7 +2203,7 @@
HAUPTSTRASSE 44
-
Postfach:
+
Adresszusatz:
diff --git a/library/src/test/resources/factur-x-vis.fr.html b/library/src/test/resources/factur-x-vis.fr.html index e22b12ef..5de16ae2 100644 --- a/library/src/test/resources/factur-x-vis.fr.html +++ b/library/src/test/resources/factur-x-vis.fr.html @@ -1905,6 +1905,55 @@
+
+
Informations de livraison
+
+
+
Identification du lieu de livraison:
+
+
+
+
Schéma de l'Identifiant:
+
+
+
+
Date de livraison:
+
10.11.2020
+
+
+
Nom du destinataire:
+
+
+
+
Rue / Numéro de maison:
+
+
+
+
Boîte postale:
+
+
+
+
Supplément d'adresse:
+
+
+
+
Code postal:
+
+
+
+
Lieu:
+
+
+
+
Région:
+
+
+
+
Pays:
+
+
+
+
@@ -2120,4 +2169,4 @@ function downloadData (element_id) { }); // - + \ No newline at end of file diff --git a/library/src/test/resources/ubl/periods.ubl.xml b/library/src/test/resources/ubl/periods.ubl.xml new file mode 100644 index 00000000..d9387bfd --- /dev/null +++ b/library/src/test/resources/ubl/periods.ubl.xml @@ -0,0 +1,197 @@ + + + urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended + 123 + 2025-02-10 + 2025-02-10 + 380 + document level 1/2 + document level 2/2 + CHF + + 2020-10-01 + 2020-10-05 + 432 + + + 28934 + 9384 + + + + abc123 + + + + 376zreurzu0983 + + + + sender@test.org + + 0009845 + + + teststr + teststadt + 55232 + + DE + + + + DE0815 + + VAT + + + + 9990815 + + NOVAT + + + + Test company + + + + + + recipient@test.org + + 0088:4304171000002 + + + teststr.12 + Hinterhaus 3 + Entenhausen + 55232 + + DE + + + + DE4711 + + VAT + + + + Franz Müller + + + 01779999999 + franz@mueller.de + + + + + 2020-11-02 + + + teststr.12a + Entenhausen + 55232 + + DE + + + + + + just the other side of the street + + + + + Verwendungszweck + + + Please remit until 10.02.2025 + + + false + discount + 0.20 + + S + 16.00 + + VAT + + + + + true + quick delivery charge + 0.50 + + S + 16.00 + + VAT + + + + + 0.20 + + 1.28 + 0.20 + + S + 16.00 + + VAT + + + + + + 0.98 + 1.28 + 1.48 + 0.20 + 0.50 + 1.48 + + + a123 + item level 1/1 + 1.00000000 + 0.98 + + 2020-01-13 + 2020-01-15 + + + xxx + + + Testprodukt + + 4711 + + + 2001015001325 + + + S + 16.00 + + VAT + + + + + 0.98 + 1.00 + + false + 0.0200 + 1.0000 + + + + diff --git a/pom.xml b/pom.xml index 03f326a5..c3dbca52 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.mustangproject core - 2.16.1-SNAPSHOT pom + 2.17.0-SNAPSHOT pom Mustang diff --git a/validator/pom.xml b/validator/pom.xml index 6ceb0dd3..47f731a9 100644 --- a/validator/pom.xml +++ b/validator/pom.xml @@ -3,7 +3,7 @@ org.mustangproject core - 2.16.1-SNAPSHOT + 2.17.0-SNAPSHOT 4.0.0 org.mustangproject @@ -11,7 +11,7 @@ Library to validate e-invoices (ZUGFeRD, Factur-X and Xrechnung) jar - 2.16.1-SNAPSHOT + 2.17.0-SNAPSHOT @@ -38,7 +38,7 @@ ${project.groupId} library - 2.16.1-SNAPSHOT + 2.17.0-SNAPSHOT org.dom4j diff --git a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java index c8bd3475..cf911dc6 100644 --- a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java @@ -10,6 +10,7 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.Calendar; +import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.stream.StreamSource; @@ -149,8 +150,23 @@ public class XMLValidator extends Validator { */ final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - dbf.setNamespaceAware(true); // otherwise we can not act namespace independently, i.e. use - // document.getElementsByTagNameNS("*",... + //REDHAT + //https://www.blackhat.com/docs/us-15/materials/us-15-Wang-FileCry-The-New-Age-Of-XXE-java-wp.pdf + dbf.setAttribute(XMLConstants.FEATURE_SECURE_PROCESSING, true); + dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + + //OWASP + //https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); + dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + // Disable external DTDs as well + dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks" + dbf.setXIncludeAware(false); + dbf.setExpandEntityReferences(false); + dbf.setNamespaceAware(true); final DocumentBuilder db = dbf.newDocumentBuilder(); final InputSource is = new InputSource(new StringReader(zfXML)); diff --git a/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java b/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java index d21317b1..a64efdce 100644 --- a/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java @@ -17,6 +17,7 @@ import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; +import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -142,6 +143,22 @@ public class ZUGFeRDValidator { String xmlAsString = null; try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + //REDHAT + //https://www.blackhat.com/docs/us-15/materials/us-15-Wang-FileCry-The-New-Age-Of-XXE-java-wp.pdf + dbf.setAttribute(XMLConstants.FEATURE_SECURE_PROCESSING, true); + dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + + //OWASP + //https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + // Disable external DTDs as well + dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks" + dbf.setXIncludeAware(false); + dbf.setExpandEntityReferences(false); + dbf.setNamespaceAware(true); DocumentBuilder db = dbf.newDocumentBuilder(); content = XMLTools.removeBOM(content); @@ -294,6 +311,7 @@ public class ZUGFeRDValidator { XMLWriter writer = new XMLWriter(sw, format); try { writer.write(document); + writer.close(); } catch (Exception e) { LOGGER.error(e.getMessage()); } diff --git a/validator/src/main/resources/schematron/XR_30/XRechnung-CII-validation.sch b/validator/src/main/resources/schematron/XR_30/XRechnung-CII-validation.sch new file mode 100644 index 00000000..07b760ef --- /dev/null +++ b/validator/src/main/resources/schematron/XR_30/XRechnung-CII-validation.sch @@ -0,0 +1,326 @@ + + + Schematron Version @xr-schematron.version.full@ - XRechnung @xrechnung.version@ compatible - CII + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [BR-DE-30] Wenn "DIRECT DEBIT" BG-19 vorhanden ist, dann muss "Bank assigned creditor identifier" BT-90 übermittelt werden. + [BR-DE-31] Wenn "DIRECT DEBIT" BG-19 vorhanden ist, dann muss "Debited account identifier" BT-91 übermittelt werden. +[BR-DE-1] Eine Rechnung (INVOICE) muss Angaben zu "PAYMENT INSTRUCTIONS" (BG-16) enthalten. + [BR-DE-15] Das Element "Buyer reference" (BT-10) muss übermittelt werden. + [BR-DE-16] Wenn in einer Rechnung die Steuercodes S, Z, E, AE, K, G, L oder M verwendet werden, muss mindestens eines der Elemente "Seller VAT identifier" (BT-31), "Seller tax registration identifier" (BT-32) + oder "SELLER TAX REPRESENTATIVE PARTY" (BG-11) übermittelt werden. + + [BR-DE-17] Mit dem Element "Invoice type code" (BT-3) sollen ausschließlich folgende Codes aus der Codeliste UNTDID 1001 übermittelt werden: 326 (Partial invoice), 380 (Commercial invoice), 384 (Corrected invoice), 389 (Self-billed invoice) und 381 (Credit note),875 (Partial construction invoice), 876 (Partial final construction invoice), 877 (Final construction invoice). + [BR-DE-18] Skonto Zeilen in muessen diesem regulärem Ausdruck entsprechen: . Die Informationen zur Gewährung von Skonto müssen wie folgt im Element "Payment terms" (BT-20) übermittelt werden: Anzugeben ist im ersten Segment "SKONTO", im zweiten "TAGE=n", im dritten "PROZENT=n". Prozentzahlen sind ohne Vorzeichen sowie mit Punkt getrennt von zwei Nachkommastellen anzugeben. Liegt dem zu berechnenden Betrag nicht BT-115, "fälliger Betrag" zugrunde, sondern nur ein Teil des fälligen Betrags der Rechnung, ist der Grundwert zur Berechnung von Skonto als viertes Segment "BASISBETRAG=n" gemäß dem semantischen Datentypen Amount anzugeben. Jeder Eintrag beginnt mit einer #, die Segmente sind mit einer # getrennt und eine Zeile schließt mit einer # ab. Am Ende einer vollständigen Skontoangabe muss ein XML-konformer Zeilenumbruch folgen. Alle Angaben zur Gewährung von Skonto müssen in Großbuchstaben gemacht werden. Zusätzliches Whitespace (Leerzeichen, Tabulatoren oder Zeilenumbrüche) ist nicht zulässig. Andere Zeichen oder Texte als in den oberen Vorgaben genannt sind nicht zulässig. + + [BR-DE-22] Not all filename attributes of the embeddedDocumentBinaryObject elements are unique + [BR-DE-26] Wenn im Element Invoice type code (BT-3) der Code 384 (Corrected invoice) übergeben wird, soll PRECEDING INVOICE REFERENCE BG-3 mind. einmal vorhanden sein. + + + + [BR-DE-21] Das Element "Specification identifier" (BT-24) soll syntaktisch der Kennung des Standards XRechnung entsprechen. + + + + [BR-DE-2] Die Gruppe "SELLER CONTACT" (BG-6) muss übermittelt werden. + + + + [BR-DE-3] Das Element "Seller city" (BT-37) muss übermittelt werden. + [BR-DE-4] Das Element "Seller post code" (BT-38) muss übermittelt werden. + + + + [BR-DE-5] Das Element "Seller contact point" (BT-41) muss übermittelt werden. + [BR-DE-6] Das Element "Seller contact telephone number" (BT-42) muss übermittelt werden. + [BR-DE-7] Das Element "Seller contact email address" (BT-43) muss übermittelt werden. + [BR-DE-27] In BT-42 sollen mindestens drei Ziffern enthalten sein. + [BR-DE-28] In BT-43 soll genau ein @-Zeichen enthalten sein, welches nicht von einem Leerzeichen, einem Punkt, aber mindestens zwei Zeichen auf beiden Seiten flankiert werden soll. Ein Punkt sollte nicht am Anfang oder am Ende stehen. + + + + [BR-DE-8] Das Element "Buyer city" (BT-52) muss übermittelt werden. + [BR-DE-9] Das Element "Buyer post code" (BT-53) muss übermittelt werden. + + + [BR-TMP-2] BT-124 "External document location" muss eine absolute URL mit gültigem Schema enthalten. + + + + [BR-DE-10] Das Element "Deliver to city" (BT-77) muss übermittelt werden, wenn die Gruppe "DELIVER TO ADDRESS" (BG-15) übermittelt wird. + [BR-DE-11] Das Element "Deliver to post code" (BT-78) muss übermittelt werden, wenn die Gruppe "DELIVER TO ADDRESS" (BG-15) übermittelt wird. + + + + [BR-DE-19] "Payment account identifier" (BT-84) soll eine korrekte IBAN enthalten, wenn in "Payment means type code" (BT-81) mit dem Code 58 SEPA als Zahlungsmittel gefordert wird. + [BR-DE-23-a] Wenn BT-81 "Payment means type code" einen Schlüssel für Überweisungen enthält (30, 58), muss BG-17 "CREDIT TRANSFER" übermittelt werden. + [BR-DE-23-b] Wenn BT-81 "Payment means type code" einen Schlüssel für Überweisungen enthält (30, 58), dürfen BG-18 und BG-19 nicht übermittelt werden. + + + + [BR-DE-24-a] Wenn BT-81 "Payment means type code" einen Schlüssel für Kartenzahlungen enthält (48, 54, 55), muss genau BG-18 "PAYMENT CARD INFORMATION" übermittelt werden. + [BR-DE-24-b] Wenn BT-81 "Payment means type code" einen Schlüssel für Kartenzahlungen enthält (48, 54, 55), dürfen BG-17 und BG-19 nicht übermittelt werden. + + + + [BR-DE-20] "Debited account identifier" (BT-91) soll eine korrekte IBAN enthalten, wenn in "Payment means type code" (BT-81) mit dem Code 59 SEPA als Zahlungsmittel gefordert wird. + [BR-DE-25-a] Wenn BT-81 "Payment means type code" einen Schlüssel für Lastschriften enthält (59), muss genau BG-19 "DIRECT DEBIT" übermittelt werden. + [BR-DE-25-b] Wenn BT-81 "Payment means type code" einen Schlüssel für Lastschriften enthält (59), dürfen BG-17 und BG-18 nicht übermittelt werden. + + + + [BR-DE-14] Das Element "VAT category rate" (BT-119) muss übermittelt werden. + + + + + + + + + [BR-DEX-15] This CII file might use the concept of Sub Invoice Lines. However XRechnung does not support this. + + + + + [BR-DEX-04] Any scheme identifier in MUST be coded using one of the ISO 6523 ICD list. + + + + [BR-DEX-05] Any scheme identifier in MUST be coded using one of the ISO 6523 ICD list. + + + + [BR-DEX-06] Any scheme identifier in MUST be coded using one of the ISO 6523 ICD list. + + + + [BR-DEX-07] Any scheme identifier for an Endpoint Identifier in MUST belong to the CEF EAS code list. + + + + [BR-DEX-08] Any scheme identifier for a Delivery location identifier in MUST be coded using one of the ISO 6523 ICD list. + + + + + [BR-DEX-01] Das Element "Attached Document" (BT-125) benutzt einen nicht zulässigen MIME-Code: . Im Falle einer Extension darf zusätzlich zu der Liste der mime codes (definiert in Abschnitt 8.2, "Binary Object") der MIME-Code application/xml genutzt werden. + + +