diff --git a/History.md b/History.md index 28aacba3..7fddc839 100644 --- a/History.md +++ b/History.md @@ -1,5 +1,30 @@ -#1049 -#1044 +2.23.0 "Bonne Journées de la Facture Électronique" +======= +2026-04-23 + +improved subitem support, support french validation + +- #993 Cash discount not parsed +- #1029 Added a breaking change notice regarding the removal of hardcoded values in #729 +- #1037 Compress attachments +- #1038 Header allowances are aggregated incorrectly for non-XRechnung profiles +- #1044 Correct version of org.apache.pdfbox:fontbox from 3.0.3 to 3.0.6. +- #1046 Add configurable BT-23 business process ID for CII export +- #1049 Prevent exceptions in validation +- #1050 Add validator information to PDF report (name and version) +- #1052 Remove parsing OriginatorDocumentReference.ID as date +- #1055 Correct order of ApplicableTradeTax sub-elements. +- #1061 Add ability to mark an Invoice as a test invoice. +- #1072 Correct XPath-expressions for GrandTotal and TaxBasisTotalAmount. +- #1073 add hierarchical invoice positions in CII XML export for EXTENDED profile +- #1075 Vulnerability in dependency PDFBox version 3.0.6 +- #1076 XMP error during validation based on mustang version 2.22. (XMP Metadata: Could not parse XMP metadata (XML invalid)) +- #1082 Centralize and secure DocumentBuilder creation. +- #1083 PDF Visualizations in English and French +- #1084 Fix xml description +- #1089 Add France schematron ruleset +- reduce possible exceptions in metrics action +- added Extended-CTC-FR profile to selection for command line 2.22.0 ======= @@ -98,7 +123,7 @@ corrections - support parsing of BT-90 CreditorReferenceID - #871 schema validation does not ignore external entities - #868 Fix wrong version in History.md -- #729 Updates about SpecifiedTradeSettlementHeaderMonetarySummation and SpecifiedTradeSettlementPaymentMeans +- #729 Updates about SpecifiedTradeSettlementHeaderMonetarySummation and SpecifiedTradeSettlementPaymentMeans. **Breaking Change:** The hardcoded values of ram:TypeCode (58) and ram:Information ("SEPA credit transfer") in the getSettlementXML() method of the IZUGFeRDTradeSettlementPayment interface have been removed. Starting with this version, any code utilizing this interface must explicitly set the corresponding attributes. - #863 LineCalculator throws NPE if product is null (since 2.17.0) - #731 Got a broken translation key when visualizing XML into PDF (xr:Business_process_type) - #865 Add sevdesk signature to PDF creators @@ -199,7 +224,7 @@ corrections - #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 +- be able to set detailedDeliveryPeriodFrom, detailedDeliverDeliveryPeriodTo 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/src/main/java/org/mustangproject/commandline/Main.java b/Mustang-CLI/src/main/java/org/mustangproject/commandline/Main.java index 429eb3d9..a9b26b21 100755 --- a/Mustang-CLI/src/main/java/org/mustangproject/commandline/Main.java +++ b/Mustang-CLI/src/main/java/org/mustangproject/commandline/Main.java @@ -44,74 +44,73 @@ public class Main { private static String getUsage() { return "Usage: --action metrics|combine|extract|a3only|ubl|validate|validateExpectInvalid|validateExpectValid|visualize [-d,--directory] [-l,--listfromstdin] [-i,--ignore fileextension, PDF/A errors] [--disable-file-logging] | [-h,--help] \r\n" - + " --action license display open source license and notice\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" - + " 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" - + " [--disable-file-logging] disable logging to file.\n" - + " --action extract extract Factur-X 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" - + " --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" - + " --action combine combine XML and PDF file to Factur-X 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" - + " [--out ]: set output PDF file\n" - + " [--format ]: set Factur-X, ZUGFeRD, Order-X or Cross Industry Despatch Advice\n" - + " [--version <1|2>]: set ZUGFeRD version\n" - + " [--profile <...>]: set ZUGFeRD profile\n" - + " For ZUGFeRD v1 or Order-X: ASIC, OMFORT or EXENDED\n" - + " 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" - + " [--source ]: set input XML file\n" - + " [--out ]: set output XML file\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" - + " --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" - + " [--log-as-pdf]: save log output as pdf\n" - + " --action validateExpectInvalid validate directory recursively expecting negative results \n" - + " [--no-notices]: refrain from reporting notices\n" - + " Additional parameters (user will be prompted if not defined)\n" - + " -d, --directory to check recursively\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 (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" - + " [--out ]: set output HTML file\n" - + " --action pdf convert XML to PDF \n" - + " [--source ]: set input XML file\n" - + " [--out ]: set output PDF file\n" - ; + + " --action license display open source license and notice\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" + + " 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" + + " [--disable-file-logging] disable logging to file.\n" + + " --action extract extract Factur-X 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" + + " --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" + + " --action combine combine XML and PDF file to Factur-X 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" + + " [--out ]: set output PDF file\n" + + " [--format ]: set Factur-X, ZUGFeRD, Order-X or Cross Industry Despatch Advice\n" + + " [--version <1|2>]: set ZUGFeRD version\n" + + " [--profile <...>]: set ZUGFeRD profile\n" + + " For ZUGFeRD v1 or Order-X: ASIC, OMFORT or EXENDED\n" + + " For ZUGFeRD v2: INIMUM, BASIC L, ASIC, IUS, N16931, Rechnung, EXTENDED-CTC-[F]R, 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" + + " [--source ]: set input XML file\n" + + " [--out ]: set output XML file\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" + + " --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" + + " [--log-as-pdf]: save log output as pdf\n" + + " --action validateExpectInvalid validate directory recursively expecting negative results \n" + + " [--no-notices]: refrain from reporting notices\n" + + " Additional parameters (user will be prompted if not defined)\n" + + " -d, --directory to check recursively\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 (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" + + " [--out ]: set output HTML file\n" + + " --action pdf convert XML to PDF \n" + + " [--source ]: set input XML file\n" + + " [--out ]: set output PDF file\n"; } private static void printHelp() { System.out.println("Mustangproject.org " + org.mustangproject.ZUGFeRD.Version.VERSION + " \r\n" - + "A Apache Public License tool for e-invoices with\r\n" - + "ZUGFeRD Metadata (http://www.zugferd.org)\r\n" + "\r\n" + getUsage() + "\r\n"); + + "A Apache Public License tool for e-invoices with\r\n" + + "ZUGFeRD Metadata (http://www.zugferd.org)\r\n" + "\r\n" + getUsage() + "\r\n"); } /** @@ -162,14 +161,16 @@ public class Main { * Prompts the user for a input or output filename * * @param prompt the text the user is asked - * @param defaultFilename a default Filename. Passing an empty string indicates that specifying a file is optional - * @param expectedExtension will warn if filename does not match expected file extension, "or" possible with e.g. pdf|xml + * @param defaultFilename a default Filename. Passing an empty string + * indicates that specifying a file is optional + * @param expectedExtension will warn if filename does not match expected file + * extension, "or" possible with e.g. pdf|xml * @param ensureFileExists will warn if file does NOT exist (for input files) * @param ensureFileNotExists will warn if file DOES exist (for output files) * @return String */ protected static String getFilenameFromUser(String prompt, String defaultFilename, String expectedExtension, - boolean ensureFileExists, boolean ensureFileNotExists) { + boolean ensureFileExists, boolean ensureFileNotExists) { boolean fileExistenceOK = false; String selectedName = ""; do { @@ -187,7 +188,7 @@ public class Main { if (selectedName.isEmpty()) { // pressed return without entering anything if (defaultFilename.isEmpty()) { - return ""; //no default -> this is an optional filename + return ""; // no default -> this is an optional filename } selectedName = defaultFilename; } @@ -211,7 +212,7 @@ public class Main { } } else if (!hasCorrectExtension) { System.err.println("Expected " + expectedExtension - + " extension, this may corrupt your file. Do you still want to continue?(Y|N)"); + + " extension, this may corrupt your file. Do you still want to continue?(Y|N)"); String selectedAnswer = ""; try { selectedAnswer = buffer.readLine(); @@ -270,6 +271,7 @@ public class Main { /*** * converts from CII to UBL + * * @param xmlName the name of the xml file * @param outName the name of the output file * @throws IOException @@ -309,7 +311,8 @@ public class Main { } // Plain Java - // based on https://mkyong.com/java/how-to-convert-inputstream-to-string-in-java/ + // based on + // https://mkyong.com/java/how-to-convert-inputstream-to-string-in-java/ private static String convertInputStreamToString(InputStream is) { try (InputStream inputStream = is) { int DEFAULT_BUFFER_SIZE = 8192; @@ -333,7 +336,9 @@ public class Main { /*** * the main function of the commandline tool... - * @param args the commandline args, see also https://www.mustangproject.org/commandline/#verbose + * + * @param args the commandline args, see also + * https://www.mustangproject.org/commandline/#verbose */ public static void main(String[] args) { try { @@ -347,12 +352,14 @@ public class Main { options.addOption(new Option("f", "format", true, "which format to output")); options.addOption(new Option("version", "version", true, "which version of the standard to use")); options.addOption(new Option("profile", "profile", true, "which profile of the standard to use")); - options.addOption(new Option("no-additional-attachments", "no-additional-attachments", false, "prevent prompting for attachments")); + options.addOption(new Option("no-additional-attachments", "no-additional-attachments", false, + "prevent prompting for attachments")); 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"); + Option excludeOpt = new Option("exclude", "exclude", true, + "Files to exclude from recursive directory traversal"); excludeOpt.setValueSeparator(','); excludeOpt.setArgs(Option.UNLIMITED_VALUES); options.addOption(excludeOpt); @@ -361,8 +368,10 @@ public class Main { options.addOption(new Option("language", "language", true, "output language (en, de or fr)")); options.addOption(new Option("out", "out", true, "which output file to write to")); options.addOption(new Option("no-notices", "no-notices", false, "suppress non-fatal errors")); - options.addOption(new Option("logAppend", "logAppend", true, "freeform text to be appended to log messages")); - options.addOption(new Option("disable-file-logging", "disable-file-logging", false, "suppress logging to file")); + options.addOption( + new Option("logAppend", "logAppend", true, "freeform text to be appended to log messages")); + options.addOption( + new Option("disable-file-logging", "disable-file-logging", false, "suppress logging to file")); options.addOption(new Option("d", "directory", true, "which directory to operate on")); options.addOption(new Option("i", "ignorefileextension", false, "ignore non-matching file extensions")); options.addOption(new Option("l", "listfromstdin", false, "take list of files from commandline")); @@ -378,7 +387,7 @@ public class Main { // Retrieve all options action = cmd.getOptionValue("action"); String directoryName = cmd.getOptionValue("directory"); - boolean filesFromStdIn = cmd.hasOption("listfromstdin");//((Number)cmdLine.getParsedOptionValue("integer-option")).intValue(); + boolean filesFromStdIn = cmd.hasOption("listfromstdin");// ((Number)cmdLine.getParsedOptionValue("integer-option")).intValue(); boolean ignoreFileExt = cmd.hasOption("ignorefileextension"); boolean noAttachments = cmd.hasOption("no-additional-attachments"); boolean helpRequested = cmd.hasOption("help") || ((action != null) && (action.equals("help"))); @@ -398,11 +407,10 @@ public class Main { String[] attachmentFilenames = cmd.hasOption("attachments") ? cmd.getOptionValues("attachments") : null; String[] excludedFilenames = cmd.hasOption("exclude") ? cmd.getOptionValues("exclude") : null; - ArrayList attachments = new ArrayList<>(); /* - setting system property to disable FILE appender and - suppress creation of files and folders. + * setting system property to disable FILE appender and + * suppress creation of files and folders. */ System.setProperty("FILE_APPENDER_ENABLED", ((Boolean) (disableFileLogging == false)).toString()); @@ -411,14 +419,17 @@ public class Main { if (helpRequested) { printHelp(); optionsRecognized = true; - } /* else if ((action!=null)&&(action.equals("license"))) { - printLicense(); - optionsRecognized=true; - } */ else if ((action != null) && (action.equals("metrics"))) { + } /* + * else if ((action!=null)&&(action.equals("license"))) { + * printLicense(); + * optionsRecognized=true; + * } + */ else if ((action != null) && (action.equals("metrics"))) { performMetrics(directoryName, filesFromStdIn, ignoreFileExt); optionsRecognized = true; } else if ((action != null) && (action.equals("combine"))) { - performCombine(sourceName, sourceXMLName, outName, format, zugferdVersion, zugferdProfile, ignoreFileExt, attachmentFilenames, attachments, noAttachments); + performCombine(sourceName, sourceXMLName, outName, format, zugferdVersion, zugferdProfile, + ignoreFileExt, attachmentFilenames, attachments, noAttachments); optionsRecognized = true; } else if ((action != null) && (action.equals("extract"))) { performExtract(sourceName, outName); @@ -439,7 +450,8 @@ public class Main { performUBL(sourceName, outName); optionsRecognized = true; } else if ((action != null) && (action.equals("validate"))) { - optionsRecognized = performValidate(sourceName, noNotices, cmd.getOptionValue("logAppend"), LogAsPDF); + optionsRecognized = performValidate(sourceName, noNotices, cmd.getOptionValue("logAppend"), + LogAsPDF); } else if ((action != null) && (action.equals("validateExpectValid"))) { optionsRecognized = performValidateExpect(true, directoryName, excludedFilenames); } else if ((action != null) && (action.equals("validateExpectInvalid"))) { @@ -466,7 +478,8 @@ public class Main { } - private static boolean performValidate(String sourceName, boolean noNotices, String logAppend, boolean createLogAsPDF) { + private static boolean performValidate(String sourceName, boolean noNotices, String logAppend, + boolean createLogAsPDF) { boolean optionsRecognized; if (sourceName == null) { sourceName = getFilenameFromUser("Source PDF or XML", "invoice.pdf", "pdf|xml", true, false); @@ -482,7 +495,7 @@ public class Main { String validationResultXML = zfv.validate(sourceName); System.out.println(validationResultXML); - if( createLogAsPDF) { + if (createLogAsPDF) { ValidationLogVisualizer vlvi = new ValidationLogVisualizer(); String fileBasename = FilenameUtils.getBaseName(sourceName); @@ -516,7 +529,6 @@ public class Main { return true; } - private static void performConvert(String pdfName, String outName) throws IOException { /* * ZUGFeRDExporter ze= new ZUGFeRDExporterFromA1Factory() @@ -541,9 +553,9 @@ public class Main { // All params are good! continue... try (ZUGFeRDExporterFromA1 ze = new ZUGFeRDExporterFromA1()) { - ze.convertOnly().load(pdfName); - ze.export(outName); - System.out.println("Written to " + outName); + ze.convertOnly().load(pdfName); + ze.export(outName); + System.out.println("Written to " + outName); } } @@ -579,7 +591,8 @@ public class Main { } private static void performCombine(String pdfName, String xmlName, String outName, String format, String zfVersion, - String zfProfile, Boolean ignoreInputErrors, String[] attachmentFilenames, ArrayList attachments, Boolean noAttachments) throws Exception { + String zfProfile, Boolean ignoreInputErrors, String[] attachmentFilenames, + ArrayList attachments, Boolean noAttachments) throws Exception { /* * ZUGFeRDExporter ze= new ZUGFeRDExporterFromA1Factory() * .setProducer("toecount") .setCreator(System.getProperty("user.name")) @@ -611,11 +624,13 @@ public class Main { byte attachmentContents[] = null; String attachmentFilename, attachmentMime; if (!noAttachments) { - attachmentFilename = getFilenameFromUser("Additional file attachments filename (empty for none)", "", "pdf", true, false); + attachmentFilename = getFilenameFromUser("Additional file attachments filename (empty for none)", + "", "pdf", true, false); if (attachmentFilename.length() != 0) { attachmentContents = Files.readAllBytes(Paths.get(attachmentFilename)); attachmentMime = Files.probeContentType(Paths.get(attachmentFilename)); - attachments.add(new FileAttachment(attachmentFilename, attachmentMime, "Data", attachmentContents)); + attachments.add( + new FileAttachment(attachmentFilename, attachmentMime, "Data", attachmentContents)); } } } else { @@ -624,15 +639,17 @@ public class Main { if (!attachmentFilename.isEmpty()) { byte[] attachmentContents = Files.readAllBytes(Paths.get(attachmentFilename)); String attachmentMime = Files.probeContentType(Paths.get(attachmentFilename)); - attachments.add(new FileAttachment(attachmentFilename, attachmentMime, "Data", attachmentContents)); + attachments.add( + new FileAttachment(attachmentFilename, attachmentMime, "Data", attachmentContents)); } } } - if (format == null) { try { - format = getStringFromUser("Format (fx=Factur-X, zf=ZUGFeRD, ox=Order-X, da=Cross Industry Despatch Advice)", "fx", "fx|zf|ox|da"); + format = getStringFromUser( + "Format (fx=Factur-X, zf=ZUGFeRD, ox=Order-X, da=Cross Industry Despatch Advice)", "fx", + "fx|zf|ox|da"); } catch (Exception e) { LOGGER.error(e.getMessage(), e); } @@ -642,7 +659,7 @@ public class Main { if (zfVersion == null) { try { - zfVersion = getStringFromUser("Version (1 or 2)", "1", "1|2");//fx default version is 1 + zfVersion = getStringFromUser("Version (1 or 2)", "1", "1|2");// fx default version is 1 } catch (Exception e) { LOGGER.error(e.getMessage(), e); } @@ -659,8 +676,10 @@ public class Main { zfProfile = getStringFromUser("Profile (p)ilot", "p", "P|p"); } else { zfProfile = getStringFromUser( - "Profile [M]INIMUM, BASIC [W]L, [B]ASIC,\n" + "[C]IUS, [E]N16931, EX[T]ENDED or [X]RECHNUNG", "E", - "M|m|W|w|B|b|C|c|E|e|T|t|X|x|"); + "Profile [M]INIMUM, BASIC [W]L, [B]ASIC,\n" + + "[C]IUS, [E]N16931, EX[T]ENDED or [X]RECHNUNG", + "E", + "M|m|W|w|B|b|C|c|E|e|T|t|X|x|"); } } catch (Exception e) { LOGGER.error(e.getMessage(), e); @@ -670,7 +689,6 @@ public class Main { } zfProfile = zfProfile.toLowerCase(); - // Verify params ensureFileExists(pdfName); ensureFileExists(xmlName); @@ -712,6 +730,8 @@ public class Main { zfConformanceLevelProfile = Profiles.getByName(standard, "CIUS", zfIntVersion); } else if (zfProfile.equals("e")) { zfConformanceLevelProfile = Profiles.getByName(standard, "EN16931", zfIntVersion); + } else if (zfProfile.equals("f")) { + zfConformanceLevelProfile = Profiles.getByName(standard, "EXTENDED-CTC-FR", zfIntVersion); } else if (zfProfile.equals("t")) { zfConformanceLevelProfile = Profiles.getByName(standard, "EXTENDED", zfIntVersion); } else if (zfProfile.equals("x")) { @@ -738,7 +758,8 @@ public class Main { } else { if (format.equals("fx")) { zfIntVersion = 2;// actually we are talking of generation, not version - // so even if someone correctly requested factur-x 1 internally we call it zugferd 2 :-( + // so even if someone correctly requested factur-x 1 internally we call it + // zugferd 2 :-( } ze = new ZUGFeRDExporterFromPDFA(); if (ignoreInputErrors) { @@ -748,16 +769,16 @@ public class Main { ze.load(pdfName); ze.setProducer("Mustang-cli") - .setZUGFeRDVersion(zfIntVersion) - .setCreator(System.getProperty("user.name")).setProfile(zfConformanceLevelProfile); + .setZUGFeRDVersion(zfIntVersion) + .setCreator(System.getProperty("user.name")).setProfile(zfConformanceLevelProfile); if (format.equals("zf")) { ze.disableFacturX(); } - for (FileAttachment attachment : attachments) { - ze.attachFile(attachment.getFilename(), attachment.getData(), attachment.getMimetype(), attachment.getRelation()); + ze.attachFile(attachment.getFilename(), attachment.getData(), attachment.getMimetype(), + attachment.getRelation()); } ze.setXML(Files.readAllBytes(Paths.get(xmlName))); @@ -774,7 +795,7 @@ public class Main { } private static void performMetrics(String directoryName, Boolean filesFromStdIn, Boolean ignoreFileExt) - throws IOException { + throws IOException { StatRun sr = new StatRun(); if (ignoreFileExt) { @@ -818,16 +839,14 @@ public class Main { } else { System.out.println("ZUGFeRD XML source set to " + sourceName); } - if (!intoPDF) { - if (lang == null) { - try { - lang = getStringFromUser("Output language", "en", "en|de|fr"); - } catch (Exception e) { - LOGGER.error(e.getMessage(), e); - } - } else { - System.out.println("Output language set to " + lang); + if (lang == null) { + try { + lang = getStringFromUser("Output language", "en", "en|de|fr"); + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); } + } else { + System.out.println("Output language set to " + lang); } if (outName == null) { @@ -855,18 +874,18 @@ public class Main { ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer(); String xml = null; try { + ZUGFeRDVisualizer.Language langCode = ZUGFeRDVisualizer.Language.EN; + if (lang.equalsIgnoreCase("de")) { + langCode = ZUGFeRDVisualizer.Language.DE; + } + if (lang.equalsIgnoreCase("fr")) { + langCode = ZUGFeRDVisualizer.Language.FR; + } if (!intoPDF) { - ZUGFeRDVisualizer.Language langCode = ZUGFeRDVisualizer.Language.EN; - if (lang.equalsIgnoreCase("de")) { - langCode = ZUGFeRDVisualizer.Language.DE; - } - if (lang.equalsIgnoreCase("fr")) { - langCode = ZUGFeRDVisualizer.Language.FR; - } xml = zvi.visualize(sourceName, langCode); Files.write(Paths.get(outName), xml.getBytes()); } else { - zvi.toPDF(sourceName, outName); + zvi.toPDF(sourceName, outName, langCode); } } catch (Exception e) { LOGGER.error(e.getMessage(), e); @@ -880,13 +899,13 @@ public class Main { ExportResource("/xrechnung-viewer.css"); ExportResource("/xrechnung-viewer.js"); - System.out.println("xrechnung-viewer.css and xrechnung-viewer.js written as well (to local working dir)"); + System.out + .println("xrechnung-viewer.css and xrechnung-viewer.js written as well (to local working dir)"); } catch (Exception e) { LOGGER.error(e.getMessage(), e); } } - } /** @@ -894,11 +913,14 @@ public class Main { * * @param resourceName ie.: "/SmartLibrary.dll" * @return The path to the exported resource - * @throws Exception e.g. if the specified resource does not exist at the specified location + * @throws Exception e.g. if the specified resource does not exist at the + * specified location */ static public String ExportResource(String resourceName) throws Exception { String jarFolder; - try (InputStream stream = Main.class.getResourceAsStream(resourceName)) {//note that each / is a directory down in the "jar tree" been the jar the root of the tree + try (InputStream stream = Main.class.getResourceAsStream(resourceName)) {// note that each / is a directory down + // in the "jar tree" been the jar + // the root of the tree if (stream == null) { throw new Exception("Cannot get resource \"" + resourceName + "\" from Jar file."); } @@ -907,9 +929,9 @@ public class Main { byte[] buffer = new byte[4096]; jarFolder = System.getProperty("user.dir"); try (FileOutputStream resStreamOut = new FileOutputStream(jarFolder + resourceName)) { - while ((readBytes = stream.read(buffer)) > 0) { - resStreamOut.write(buffer, 0, readBytes); - } + while ((readBytes = stream.read(buffer)) > 0) { + resStreamOut.write(buffer, 0, readBytes); + } } } diff --git a/library/src/main/java/org/mustangproject/CashDiscount.java b/library/src/main/java/org/mustangproject/CashDiscount.java index b1993a02..d27b3dcb 100644 --- a/library/src/main/java/org/mustangproject/CashDiscount.java +++ b/library/src/main/java/org/mustangproject/CashDiscount.java @@ -21,7 +21,9 @@ public class CashDiscount implements IZUGFeRDCashDiscount { /*** * the period (usually days) count how long the percent apply */ - protected Integer days=null; + protected Integer days; + + protected BigDecimal basisAmount; /*** * Create a cash discount (skonto) with the specified height in the specified period. @@ -61,18 +63,31 @@ public class CashDiscount implements IZUGFeRDCashDiscount { return this; } + public BigDecimal getBasisAmount() { + return basisAmount; + } + + public CashDiscount setBasisAmount(BigDecimal basisAmount) { + this.basisAmount = basisAmount; + return this; + } + /*** * @return this particular cash discount as cross industry invoice XML */ @JsonIgnore public String getAsCII() { - return ""+ - "Cash Discount"+ - " "+ - " "+days+""+ - " "+XMLTools.nDigitFormat(percent,3)+""+ - " "+ - ""; + String s = "" + + " Cash Discount" + + " " + + " " + days + ""; + if (basisAmount != null) { + s += " " + XMLTools.nDigitFormat(basisAmount, 2) + ""; + } + s += " "+XMLTools.nDigitFormat(percent,3)+""+ + " "+ + ""; + return s; } /*** @@ -84,8 +99,4 @@ public class CashDiscount implements IZUGFeRDCashDiscount { public String getAsXRechnung() { return "#SKONTO#TAGE="+days+"#PROZENT="+XMLTools.nDigitFormat(percent,2)+"#\n"; } - - - - } diff --git a/library/src/main/java/org/mustangproject/Invoice.java b/library/src/main/java/org/mustangproject/Invoice.java index 60de0622..cbc00060 100644 --- a/library/src/main/java/org/mustangproject/Invoice.java +++ b/library/src/main/java/org/mustangproject/Invoice.java @@ -39,6 +39,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @JsonInclude(JsonInclude.Include.NON_EMPTY) public class Invoice implements IExportableTransaction { + protected boolean testIndicator; 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 TradeParty sender = null, recipient = null, deliveryAddress = null, payee = null, invoicer = null, invoicee = null; @@ -75,13 +76,23 @@ public class Invoice implements IExportableTransaction { protected String creditorReferenceID; // required when direct debit is used. private BigDecimal roundingAmount=null; private String paymentReference; // Remittance information / Verwendungszweck, BT-83 - + private String businessProcessId; public Invoice() { ZFItems = new ArrayList<>(); cashDiscounts = new ArrayList<>(); setCurrency("EUR"); } + @Override + public boolean getTestIndicator() { + return testIndicator; + } + + public Invoice setTestIndicator() { + this.testIndicator = true; + return this; + } + @Override public String getDocumentName() { return documentName; @@ -197,7 +208,18 @@ public class Invoice implements IExportableTransaction { setObjectIdentifierReferencedDocument(dr); return this; } - + public Invoice setObjectIdentifierReferencedDocument(String id, String referenceTypeCode) { + ReferencedDocument dr = new ReferencedDocument(id); + dr.setReferenceTypeCode(referenceTypeCode); + return setObjectIdentifierReferencedDocument(dr); + } + + public Invoice setObjectIdentifierReferencedDocument(String id, String referenceTypeCode, Date issueDate) { + ReferencedDocument dr = new ReferencedDocument(id); + dr.setReferenceTypeCode(referenceTypeCode); + dr.setFormattedIssueDateTime(issueDate); + return setObjectIdentifierReferencedDocument(dr); + } public Invoice setNumber(String number) { this.number = number; @@ -1156,4 +1178,14 @@ public class Invoice implements IExportableTransaction { return this; } + public Invoice setBusinessProcessId(String id) { + this.businessProcessId = id; + return this; + } + + @Override + public String getBusinessProcessId() { + return businessProcessId; + } + } diff --git a/library/src/main/java/org/mustangproject/XMLTools.java b/library/src/main/java/org/mustangproject/XMLTools.java index 5905fdae..e7d09fca 100644 --- a/library/src/main/java/org/mustangproject/XMLTools.java +++ b/library/src/main/java/org/mustangproject/XMLTools.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.math.RoundingMode; +import java.net.URL; import java.text.SimpleDateFormat; import java.util.Date; @@ -11,6 +12,19 @@ import org.apache.commons.io.IOUtils; import org.dom4j.io.XMLWriter; import org.mustangproject.ZUGFeRD.ZUGFeRDDateFormat; import org.w3c.dom.Node; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; + +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerFactory; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; public class XMLTools extends XMLWriter { @Override @@ -23,6 +37,86 @@ public class XMLTools extends XMLWriter { return super.escapeElementEntities(s); } + public static DocumentBuilder getDocumentBuilder(boolean namespaceAware) throws ParserConfigurationException { + 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 + try { + dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + } catch (ParserConfigurationException e) { + // ignore + } + try { + dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + } catch (IllegalArgumentException e) { + // ignore + } + try { + dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + } catch (IllegalArgumentException e) { + // ignore + } + + //OWASP + //https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + try { + dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + } catch (ParserConfigurationException e) { + // ignore + } + try { + dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); + } catch (ParserConfigurationException e) { + // ignore + } + try { + dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + } catch (ParserConfigurationException e) { + // ignore + } + try { + // Disable external DTDs as well + dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + } catch (ParserConfigurationException e) { + // ignore + } + // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks" + dbf.setXIncludeAware(false); + dbf.setExpandEntityReferences(false); + dbf.setNamespaceAware(namespaceAware); + return dbf.newDocumentBuilder(); + } + + public static Validator getValidator(URL schemaFile) throws SAXException + { + SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + try { + schemaFactory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + } catch (SAXNotSupportedException | SAXNotRecognizedException e) { + // ignore + } + Schema schema = schemaFactory.newSchema(schemaFile); + + Validator validator = schema.newValidator(); + try { + validator.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + } catch (SAXNotSupportedException | SAXNotRecognizedException e) { + // ignore + } + return validator; + } + + public static TransformerFactory getTransformerFactory() + { + TransformerFactory factory = new net.sf.saxon.TransformerFactoryImpl(); + try { + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + } catch (TransformerConfigurationException e) { + // ignore + } + return factory; + } + public static String nDigitFormat(BigDecimal value, int scale) { /* * I needed 123.45, locale independent.I tried diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/CustomXMLProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/CustomXMLProvider.java index 45f79f2f..211b0103 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/CustomXMLProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/CustomXMLProvider.java @@ -49,12 +49,6 @@ public class CustomXMLProvider implements IXMLProvider { } - @Override - public void setTest() { - // TODO Auto-generated method stub - - } - @Override public Profile getProfile() { return profile; diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/DXExporterFromA1.java b/library/src/main/java/org/mustangproject/ZUGFeRD/DXExporterFromA1.java index f22d76c7..b94a763a 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/DXExporterFromA1.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/DXExporterFromA1.java @@ -41,28 +41,6 @@ public class DXExporterFromA1 extends DXExporterFromA3 { private static boolean isValidA1(DataSource dataSource) throws IOException { return getPDFAParserValidationResult(PreflightParserHelper.createPreflightParser (dataSource)); } - /*** - * internal helper function: get namespace for order-x - * @param ver the delivery-x version - * @return the URN of the namespace - */ - @Override - public String getNamespaceForVersion(int ver) { - // As of late 2022 the Delivery-X standard is not yet published. See specification: - // Die digitale Ablösung des Papier-Lieferscheins, Version 1.1, April 2022 - // Chapter 7.1 XMP-Erweiterungsschema für PDF/A-3 - // http://docplayer.org/230301085-Der-digitale-lieferschein-dls.html - return "urn:factur-x:pdfa:CrossIndustryDocument:despatchadvice:1p0#"; - } - /*** - * internal helper: returns the namespace prefix for the given order-x version number - * @param ver the ox version - * @return the namespace prefix as string, without colon - */ - @Override - public String getPrefixForVersion(int ver) { - return "fx"; - } private static boolean getPDFAParserValidationResult(PreflightParser parser) throws IOException { /* @@ -136,6 +114,7 @@ public class DXExporterFromA1 extends DXExporterFromA3 { public DXExporterFromA1 setConformanceLevel(PDFAConformanceLevel newLevel) { return (DXExporterFromA1) super.setConformanceLevel(newLevel); } + @Override public DXExporterFromA1 setProducer(String producer){ return (DXExporterFromA1) super.setProducer(producer); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/DXExporterFromA3.java b/library/src/main/java/org/mustangproject/ZUGFeRD/DXExporterFromA3.java index 3324e36c..9b6a95cf 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/DXExporterFromA3.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/DXExporterFromA3.java @@ -71,6 +71,7 @@ import jakarta.activation.FileDataSource; public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 { protected PDFAConformanceLevel conformanceLevel = PDFAConformanceLevel.UNICODE; + protected boolean compressionEnabled = false; protected ArrayList fileAttachments = new ArrayList<>(); /** @@ -129,6 +130,29 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 { private boolean attachZUGFeRDHeaders = true; + /*** + * internal helper function: get namespace for order-x + * @param ver the delivery-x version + * @return the URN of the namespace + */ + @Override + public String getNamespaceForVersion(int ver) { + // As of late 2022 the Delivery-X standard is not yet published. See specification: + // Die digitale Ablösung des Papier-Lieferscheins, Version 1.1, April 2022 + // Chapter 7.1 XMP-Erweiterungsschema für PDF/A-3 + // http://docplayer.org/230301085-Der-digitale-lieferschein-dls.html + return "urn:factur-x:pdfa:CrossIndustryDocument:despatchadvice:1p0#"; + } + /*** + * internal helper: returns the namespace prefix for the given order-x version number + * @param ver the ox version + * @return the namespace prefix as string, without colon + */ + @Override + public String getPrefixForVersion(int ver) { + return "fx"; + } + /** * Makes A PDF/A3a-compliant document from a PDF-A1 compliant document (on the * metadata level, this will not e.g. convert graphics to JPG-2000) @@ -296,8 +320,8 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 { dict.setString("Desc", description); ByteArrayInputStream fakeFile = new ByteArrayInputStream(data); - PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile); -// ef.addCompression(); + COSName filter = compressionEnabled ? COSName.FLATE_DECODE : null; + PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile, filter); ef.setSubtype(subType); ef.setSize(data.length); ef.setCreationDate(new GregorianCalendar()); @@ -417,7 +441,6 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 { return this; } - @Override public DXExporterFromA3 setCreator(String creator) { this.creator = creator; diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java index 85eca73a..f3e87aab 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IExportableTransaction.java @@ -38,7 +38,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import org.mustangproject.FileAttachment; import org.mustangproject.IncludedNote; import org.mustangproject.ReferencedDocument; -import org.mustangproject.TradeParty; import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants; /*** @@ -49,6 +48,11 @@ import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants; */ public interface IExportableTransaction { + @JsonIgnore + default boolean getTestIndicator() { + return false; + } + /** * appears in /rsm:CrossIndustryDocument/rsm:HeaderExchangedDocument/ram:Name * @@ -626,4 +630,16 @@ public interface IExportableTransaction { default String getCreditorReferenceID() { return null; } + + /** + * BT-23 Business process identifier + * /rsm:CrossIndustryInvoice/rsm:ExchangedDocumentContext/ + * ram:BusinessProcessSpecifiedDocumentContextParameter/ram:ID + * + * @return business process ID (e.g. "B1" or a URN) or null if not provided + */ + default String getBusinessProcessId() { + return null; + } + } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IXMLProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IXMLProvider.java index b6136f37..24cc7607 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IXMLProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IXMLProvider.java @@ -22,13 +22,9 @@ public interface IXMLProvider { public byte[] getXML(); - public void setTest(); - public void generateXML(IExportableTransaction trans); public void setProfile(Profile p); public Profile getProfile(); - - } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExporter.java index fd4ffb4c..ca84cc26 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExporter.java @@ -56,6 +56,7 @@ public interface IZUGFeRDExporter extends Closeable, IExporter { public IZUGFeRDExporter load(InputStream pdfSource) throws IOException; public IZUGFeRDExporter setCreator(String creator); public IZUGFeRDExporter setConformanceLevel(PDFAConformanceLevel newLevel); + public IZUGFeRDExporter setEnablePDFAttachmentCompression(boolean enablePDFAttachmentCompression); public IZUGFeRDExporter setProducer(String producer); public IZUGFeRDExporter setZUGFeRDVersion(int version); public boolean ensurePDFIsValid(final DataSource dataSource) throws IOException; diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/OXExporterFromA1.java b/library/src/main/java/org/mustangproject/ZUGFeRD/OXExporterFromA1.java index 33f95aea..6b4b7e6c 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/OXExporterFromA1.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/OXExporterFromA1.java @@ -41,24 +41,6 @@ public class OXExporterFromA1 extends OXExporterFromA3 { private static boolean isValidA1(DataSource dataSource) throws IOException { return getPDFAParserValidationResult(PreflightParserHelper.createPreflightParser(dataSource)); } - /*** - * internal helper function: get namespace for order-x - * @param ver the order-x version - * @return the URN of the namespace - */ - @Override - public String getNamespaceForVersion(int ver) { - return "urn:factur-x:pdfa:CrossIndustryDocument:1p0#"; - } - /*** - * internal helper: returns the namespace prefix for the given order-x version number - * @param ver the ox version - * @return the namespace prefix as string, without colon - */ - @Override - public String getPrefixForVersion(int ver) { - return "fx"; - } private static boolean getPDFAParserValidationResult(PreflightParser parser) throws IOException { /* diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/OXExporterFromA3.java b/library/src/main/java/org/mustangproject/ZUGFeRD/OXExporterFromA3.java index 5db1a693..a38eec1c 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/OXExporterFromA3.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/OXExporterFromA3.java @@ -126,9 +126,30 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 { */ protected String orderXDocumentType = "ORDER"; - private boolean attachZUGFeRDHeaders = true; + /*** + * internal helper function: get namespace for order-x + * @param ver the order-x version + * @return the URN of the namespace + */ + @Override + public String getNamespaceForVersion(int ver) { + return "urn:factur-x:pdfa:CrossIndustryDocument:1p0#"; + } + /*** + * internal helper: returns the namespace prefix for the given order-x version number + * @param ver the ox version + * @return the namespace prefix as string, without colon + */ + @Override + public String getPrefixForVersion(int ver) { + return "fx"; + } + + /** Defines whether attachments to the PDF should be using FLATE compression */ + private boolean compressionEnabled = false; + /** * Makes A PDF/A3a-compliant document from a PDF-A1 compliant document (on the * metadata level, this will not e.g. convert graphics to JPG-2000) @@ -296,8 +317,8 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 { dict.setString("Desc", description); ByteArrayInputStream fakeFile = new ByteArrayInputStream(data); - PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile); -// ef.addCompression(); + COSName filter = compressionEnabled ? COSName.FLATE_DECODE : null; + PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile, filter); ef.setSubtype(subType); ef.setSize(data.length); ef.setCreationDate(new GregorianCalendar()); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/Profiles.java b/library/src/main/java/org/mustangproject/ZUGFeRD/Profiles.java index 3d73e7ac..9adae572 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/Profiles.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/Profiles.java @@ -32,6 +32,7 @@ public class Profiles { {"BASICWL", new Profile("BASICWL", "urn:factur-x.eu:1p0:basicwl")}, {"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-CTC-FR", new Profile("EXTENDED-CTC-FR", "urn:cen.eu:en16931:2017#conformant#urn.cpro.gouv.fr:1p0:extended-ctc-fr")}, {"EXTENDED", new Profile("EXTENDED", "urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended")}, {"XRECHNUNG", new Profile("XRECHNUNG", "urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0")} // up next: urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0 diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/UBLDAPullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/UBLDAPullProvider.java index 3e39c51b..15e52edf 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/UBLDAPullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/UBLDAPullProvider.java @@ -136,11 +136,6 @@ public class UBLDAPullProvider implements IXMLProvider { } - @Override - public void setTest() { - - } - @Override public void setProfile(Profile p) { profile = p; @@ -150,5 +145,4 @@ public class UBLDAPullProvider implements IXMLProvider { public Profile getProfile() { return profile; } - } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ValidationLogVisualizer.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ValidationLogVisualizer.java index 17c3e1ea..53218d70 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ValidationLogVisualizer.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ValidationLogVisualizer.java @@ -15,6 +15,7 @@ import org.apache.fop.configuration.ConfigurationException; import org.apache.fop.configuration.DefaultConfigurationBuilder; import org.apache.xmlgraphics.util.MimeConstants; import org.mustangproject.ClasspathResolverURIAdapter; +import org.mustangproject.XMLTools; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,8 +43,7 @@ public class ValidationLogVisualizer { public ValidationLogVisualizer() { - mFactory = new net.sf.saxon.TransformerFactoryImpl(); - // fact = TransformerFactory.newInstance(); + mFactory = XMLTools.getTransformerFactory(); mFactory.setURIResolver(new ValidationLogVisualizer.ClasspathResourceURIResolver()); } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/XMLUpgrader.java b/library/src/main/java/org/mustangproject/ZUGFeRD/XMLUpgrader.java index cf71f2b7..4153938f 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/XMLUpgrader.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/XMLUpgrader.java @@ -16,6 +16,8 @@ import javax.xml.transform.URIResolver; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; +import org.mustangproject.XMLTools; + /*** * Uses a XSLT transformation to upgrade @@ -31,8 +33,7 @@ public class XMLUpgrader { private Templates mXsltTemplate = null; public XMLUpgrader() { - mFactory = new net.sf.saxon.TransformerFactoryImpl(); - //fact = TransformerFactory.newInstance(); + mFactory = XMLTools.getTransformerFactory(); mFactory.setURIResolver(new ClasspathResourceURIResolver()); } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java index 91901a19..dbe8b69c 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java @@ -49,14 +49,6 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider { private String paymentTermsDescription; protected Profile profile = Profiles.getByName("COMFORT", 1); - - /** - * enables the flag to indicate a test invoice in the XML structure - */ - @Override - public void setTest() { - } - @Override protected String vatFormat(BigDecimal value) { return XMLTools.nDigitFormat(value, 2); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index d7aab617..1dba311c 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -35,7 +35,6 @@ import java.util.Date; import java.util.List; import java.util.Map.Entry; import java.util.Optional; -import java.util.Set; import java.util.stream.Collectors; import org.dom4j.Document; @@ -61,14 +60,6 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { private String paymentTermsDescription; protected Profile profile = Profiles.getByName("EN16931"); - - /** - * enables the flag to indicate a test invoice in the XML structure - */ - @Override - public void setTest() { - } - protected String vatFormat(BigDecimal value) { return XMLTools.nDigitFormat(value, 2); } @@ -384,11 +375,17 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { + " xmlns:qdt=\"urn:un:unece:uncefact:data:standard:QualifiedDataType:100\">" + "" + "\n"; - // + " - // "+testBooleanStr+"" - // - if (getProfile() == Profiles.getByName("XRechnung")) { + if (trans.getTestIndicator() && profile == Profiles.getByName("Extended")) { + xml += "" + trans.getTestIndicator() + ""; + } + + String businessProcessId = trans.getBusinessProcessId(); + if (businessProcessId != null && !businessProcessId.isBlank()) { + xml += "\n" + + "" + XMLTools.encodeXML(businessProcessId) + "\n" + + "\n"; + }else if (getProfile() == Profiles.getByName("XRechnung")) { xml += "\n" + "urn:fdc:peppol.eu:2017:poacc:billing:01:1.0\n" + "\n"; @@ -419,8 +416,16 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { if ((getProfile() != Profiles.getByName("Minimum")) && (getProfile() != Profiles.getByName("BasicWL"))) { xml += "" + "" - + "" + lineIDStr + "" - + buildItemNotes(currentItem) + + "" + lineIDStr + ""; + if (getProfile() == Profiles.getByName("Extended")) { + if (currentItem.getParentLineID() != null) { + xml += "" + XMLTools.encodeXML(currentItem.getParentLineID()) + ""; + } + if (currentItem.getLineStatusReasonCode() != null) { + xml += "" + XMLTools.encodeXML(currentItem.getLineStatusReasonCode()) + ""; + } + } + xml += buildItemNotes(currentItem) + "" + ""; @@ -550,15 +555,16 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { xml += "" + "" - + "" - + "VAT"; + + ""; + // + xml += "VAT"; if (currentItem.getProduct().getTaxExemptionReason() != null) { xml += "" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + ""; } + xml += "" + currentItem.getProduct().getTaxCategoryCode() + ""; if (currentItem.getProduct().getTaxExemptionReasonCode() != null) { xml += "" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReasonCode()) + ""; } - xml += "" + currentItem.getProduct().getTaxCategoryCode() + ""; BigDecimal vatValue=BigDecimal.ZERO; if (currentItem.getProduct().getTaxCategoryCode().equals(TaxCategoryCodeTypeConstants.ZEROTAXPRODUCTS)) { vatValue=BigDecimal.ZERO; @@ -662,6 +668,11 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { xml += "" + "" + XMLTools.encodeXML(trans.getObjectIdentifierReferencedDocument().getIssuerAssignedID()) + "" + "130"; + // NEW: BT-18-1 scheme identifier + String rtc = trans.getObjectIdentifierReferencedDocument().getReferenceTypeCode(); + if (rtc != null && !rtc.isEmpty()) { + xml += "" + XMLTools.encodeXML(rtc) + ""; + } if (trans.getObjectIdentifierReferencedDocument().getFormattedIssueDateTime()!=null) { xml += "" + DATE.qdtFormat(trans.getObjectIdentifierReferencedDocument().getFormattedIssueDateTime()) + ""; } @@ -700,6 +711,12 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { + ""; } + if (trans.getDespatchAdviceReferencedDocumentID() != null && !trans.getDespatchAdviceReferencedDocumentID().trim().isEmpty()) { + xml += ""; + xml += "" + XMLTools.encodeXML(trans.getDespatchAdviceReferencedDocumentID()) + ""; + xml += ""; + } + if (trans.getDeliveryNoteReferencedDocumentID() != null && !trans.getDeliveryNoteReferencedDocumentID().trim().isEmpty()) { xml += ""; xml += "" + XMLTools.encodeXML(trans.getDeliveryNoteReferencedDocumentID()) + ""; @@ -708,14 +725,6 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { xml += ""+XMLTools.encodeXML(dateFormat102.format(trans.getDeliveryNoteReferencedDocumentDate()))+""; } xml += ""; - - } - - if (trans.getDespatchAdviceReferencedDocumentID() != null && !trans.getDespatchAdviceReferencedDocumentID().trim().isEmpty()) { - xml += ""; - xml += "" + XMLTools.encodeXML(trans.getDespatchAdviceReferencedDocumentID()) + ""; - xml += ""; - } xml += ""; @@ -811,8 +820,14 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { xml += "" + "" + "true" + - "" + - "" + currencyFormat(charge.getTotalAmount(calc)) + ""; + ""; + if (charge.getPercent() != null && (profile == Profiles.getByName("EN16931") || (profile == Profiles.getByName("EXTENDED")))) { + xml += "" + vatFormat(charge.getPercent()) + ""; + } + if (charge.getBasisAmount() != null && (profile == Profiles.getByName("EN16931") || (profile == Profiles.getByName("EXTENDED")))) { + xml += "" + currencyFormat(charge.getBasisAmount()) + ""; + } + xml += "" + currencyFormat(charge.getTotalAmount(calc)) + ""; if (charge.getReasonCode() != null) { xml += "" + charge.getReasonCode() + ""; } @@ -850,19 +865,25 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { } if ((trans.getZFAllowances() != null) && (trans.getZFAllowances().length > 0)) { - if (profile == Profiles.getByName("XRechnung")) { + if ((profile == Profiles.getByName("XRechnung")) || (profile == Profiles.getByName("EN16931")) || (profile == Profiles.getByName("EXTENDED"))) { for (IZUGFeRDAllowanceCharge allowance : trans.getZFAllowances()) { xml += "" + "" + "false" + - "" + - "" + currencyFormat(allowance.getTotalAmount(calc)) + ""; - if (allowance.getReason() != null) { - xml += "" + XMLTools.encodeXML(allowance.getReason()) + ""; + ""; + if (allowance.getPercent() != null) { + xml += "" + vatFormat(allowance.getPercent()) + ""; } + if (allowance.getBasisAmount() != null) { + xml += "" + currencyFormat(allowance.getBasisAmount()) + ""; + } + xml += "" + currencyFormat(allowance.getTotalAmount(calc)) + ""; if (allowance.getReasonCode() != null) { xml += "" + allowance.getReasonCode() + ""; } + if (allowance.getReason() != null) { + xml += "" + XMLTools.encodeXML(allowance.getReason()) + ""; + } xml += "" + "VAT" + "" + allowance.getCategoryCode() + ""; diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java index 0b59b7fd..5a232747 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java @@ -77,8 +77,9 @@ import jakarta.activation.DataSource; import jakarta.activation.FileDataSource; public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporter { - private boolean isFacturX = true; + private boolean isFacturX = true; + public static final int DefaultZUGFeRDVersion = 2; protected boolean ignorePDFAErrors = false; @@ -101,6 +102,9 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte private Profile profile = null; protected boolean documentPrepared = false; + /** Defines whether attachments to the PDF should be using FLATE compression */ + private boolean compressionEnabled = false; + /** * Data (XML invoice) to be added to the ZUGFeRD PDF. It may be externally set, * in which case passing a IZUGFeRDExportableTransaction is not necessary. By @@ -392,7 +396,8 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte dict.setString("Desc", description); ByteArrayInputStream fakeFile = new ByteArrayInputStream(data); - PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile); + COSName filter = compressionEnabled ? COSName.FLATE_DECODE : null; + PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile, filter); // ef.addCompression(); ef.setSubtype(subType); ef.setSize(data.length); @@ -503,6 +508,12 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte return this; } + @Override + public IZUGFeRDExporter setEnablePDFAttachmentCompression(boolean compressionEnabled) { + this.compressionEnabled = compressionEnabled; + return this; + } + public ZUGFeRDExporterFromA3 setCreator(String creator) { this.creator = creator; diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromPDFA.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromPDFA.java index 12653cdf..c9519fb5 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromPDFA.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromPDFA.java @@ -188,6 +188,10 @@ public class ZUGFeRDExporterFromPDFA implements IZUGFeRDExporter { return getExporter().setConformanceLevel(newLevel); } + public IZUGFeRDExporter setEnablePDFAttachmentCompression(boolean compressionEnabled) { + return getExporter().setEnablePDFAttachmentCompression(compressionEnabled); + } + public IZUGFeRDExporter setProducer(String producer) { return getExporter().setProducer(producer); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java index e05a8652..f99dfff8 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java @@ -19,6 +19,8 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; @@ -29,6 +31,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRDImporter.class); @@ -472,17 +475,6 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { } - /** - * @return raw XML of the invoice - */ - public String getMeta() { - if (rawXML == null) { - return null; - } - - return new String(rawXML, StandardCharsets.UTF_8); - } - public int getVersion() throws Exception { if (!containsMeta) { @@ -517,20 +509,6 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter { } - /** - * will return true if the metadata (just extract-ed or set with setMeta) contains ZUGFeRD XML - * - * @return true if the invoice contains ZUGFeRD XML - */ - public boolean canParse() { - - // SpecifiedExchangedDocumentContext is in the schema, so a relatively good - // indication if zugferd is present - better than just invoice - final String meta = getMeta(); - return (meta != null) && (meta.length() > 0) && ((meta.contains("SpecifiedExchangedDocumentContext") - /* ZF1 */ || meta.contains("ExchangedDocumentContext") /* ZF2 */)); - } - /** * returns an instance of PostalTradeAddress for SellerTradeParty section diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java index 6bbfbf9c..3e594830 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDInvoiceImporter.java @@ -18,9 +18,7 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; -import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; @@ -48,6 +46,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -311,36 +310,62 @@ public class ZUGFeRDInvoiceImporter { setRawXML(rawXML, true); } - private void setDocument() throws ParserConfigurationException, IOException, SAXException, ParseException { - 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.setFeature(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(); + /** + * @return raw XML of the invoice + */ + public String getMeta() { + if (rawXML == null) { + return null; + } + + return new String(rawXML, StandardCharsets.UTF_8); + } + + /** + * will return true if the metadata (just extract-ed or set with setMeta) contains ZUGFeRD XML + * + * @return true if the invoice contains ZUGFeRD XML + */ + public boolean canParse() { + + // SpecifiedExchangedDocumentContext is in the schema, so a relatively good + // indication if zugferd is present - better than just invoice + final String meta = getMeta(); + if ((meta == null) || (meta.length() == 0)) { + return false; + } + final ByteArrayInputStream is = new ByteArrayInputStream(rawXML); /// is.skip(guessBOMSize(is)); - document = builder.parse(is); - if (parseAutomatically) { - try { - importedInvoice = new CalculatedInvoice(); - extractInto(importedInvoice); - } catch (XPathExpressionException e) { - throw new RuntimeException(e); + try { + DocumentBuilder builder = XMLTools.getDocumentBuilder(true); + document = builder.parse(is); + + } catch (Exception e) { + return false; + } + return ((meta.contains("SpecifiedExchangedDocumentContext") + /* ZF1 */ || meta.contains("ExchangedDocumentContext") /* ZF2 */)); + } + + private void setDocument() throws ParserConfigurationException, IOException, SAXException, ParseException { + + + final ByteArrayInputStream is = new ByteArrayInputStream(rawXML); + /// is.skip(guessBOMSize(is)); + DocumentBuilder builder = XMLTools.getDocumentBuilder(true); + if (canParse()) { + document = builder.parse(is); + if (parseAutomatically) { + try { + importedInvoice = new CalculatedInvoice(); + extractInto(importedInvoice); + } catch (XPathExpressionException e) { + throw new RuntimeException(e); + } } + } } @@ -472,7 +497,7 @@ public class ZUGFeRDInvoiceImporter { xpr = xpath.compile("//*[local-name()=\"ExchangedDocument\"]|//*[local-name()=\"HeaderExchangedDocument\"]"); NodeList ExchangedDocumentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); - xpr = xpath.compile("//*[local-name()=\"GrandTotalAmount\"]|//*[local-name()=\"TaxInclusiveAmount\"]"); + xpr = xpath.compile("//*[local-name()=\"SpecifiedTradeSettlementMonetarySummation\"]/*[local-name()=\"GrandTotalAmount\"]|//*[local-name()=\"SpecifiedTradeSettlementHeaderMonetarySummation\"]/*[local-name()=\"GrandTotalAmount\"]|//*[local-name()=\"LegalMonetaryTotal\"]/*[local-name()=\"TaxInclusiveAmount\"]"); BigDecimal expectedGrandTotal = null; NodeList totalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); if (totalNodes.getLength() > 0) { @@ -485,7 +510,7 @@ public class ZUGFeRDInvoiceImporter { } } - xpr = xpath.compile("//*[local-name()=\"TaxBasisTotalAmount\"]|//*[local-name()=\"TaxExclusiveAmount\"]"); + xpr = xpath.compile("//*[local-name()=\"SpecifiedTradeSettlementMonetarySummation\"]/*[local-name()=\"TaxBasisTotalAmount\"]|//*[local-name()=\"SpecifiedTradeSettlementHeaderMonetarySummation\"]/*[local-name()=\"TaxBasisTotalAmount\"]|//*[local-name()=\"LegalMonetaryTotal\"]/*[local-name()=\"TaxExclusiveAmount\"]"); BigDecimal expectedTaxBasis = null; NodeList basisNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); if (basisNodes.getLength() > 0) { @@ -628,14 +653,8 @@ public class ZUGFeRDInvoiceImporter { } String tenderReference = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"OriginatorDocumentReference\"]/*[local-name()=\"ID\"]").trim(); - String tenderReferenceDate = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"OriginatorDocumentReference\"]/*[local-name()=\"ID\"]").trim(); if((tenderReference != null)&&(!tenderReference.isEmpty())){ - if((tenderReferenceDate != null)&&(!tenderReferenceDate.isEmpty())){ - zpp.setTenderReferencedDocument(new ReferencedDocument(tenderReference, parseDate(tenderReferenceDate, "yyyy-MM-dd"))); - } else { - zpp.setTenderReferencedDocument(tenderReference); - } - + zpp.setTenderReferencedDocument(tenderReference); } String dueDt = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"DueDate\"] | /*[local-name()=\"CreditNote\"]/*[local-name()=\"PaymentMeans\"]/*[local-name()=\"PaymentDueDate\"]").trim(); @@ -1275,6 +1294,25 @@ public class ZUGFeRDInvoiceImporter { //appliedAmount //AppliedTradeTax } + + if (cd.getDays() == null) { + NodeList siblingNodes = cashdiscountNodes.item(i).getParentNode().getChildNodes(); + for (int siblingIndex = 0; siblingIndex < siblingNodes.getLength(); siblingIndex++) { + if (siblingNodes.item(siblingIndex).getLocalName() != null && siblingNodes.item(siblingIndex).getLocalName().equals("DueDateDateTime")) { + NodeList dueDateChilds = siblingNodes.item(siblingIndex).getChildNodes(); + for (int dueDateChildIndex = 0; dueDateChildIndex < dueDateChilds.getLength(); dueDateChildIndex++) { + if ((dueDateChilds.item(dueDateChildIndex).getLocalName() != null) && (dueDateChilds.item(dueDateChildIndex).getLocalName().equals("DateTimeString"))) { + String dueDateString = XMLTools.trimOrNull(dueDateChilds.item(dueDateChildIndex)); + dueDate = parseDate(dueDateString, "yyyyMMdd"); + long diffInMillies = Math.abs(dueDate.getTime() - zpp.getIssueDate().getTime()); + long diffInDays = TimeUnit.DAYS.convert(diffInMillies, TimeUnit.MILLISECONDS); + cd.setDays((int) diffInDays); + } + } + } + } + } + if ((cd.getPercent() != null)&&(cd.getDays() != null)) { zpp.addCashDiscount(cd); } @@ -1331,6 +1369,9 @@ public class ZUGFeRDInvoiceImporter { + Stream.of(tc.trans.getZFItems()) .map(item -> item.getCalculation().getItemTotalNetAmount().toPlainString()) .collect(Collectors.joining(" + ")); + if (tc.trans.getRoundingAmount()!=null) { + moreDetails += " and rounding amount "+tc.trans.getRoundingAmount().toPlainString(); + } } catch (Exception ignored) { } throw new ArithmeticException("Payable total in XML is " + payableTotalFromXml + ", but calculated total is " + calculatedPayableTotal + moreDetails); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDVisualizer.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDVisualizer.java index e9170df7..357a3b87 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDVisualizer.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDVisualizer.java @@ -32,6 +32,7 @@ import org.apache.fop.configuration.DefaultConfigurationBuilder; import org.apache.xmlgraphics.util.MimeConstants; import org.mustangproject.ClasspathResolverURIAdapter; import org.mustangproject.EStandard; +import org.mustangproject.XMLTools; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -39,7 +40,6 @@ import org.w3c.dom.Element; import org.xml.sax.InputSource; import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.*; import javax.xml.transform.sax.SAXResult; import javax.xml.transform.stream.StreamResult; @@ -47,8 +47,6 @@ import javax.xml.transform.stream.StreamSource; import java.io.*; import java.nio.charset.StandardCharsets; import java.util.EnumMap; -import java.util.HashMap; -import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; @@ -85,8 +83,7 @@ public class ZUGFeRDVisualizer { private Templates mXsltZF1HTMLTemplate = null; public ZUGFeRDVisualizer() { - mFactory = new net.sf.saxon.TransformerFactoryImpl(); - // fact = TransformerFactory.newInstance(); + mFactory = XMLTools.getTransformerFactory(); mFactory.setURIResolver(new ClasspathResourceURIResolver()); } @@ -104,40 +101,8 @@ public class ZUGFeRDVisualizer { String ublCreditNoteSignature = "CreditNote"; 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.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); - try - { - dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); - } - catch (IllegalArgumentException e) - { - LOGGER.warn("Property: \"Access external DTD\" not supported."); - } - try - { - dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); - } - catch (IllegalArgumentException e) - { - LOGGER.warn("Property: \"Access external schema\" not supported."); - } - - //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(); + DocumentBuilder db = XMLTools.getDocumentBuilder(true); Document doc = db.parse(new InputSource(fis)); Element root = doc.getDocumentElement(); if (root.getLocalName().equals(zf1Signature)) { @@ -252,7 +217,7 @@ public class ZUGFeRDVisualizer { } } - protected String toFOP(String xmlFilename) + protected String toFOP(String xmlFilename, Language lang) throws IOException, TransformerException, ParserConfigurationException { EStandard theStandard; try (FileInputStream fis = new FileInputStream(xmlFilename)) { @@ -260,11 +225,11 @@ public class ZUGFeRDVisualizer { } try (FileInputStream fis = new FileInputStream(xmlFilename)) { - return toFOP(fis, theStandard); + return toFOP(fis, theStandard, lang); } } - protected String toFOP(InputStream is, EStandard theStandard) + protected String toFOP(InputStream is, EStandard theStandard, Language lang) throws TransformerException, IOException { try { @@ -291,12 +256,16 @@ public class ZUGFeRDVisualizer { Optional in = copyStream(iaos); ByteArrayOutputStream baos = new ByteArrayOutputStream(); if (in.isPresent()) { - applyXSLTToPDF(in.get(), baos); + applyXSLTToPDF(in.get(), baos, lang); } return baos.toString(StandardCharsets.UTF_8); } public void toPDF(String xmlFilename, String pdfFilename) { + toPDF(xmlFilename, pdfFilename, Language.DE); + } + + public void toPDF(String xmlFilename, String pdfFilename, Language lang) { // the writing part File XMLinputFile = new File(xmlFilename); @@ -307,7 +276,7 @@ public class ZUGFeRDVisualizer { out from git with arbitrary options (which may include CSRF changes) */ try { - fopInput = this.toFOP(XMLinputFile.getAbsolutePath()); + fopInput = this.toFOP(XMLinputFile.getAbsolutePath(), lang); } catch (TransformerException | IOException | ParserConfigurationException e) { LOGGER.error("Failed to apply FOP", e); } @@ -321,8 +290,12 @@ public class ZUGFeRDVisualizer { return null; }, (OutputStream out) -> {}); } - + public byte[] toPDF(String xmlContent) { + return toPDF(xmlContent, Language.DE); + } + + public byte[] toPDF(String xmlContent, Language lang) { String fopInput = null; @@ -334,7 +307,7 @@ public class ZUGFeRDVisualizer { EStandard theStandard = findOutStandardFromRootNode(fis); fis = new ByteArrayInputStream(xmlContent.getBytes(StandardCharsets.UTF_8));//rewind :-( - fopInput = toFOP(fis, theStandard); + fopInput = toFOP(fis, theStandard, lang); } catch (TransformerException | IOException | ParserConfigurationException e) { LOGGER.error("Failed to apply FOP", e); } @@ -470,9 +443,10 @@ public class ZUGFeRDVisualizer { xmlFile.close(); } - protected void applyXSLTToPDF(final InputStream xmlFile, final OutputStream PDFOutstream) + protected void applyXSLTToPDF(final InputStream xmlFile, final OutputStream PDFOutstream, Language lang) throws TransformerException, IOException { Transformer transformer = mXsltPDFTemplate.newTransformer(); + transformer.setParameter("lang", lang.name().toLowerCase()); transformer.transform(new StreamSource(xmlFile), new StreamResult(PDFOutstream)); xmlFile.close(); diff --git a/library/src/main/resources/stylesheets/l10n/fr.xml b/library/src/main/resources/stylesheets/l10n/fr.xml new file mode 100644 index 00000000..647c908d --- /dev/null +++ b/library/src/main/resources/stylesheets/l10n/fr.xml @@ -0,0 +1,254 @@ + + + Référence acheteur + Nom + Adresse ligne 1 + Adresse ligne 2 + Adresse ligne 3 + Code postal + Ville + Code pays + Identifiant + Schéma de l'identifiant + Nom + Téléphone + Adresse e-mail + Raison sociale + Adresse ligne 1 + Adresse ligne 2 + Adresse ligne 3 + Code postal + Ville + Région + Code pays + Identifiant + Schéma de l'identifiant + Nom + Téléphone + Adresse e-mail + Numéro de facture + Date de facture + Type de facture + Devise + Numéro de projet + Numéro de contrat + Numéro de commande + Référence de commande client + + Du + Au + Numéro de facture + Date de facture + Total des lignes + Total des remises + Total des frais + Total HT + Total TVA + Total TVA en devise comptable + Total TTC + Montant payé + Montant d'arrondi + Montant dû + Catégorie de TVA + Total + Taux de TVA + Montant de TVA + Motif d'exonération + Code du motif d'exonération + + Catégorie de TVA de la remise + Montant de base + Pourcentage + Remise + Taux de TVA de la remise + Motif de la remise + Code du motif de la remise + + Catégorie de TVA des frais + Montant de base + Pourcentage + Frais + Taux de TVA des frais + Motif des frais + Code du motif des frais + Escompte ; autres conditions de paiement + Date d'échéance + Code du moyen de paiement + Moyen de paiement + Référence de paiement + Numéro de carte + Titulaire de la carte + + Référence du mandat + IBAN + Identifiant créancier + + Titulaire du compte + IBAN + BIC + Objet + Remarque + + Nom commercial + Adresse électronique + Schéma de l'adresse électronique + Numéro SIRET/SIREN + Schéma du numéro d'enregistrement + Numéro de TVA intracommunautaire + Numéro fiscal + Informations juridiques complémentaires + Code devise de la TVA + + Nom + Adresse ligne 1 + Adresse ligne 2 + Adresse ligne 3 + Code postal + Ville + Région + Code pays + Numéro de TVA intracommunautaire + + Nom commercial + Région + Adresse électronique + Schéma de l'adresse électronique + Numéro SIRET/SIREN + Schéma du numéro d'enregistrement + Numéro de TVA intracommunautaire + Date d'exigibilité de la TVA + Code de la date d'exigibilité de la TVA + Référence comptable + Identifiant du lieu de livraison + Schéma de l'identifiant + Date de livraison + Nom du destinataire + Adresse ligne 1 + Adresse ligne 2 + Adresse ligne 3 + Code postal + Ville + Région + Code pays + Numéro d'appel d'offres + Référence de l'avis de réception + Référence de l'avis d'expédition + Identifiant du processus + Identifiant de spécification + Identifiant de l'objet + Schéma de l'identifiant de l'objet + + Nom + Identifiant + Schéma de l'identifiant + Numéro SIRET/SIREN + Schéma du numéro d'enregistrement + + Identifiant + Description + Référence (ex. adresse Internet) + Document joint + Format du document joint + Nom du document joint + Date/Heure + Objet + Texte + Détails + Position + Texte libre + Identifiant de l'objet + Schéma de l'identifiant de l'objet + Numéro de ligne de commande + Référence comptable + Du + Au + Quantité + Unité + Prix unitaire (HT) + Prix total (HT) + Remise (HT) + Prix catalogue (HT) + Quantité de base + Code de l'unité de mesure + TVA + Taux de TVA + Désignation + Description + Référence article + Référence article acheteur + Identifiant article + Schéma de l'identifiant article + Code de classification article + Identifiant du schéma + Version du schéma + Code du pays d'origine + Montant de base (HT) + Pourcentage + Remise (HT) + Motif de la remise + Code du motif de la remise + Montant de base (HT) + Pourcentage + Frais (HT) + Motif des frais + Code du motif des frais + Type de créance tierce + Montant de la créance tierce + Description de la créance tierce + Données de la facture électronique + Informations sur l'acheteur + Informations sur le vendeur + Données de facturation + Totaux de la facture + Créance tierce + Ventilation de la TVA au niveau de la facture + Remise au niveau de la facture + Frais au niveau de la facture + Période de facturation + Factures précédentes + Informations de paiement + Informations carte + Prélèvement + Virement + Remarques sur la facture + Lignes de facture + Période de facturation + Détails du prix + Remises au niveau de la ligne de facture + Frais au niveau de la ligne de facture + Informations sur l'article + Caractéristiques de l'article + Compléments + Informations sur le vendeur + Représentant fiscal du vendeur + Informations sur l'acheteur + Informations de livraison + Informations contractuelles + Bénéficiaire différent du vendeur + Fiche de suivi + Historique de traitement + Pièces jointes + Documents justificatifs + [D]/[M]/[Y] + [D]/[M]/[Y] [H]:[m]:[s] + ###.##0,00 + ##0,00 + ###.##0,################# + + Total des créances tierces + + Nous déclinons toute responsabilité quant à l'exactitude des données. + Remarques sur la facture + Ouvrir + Aucune donnée disponible + HT + TTC + Les sections sans contenu ne sont pas affichées ! + Description + Prix + Unité de prix + Taux TVA + Code taxe + Total + Page + diff --git a/library/src/main/resources/stylesheets/result-pdf.xsl b/library/src/main/resources/stylesheets/result-pdf.xsl index 2e135c08..89ec4ebe 100644 --- a/library/src/main/resources/stylesheets/result-pdf.xsl +++ b/library/src/main/resources/stylesheets/result-pdf.xsl @@ -85,6 +85,37 @@ font-weight="bold"> Prüfbericht + + + + + + + + + + + Name: + + + mustangproject.org + + + + + Version: + + + + + + + + + diff --git a/library/src/main/resources/stylesheets/xr-content.xsl b/library/src/main/resources/stylesheets/xr-content.xsl index 69f91322..fb42f8b8 100644 --- a/library/src/main/resources/stylesheets/xr-content.xsl +++ b/library/src/main/resources/stylesheets/xr-content.xsl @@ -8,6 +8,7 @@ xmlns:xrf="https://projekte.kosit.org/xrechnung/xrechnung-visualization/functions"> + diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/BT18ReferenceTypeCodeTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/BT18ReferenceTypeCodeTest.java new file mode 100644 index 00000000..2db93307 --- /dev/null +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/BT18ReferenceTypeCodeTest.java @@ -0,0 +1,64 @@ +package org.mustangproject.ZUGFeRD; + +import junit.framework.TestCase; +import org.mustangproject.Invoice; +import org.mustangproject.Item; +import org.mustangproject.Product; +import org.mustangproject.ReferencedDocument; +import org.mustangproject.TradeParty; + +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +import static org.xmlunit.assertj.XmlAssert.assertThat; + +public class BT18ReferenceTypeCodeTest extends TestCase { + + /** + * Ensures BT-18-1 (scheme identifier) is serialized as ram:ReferenceTypeCode + * inside ram:AdditionalReferencedDocument (TypeCode=130). + */ + public void testBT18ReferenceTypeCodeIsWrittenWhenSet() { + // Minimal invoice (XRechnung profile => CII output) + Invoice i = new Invoice() + .setIssueDate(new Date()) + .setDueDate(new Date()) + .setDeliveryDate(new Date()) + .setSender(new TradeParty("Test company", "teststr", "55232", "teststadt", "DE")) + .setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE")) + .setNumber("INV-123") + .addItem(new Item( + new Product("Testprodukt", "", "C62", BigDecimal.ZERO), + new BigDecimal("1.00"), + BigDecimal.ONE + )); + + // BT-18: Invoiced Object Identifier (AdditionalReferencedDocument / TypeCode 130) + ReferencedDocument rd = new ReferencedDocument("ID unique"); + rd.setReferenceTypeCode("AJW"); // BT-18-1 + i.setObjectIdentifierReferencedDocument(rd); + + ZUGFeRD2PullProvider zf2p = new ZUGFeRD2PullProvider(); + zf2p.setProfile(Profiles.getByName("XRechnung")); + zf2p.generateXML(i); + + String xml = new String(zf2p.getXML(), StandardCharsets.UTF_8); + + // Assert: we still have a CII invoice + assertTrue(xml.contains(" /.../ExchangedDocument/ram:ID + .setIssueDate(new Date()) // BT-2: Issue date + .setDeliveryDate(new Date()) // Delivery date (depending on profile/exporter mapping) + .setDueDate(new Date()) // BT-9: Payment due date + .setCurrency("EUR") // BT-5: Invoice currency code + .setSender(buyer) + .setRecipient(seller); + + Product service = new Product("Prestation", "Service intercommunal", "C62", new BigDecimal("20.00")); + invoice.addItem(new Item(service, BigDecimal.ONE, BigDecimal.TEN).setTax(BigDecimal.valueOf(20))); + + // ===== WHEN: Generating CII XML using the XRechnung profile ===== + + ZUGFeRD2PullProvider provider = new ZUGFeRD2PullProvider(); + provider.setProfile(Profiles.getByName("XRechnung")); + provider.generateXML(invoice); + + String xml = new String(provider.getXML(), StandardCharsets.UTF_8); + + // ===== THEN: Non-regression check ===== + // XRechnung currently emits BT-23 with a default fixed URN. + // This behavior must remain unchanged when businessProcessId is NOT explicitly provided by the user. + + assertTrue( + xml.contains(""), + "XRechnung should still emit BusinessProcessSpecifiedDocumentContextParameter (BT-23)." + ); + + assertTrue( + xml.contains("urn:fdc:peppol.eu:2017:poacc:billing:01:1.0"), + "XRechnung default BT-23 URN must remain unchanged when businessProcessId is not set." + ); + } + + @Test + void whenBusinessProcessIdIsSet_itIsEmitted_inExtendedProfile() throws Exception { + // ===== GIVEN: The same minimal invoice, but WITH an explicit businessProcessId ===== + + TradeParty buyer = new TradeParty("Client X", "3 rue C", "33000", "Bordeaux", "FR"); + TradeParty seller = new TradeParty("Mairie A", "1 rue A", "75001", "Paris", "FR"); + + Invoice invoice = new Invoice() + .setNumber("INV-CALC-001") + .setIssueDate(new Date()) + .setDeliveryDate(new Date()) + .setDueDate(new Date()) + .setCurrency("EUR") + .setSender(buyer) + .setRecipient(seller); + + Product service = new Product("Prestation", "Service intercommunal", "C62", new BigDecimal("20.00")); + invoice.addItem(new Item(service, BigDecimal.ONE, BigDecimal.TEN).setTax(BigDecimal.valueOf(20))); + + // Proposed new API: user-provided BT-23 value + invoice.setBusinessProcessId("B1"); + // BT-23 path: + // /rsm:CrossIndustryInvoice/rsm:ExchangedDocumentContext/ + // ram:BusinessProcessSpecifiedDocumentContextParameter/ram:ID + + // ===== WHEN: Generating CII XML using the EXTENDED profile ===== + + ZUGFeRD2PullProvider provider = new ZUGFeRD2PullProvider(); + provider.setProfile(Profiles.getByName("EXTENDED")); + provider.generateXML(invoice); + + String xml = new String(provider.getXML(), StandardCharsets.UTF_8); + + // ===== THEN: BT-23 must be emitted with the user-provided value ===== + + assertTrue( + xml.contains(""), + "When businessProcessId is set, BT-23 must be emitted even for EXTENDED." + ); + + assertTrue( + xml.contains("B1"), + "BT-23 must contain the user-provided businessProcessId." + ); + + // Extra sanity check: the guideline/specification identifier (BT-24) should still be present + assertTrue( + xml.contains(""), + "BT-24 (GuidelineSpecifiedDocumentContextParameter) must be present." + ); + } +} diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/CashDiscountFacturXTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/CashDiscountFacturXTest.java new file mode 100644 index 00000000..b2631802 --- /dev/null +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/CashDiscountFacturXTest.java @@ -0,0 +1,178 @@ +package org.mustangproject.ZUGFeRD; + +import junit.framework.TestCase; +import org.mustangproject.Invoice; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.text.ParseException; +import javax.xml.xpath.XPathExpressionException; + +/* https://github.com/ZUGFeRD/mustangproject/issues/993 */ +public class CashDiscountFacturXTest extends TestCase { + String XMLString = "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended\n" + + " \n" + + " \n" + + " \n" + + " 471102\n" + + " 380\n" + + " \n" + + " 20260201\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " 1\n" + + " \n" + + " \n" + + " Trennblätter A4\n" + + " \n" + + " \n" + + " \n" + + " 9.9000\n" + + " 1.0000\n" + + " \n" + + " \n" + + " \n" + + " 20.0000\n" + + " \n" + + " \n" + + " \n" + + " VAT\n" + + " S\n" + + " 19.00\n" + + " \n" + + " \n" + + " 198.00\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " 2\n" + + " \n" + + " \n" + + " Joghurt Banane\n" + + " \n" + + " \n" + + " \n" + + " 5.5000\n" + + " 1.0000\n" + + " \n" + + " \n" + + " \n" + + " 50.0000\n" + + " \n" + + " \n" + + " \n" + + " VAT\n" + + " S\n" + + " 7.00\n" + + " \n" + + " \n" + + " 275.00\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " Lieferant GmbH\n" + + " \n" + + " 80333\n" + + " Lieferantenstraße 20\n" + + " München\n" + + " DE\n" + + " \n" + + " \n" + + " DE123456789\n" + + " \n" + + " \n" + + " 201/113/40209\n" + + " \n" + + " \n" + + " \n" + + " Kunden AG Mitte\n" + + " \n" + + " 69876\n" + + " Kundenstraße 15\n" + + " Frankfurt\n" + + " DE\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " 20180304\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " 471102\n" + + " EUR\n" + + " \n" + + " 19.25\n" + + " VAT\n" + + " 275.00\n" + + " S\n" + + " 7.00\n" + + " \n" + + " \n" + + " 37.62\n" + + " VAT\n" + + " 198.00\n" + + " S\n" + + " 19.00\n" + + " \n" + + " \n" + + " Please remit until 04.03.2018\n" + + " \n" + + " 20180304\n" + + " \n" + + " \n" + + " \n" + + " Bis zum 15.02.2026 erhalten Sie 4,000 % Skonto, 21.19 EUR\n" + + " \n" + + " 20260215\n" + + " \n" + + " \n" + + " 529.8700\n" + + " 4.00\n" + + " 21.19\n" + + " \n" + + " \n" + + " \n" + + " Bis zum 16.02.2026 ohne Abzug\n" + + " \n" + + " 20260216\n" + + " \n" + + " \n" + + " \n" + + " 473.00\n" + + " 0.00\n" + + " 0.00\n" + + " 473.00\n" + + " 56.87\n" + + " 529.87\n" + + " 0.00\n" + + " 529.87\n" + + " \n" + + " \n" + + " \n" + + " "; + + public void testCashDiscount() throws XPathExpressionException, ParseException { + InputStream inputStream = new ByteArrayInputStream(XMLString.getBytes(StandardCharsets.UTF_8)); + ZUGFeRDInvoiceImporter zii = new ZUGFeRDInvoiceImporter(inputStream); + Invoice invoice = zii.extractInvoice(); + assertEquals(new BigDecimal("4.00"), invoice.getCashDiscounts()[0].getPercent()); + } +} diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterTest.java index b7fbede6..a00df382 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterTest.java @@ -23,7 +23,11 @@ import junit.framework.TestSuite; import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary; +import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode; import org.apache.pdfbox.pdmodel.common.PDMetadata; +import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; +import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; import org.apache.pdfbox.pdmodel.encryption.InvalidPasswordException; import org.apache.xmpbox.XMPMetadata; import org.apache.xmpbox.schema.PDFAIdentificationSchema; @@ -218,8 +222,6 @@ public class MustangReaderWriterTest extends MustangReaderTestCase { zi.doRecalculateItemPricesFromLineTotals(); zi.doIgnoreCalculationErrors(); zi.setInputStream(inputStream); - // Reading ZUGFeRD - String amount = zi.getAmount(); assertEquals("\n" + " \n" + @@ -297,8 +299,8 @@ public class MustangReaderWriterTest extends MustangReaderTestCase { " \n" + "", zi.getXMP()); // this resembles the data written in MustangReaderWriterCustomXMLTest - assertEquals(amount, "1005.55"); - + assertEquals("845.00", zi.getTaxBasisTotalAmount()); + assertEquals("1005.55", zi.getAmount()); } @@ -415,17 +417,18 @@ public class MustangReaderWriterTest extends MustangReaderTestCase { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ze.export(baos); ze.close(); - String pdfContent = baos.toString(StandardCharsets.UTF_8); - assertFalse(pdfContent.indexOf(DocumentContextParameterTypeConstants.BASIC) >= 0); - assertFalse(pdfContent.indexOf(DocumentContextParameterTypeConstants.EXTENDED) >= 0); - assertTrue(pdfContent.indexOf(DocumentContextParameterTypeConstants.COMFORT) >= 0); + byte[] targetBytes = baos.toByteArray(); + String xmlContent = readEmbeddedXmlFromPdf(targetBytes, "ZUGFeRD-invoice.xml"); + assertFalse(xmlContent.contains(DocumentContextParameterTypeConstants.BASIC)); + assertFalse(xmlContent.contains(DocumentContextParameterTypeConstants.EXTENDED)); + assertTrue(xmlContent.contains(DocumentContextParameterTypeConstants.COMFORT)); } catch (IOException e) { fail("IOException should not happen in testZExport"); } } - + public void testFXExport() throws Exception { final String TARGET_PDF = "./target/testout-MustangGnuaccountingBeispielRE-20171118_506fx.pdf"; @@ -442,7 +445,8 @@ public class MustangReaderWriterTest extends MustangReaderTestCase { result.write(buffer, 0, length); } - ze.addAdditionalFile("test.pdf", result.toByteArray()); + var bytes = result.toByteArray(); + ze.addAdditionalFile("test.pdf", bytes); ze.setTransaction(this); ze.disableAutoClose(true); @@ -451,12 +455,14 @@ public class MustangReaderWriterTest extends MustangReaderTestCase { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ze.export(baos); ze.close(); - String pdfContent = baos.toString(StandardCharsets.UTF_8); - assertFalse(pdfContent.indexOf("(via mustangproject.org") == -1); + byte[] targetBytes = baos.toByteArray(); + String pdfContent = new String(targetBytes, StandardCharsets.UTF_8); + String xmlContent = readEmbeddedXmlFromPdf(targetBytes, "factur-x.xml"); + assertTrue(pdfContent.contains("(via mustangproject.org")); // check for pdf-a schema extension - assertFalse(pdfContent.indexOf("EN 16931") == -1); - assertFalse(pdfContent.indexOf("fx") == -1); - assertFalse(pdfContent.indexOf("urn:cen.eu:en16931:2017") == -1); + assertTrue(pdfContent.contains("EN 16931")); + assertTrue(pdfContent.contains("fx")); + assertTrue(xmlContent.contains("urn:cen.eu:en16931:2017")); } // now check the contents (like MustangReaderTest) @@ -470,6 +476,26 @@ public class MustangReaderWriterTest extends MustangReaderTestCase { assertEquals(getNumber(), zi.getForeignReference()); } + private static String readEmbeddedXmlFromPdf(byte[] targetBytes, String xmlFilename) throws IOException { + // Read attached (compressed) XML file + try (PDDocument pdDocument = Loader.loadPDF(targetBytes)) { + PDDocumentNameDictionary names = new PDDocumentNameDictionary(pdDocument.getDocumentCatalog()); + + PDEmbeddedFilesNameTreeNode embeddedFiles = names.getEmbeddedFiles(); + Map fileMap = embeddedFiles.getNames(); + + + PDComplexFileSpecification fileSpec = fileMap.get(xmlFilename); + if (fileSpec == null) { + fail("Files does not contain a file named " + xmlFilename); + return null; + } + PDEmbeddedFile embeddedFile = fileSpec.getEmbeddedFile(); + String xmlContent = new String(embeddedFile.toByteArray(), StandardCharsets.UTF_8); + return xmlContent; + } + } + public void testExceptionOnPDF14() { final String TARGET_PDF = "./target/testout-MustangGnuaccountingBeispielRE-20170509_505new.pdf"; diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/SubInvoiceLineExportTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/SubInvoiceLineExportTest.java new file mode 100644 index 00000000..0cfab810 --- /dev/null +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/SubInvoiceLineExportTest.java @@ -0,0 +1,154 @@ +package org.mustangproject.ZUGFeRD; + +import org.junit.jupiter.api.Test; +import org.mustangproject.*; + +import java.io.File; +import java.io.InputStream; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +import static org.junit.jupiter.api.Assertions.*; + +public class SubInvoiceLineExportTest { + + private static final String TARGET_PDF = "./target/testout-SubInvoiceLines.pdf"; + + @Test + public void testSubInvoiceLineExportRoundTrip() throws Exception { + + // -- Build invoice with hierarchical positions -- + TradeParty sender = new TradeParty("Test Seller", "Test Street 1", "12345", "Test City", "DE") + .addTaxID("DE123456789") + .addVATID("DE123456789"); + Contact senderContact = new Contact("Max Mustermann", "012345", "max@test.de"); + sender.setContact(senderContact); + + TradeParty recipient = new TradeParty("Test Buyer", "Buyer Street 1", "54321", "Buyer City", "DE") + .addVATID("DE987654321"); + + // DETAIL line 0101: child of group 01 + Item detail0101 = new Item( + new Product("Laser printer", "", "H87", new BigDecimal("19")), + new BigDecimal("300.00"), + new BigDecimal("2.0000") + ); + detail0101.setId("0101"); + detail0101.setParentLineID("01"); + detail0101.setLineStatusReasonCode("DETAIL"); + + // DETAIL line 0102: child of group 01 + Item detail0102 = new Item( + new Product("Ink printer", "", "H87", new BigDecimal("19")), + new BigDecimal("150.00"), + new BigDecimal("3.0000") + ); + detail0102.setId("0102"); + detail0102.setParentLineID("01"); + detail0102.setLineStatusReasonCode("DETAIL"); + + // INFORMATION line 0103: child of group 01, non-calculation-relevant + Item info0103 = new Item( + new Product("Setup instructions", "", "H87", new BigDecimal("19")), + new BigDecimal("0.00"), + new BigDecimal("1.0000") + ); + info0103.setId("0103"); + info0103.setParentLineID("01"); + info0103.setLineStatusReasonCode("INFORMATION"); + + // GROUP line 01: parent, LineTotalAmount = sum of DETAIL children = 1050 + Item group01 = new Item( + new Product("Hardware bundle", "", "H87", new BigDecimal("19")), + new BigDecimal("1050.00"), + new BigDecimal("1.0000") + ); + group01.setId("01"); + group01.setLineStatusReasonCode("GROUP"); + + Invoice invoice = new Invoice() + .setNumber("SUB-INV-001") + .setIssueDate(new Date()) + .setDeliveryDate(new Date()) + .setDueDate(new Date()) + .setSender(sender) + .setRecipient(recipient) + .setCurrency("EUR") + .setPaymentTermDescription("Net 30") + .addItem(detail0101) + .addItem(detail0102) + .addItem(info0103) + .addItem(group01); + + // -- Export to PDF with EXTENDED profile -- + try (InputStream sourcePDF = this.getClass() + .getResourceAsStream("/MustangGnuaccountingBeispielRE-20170509_505PDFA3.pdf"); + ZUGFeRDExporterFromA3 exporter = new ZUGFeRDExporterFromA3() + .setProducer("Mustang SubInvoiceLine Test") + .setCreator("Test") + .setZUGFeRDVersion(2) + .setProfile("EXTENDED") + .load(sourcePDF)) { + + exporter.setTransaction(invoice); + + // Verify XML contains the hierarchical elements + String xml = new String(exporter.getProvider().getXML(), StandardCharsets.UTF_8); + assertTrue(xml.contains("01"), + "XML must contain ParentLineID element"); + assertTrue(xml.contains("DETAIL"), + "XML must contain DETAIL LineStatusReasonCode"); + assertTrue(xml.contains("GROUP"), + "XML must contain GROUP LineStatusReasonCode"); + assertTrue(xml.contains("INFORMATION"), + "XML must contain INFORMATION LineStatusReasonCode"); + + exporter.export(TARGET_PDF); + } + + // -- Re-import and verify round-trip -- + ZUGFeRDImporter importer = new ZUGFeRDImporter(TARGET_PDF); + Invoice reimported = new Invoice(); + importer.extractInto(reimported); + + // Find items by ID + Item rDetail0101 = findItemById(reimported, "0101"); + assertNotNull(rDetail0101, "DETAIL item 0101 must be reimported"); + assertEquals("01", rDetail0101.getParentLineID()); + assertEquals("DETAIL", rDetail0101.getLineStatusReasonCode()); + assertTrue(rDetail0101.isCalculationRelevant()); + + Item rDetail0102 = findItemById(reimported, "0102"); + assertNotNull(rDetail0102, "DETAIL item 0102 must be reimported"); + assertEquals("01", rDetail0102.getParentLineID()); + assertEquals("DETAIL", rDetail0102.getLineStatusReasonCode()); + assertTrue(rDetail0102.isCalculationRelevant()); + + Item rInfo0103 = findItemById(reimported, "0103"); + assertNotNull(rInfo0103, "INFORMATION item 0103 must be reimported"); + assertEquals("01", rInfo0103.getParentLineID()); + assertEquals("INFORMATION", rInfo0103.getLineStatusReasonCode()); + assertFalse(rInfo0103.isCalculationRelevant()); + + Item rGroup01 = findItemById(reimported, "01"); + assertNotNull(rGroup01, "GROUP item 01 must be reimported"); + assertNull(rGroup01.getParentLineID()); + assertEquals("GROUP", rGroup01.getLineStatusReasonCode()); + assertFalse(rGroup01.isCalculationRelevant()); + + // Verify only DETAIL lines contribute to totals + // DETAIL: 0101=600, 0102=450 = 1050 + TransactionCalculator tc = new TransactionCalculator(reimported); + assertEquals(new BigDecimal("1050.00"), tc.getTotal().setScale(2)); + } + + private Item findItemById(Invoice invoice, String id) { + for (IZUGFeRDExportableItem item : invoice.getZFItems()) { + if (item instanceof Item && id.equals(((Item) item).getId())) { + return (Item) item; + } + } + return null; + } +} diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/VisualizationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/VisualizationTest.java index db772ff1..a57d62a9 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/VisualizationTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/VisualizationTest.java @@ -1,176 +1,210 @@ -/** - * ********************************************************************* - *

- * Copyright 2019 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 javax.xml.parsers.ParserConfigurationException; -import org.junit.FixMethodOrder; -import org.junit.runners.MethodSorters; -import org.mustangproject.ZUGFeRD.ZUGFeRDVisualizer.Language; -import org.mustangproject.util.ByteArraySearcher; - -import javax.xml.transform.TransformerException; -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.text.SimpleDateFormat; -import java.util.Date; - -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -public class VisualizationTest extends ResourceCase { - - final String TARGET_PDF_CII = "./target/testout-Visualization-cii.pdf"; - final String TARGET_PDF_UBL = "./target/testout-Visualization-cii.pdf"; - - public void testCIIVisualizationBasic() { - this.runZUGFeRDVisualization("factur-x.xml", "factur-x-vis.fr.html", Language.FR); - } - - public void testCIIVisualizationExtended() { - this.runZUGFeRDVisualization("factur-x-extended.xml", "factur-x-vis-extended.de.html", Language.DE); - } - - public void testUBLCreditNoteVisualizationBasic() { - this.runZUGFeRDVisualization("ubl-creditnote.xml", "factur-x-vis-ubl-creditnote.en.html", Language.EN); - } - - public void testUBLVisualizationBasic() { - this.runZUGFeRDVisualization("ubl/01.01a-INVOICE.ubl.xml", "factur-x-vis-ubl.en.html", Language.EN); - } - - private void runZUGFeRDVisualization(String inputFilename, String resultFileName, Language lang) { - File CIIinputFile = getResourceAsFile(inputFilename); - - String expected = null; - String result = null; - try { - ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer(); - result = zvi.visualize(CIIinputFile.getAbsolutePath(), lang); - Files.write(Paths.get("./target/testout-" + resultFileName), result.getBytes(StandardCharsets.UTF_8)); - - File expectedResult = getResourceAsFile(resultFileName); - expected = new String(Files.readAllBytes(expectedResult.toPath()), StandardCharsets.UTF_8) - ; - - } catch (UnsupportedOperationException e) { - fail("UnsupportedOperationException should not happen: " + e.getMessage()); - } catch (IllegalArgumentException e) { - fail("IllegalArgumentException should not happen: " + e.getMessage()); - } catch (TransformerException e) { - fail("TransformerException should not happen: " + e.getMessage()); - } catch (IOException e) { - fail("IOException should not happen: " + e.getMessage()); - } catch (ParserConfigurationException e) { - fail("ParserConfigurationException should not happen: " + e.getMessage()); - } - - assertNotNull(result); - /* remove file endings so that tests can also pass after checking - out from git with arbitrary options (which may include CSRF changes) - */ - assertEquals(expected.replace("\r", "") - .replace("\n", "") - .replace("\t", "") - .replace(" ", ""), result.replace("\r", "") - .replace("\n", "") - .replace("\t", "") - .replace(" ", "")); - } - - - public void testPDFVisualizationCII() { - - File CIIinputFile = getResourceAsFile("cii/01.01a-INVOICE.cii.xml"); - - // the writing part - - String expected = null; - String result = null; - try { - ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer(); - zvi.toPDF(CIIinputFile.getAbsolutePath(), TARGET_PDF_CII); - } catch (UnsupportedOperationException e) { - fail("UnsupportedOperationException should not happen: " + e.getMessage()); - } catch (IllegalArgumentException e) { - fail("IllegalArgumentException should not happen: " + e.getMessage()); - } - - try { - assertTrue(ByteArraySearcher.startsWith(Files.readAllBytes(Paths.get(TARGET_PDF_CII)), new byte[]{'%', 'P', 'D', 'F'})); - } catch (IOException e) { - fail("IOException should not occur"); - } - } - - public void testPDFVisualizationUBL() { - - File UBLinputFile = getResourceAsFile("ubl/01.01a-INVOICE.ubl.xml"); - - // the writing part - String sourceFilename = "factur-x.xml"; - - String expected = null; - String result = null; - try { - ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer(); - zvi.toPDF(UBLinputFile.getAbsolutePath(), TARGET_PDF_UBL); - } catch (UnsupportedOperationException e) { - fail("UnsupportedOperationException should not happen: " + e.getMessage()); - } catch (IllegalArgumentException e) { - fail("IllegalArgumentException should not happen: " + e.getMessage()); - } - - - try { - assertTrue(ByteArraySearcher.startsWith(Files.readAllBytes(Paths.get(TARGET_PDF_CII)), new byte[]{'%', 'P', 'D', 'F'})); - } catch (IOException e) { - fail("IOException should not occur"); - } - } - - public void testPDFVisualizationUBLCreditNote() { - - File UBLinputFile = getResourceAsFile("ubl/UBL-CreditNote-2.1-Example.ubl.xml"); - - // the writing part - String sourceFilename = "factur-x.xml"; - - String expected = null; - String result = null; - try { - ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer(); - zvi.toPDF(UBLinputFile.getAbsolutePath(), TARGET_PDF_UBL); - } catch (UnsupportedOperationException e) { - fail("UnsupportedOperationException should not happen: " + e.getMessage()); - } catch (IllegalArgumentException e) { - fail("IllegalArgumentException should not happen: " + e.getMessage()); - } - - - try { - assertTrue(ByteArraySearcher.startsWith(Files.readAllBytes(Paths.get(TARGET_PDF_CII)), new byte[]{'%', 'P', 'D', 'F'})); - } catch (IOException e) { - fail("IOException should not occur"); - } - } - -} +/** + * ********************************************************************* + *

+ * Copyright 2019 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 javax.xml.parsers.ParserConfigurationException; +import org.junit.FixMethodOrder; +import org.junit.runners.MethodSorters; +import org.mustangproject.ZUGFeRD.ZUGFeRDVisualizer.Language; +import org.mustangproject.util.ByteArraySearcher; + +import javax.xml.transform.TransformerException; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class VisualizationTest extends ResourceCase { + + final String TARGET_PDF_CII = "./target/testout-Visualization-cii.pdf"; + final String TARGET_PDF_UBL = "./target/testout-Visualization-cii.pdf"; + + public void testCIIVisualizationBasic() { + this.runZUGFeRDVisualization("factur-x.xml", "factur-x-vis.fr.html", Language.FR); + } + + public void testCIIVisualizationExtended() { + this.runZUGFeRDVisualization("factur-x-extended.xml", "factur-x-vis-extended.de.html", Language.DE); + } + + public void testUBLCreditNoteVisualizationBasic() { + this.runZUGFeRDVisualization("ubl-creditnote.xml", "factur-x-vis-ubl-creditnote.en.html", Language.EN); + } + + public void testUBLVisualizationBasic() { + this.runZUGFeRDVisualization("ubl/01.01a-INVOICE.ubl.xml", "factur-x-vis-ubl.en.html", Language.EN); + } + + private void runZUGFeRDVisualization(String inputFilename, String resultFileName, Language lang) { + File CIIinputFile = getResourceAsFile(inputFilename); + + String expected = null; + String result = null; + try { + ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer(); + result = zvi.visualize(CIIinputFile.getAbsolutePath(), lang); + Files.write(Paths.get("./target/testout-" + resultFileName), result.getBytes(StandardCharsets.UTF_8)); + + File expectedResult = getResourceAsFile(resultFileName); + expected = new String(Files.readAllBytes(expectedResult.toPath()), StandardCharsets.UTF_8) + ; + + } catch (UnsupportedOperationException e) { + fail("UnsupportedOperationException should not happen: " + e.getMessage()); + } catch (IllegalArgumentException e) { + fail("IllegalArgumentException should not happen: " + e.getMessage()); + } catch (TransformerException e) { + fail("TransformerException should not happen: " + e.getMessage()); + } catch (IOException e) { + fail("IOException should not happen: " + e.getMessage()); + } catch (ParserConfigurationException e) { + fail("ParserConfigurationException should not happen: " + e.getMessage()); + } + + assertNotNull(result); + /* remove file endings so that tests can also pass after checking + out from git with arbitrary options (which may include CSRF changes) + */ + assertEquals(expected.replace("\r", "") + .replace("\n", "") + .replace("\t", "") + .replace(" ", ""), result.replace("\r", "") + .replace("\n", "") + .replace("\t", "") + .replace(" ", "")); + } + + + public void testPDFVisualizationCII() { + File CIIinputFile = getResourceAsFile("cii/01.01a-INVOICE.cii.xml"); + + try { + ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer(); + zvi.toPDF(CIIinputFile.getAbsolutePath(), TARGET_PDF_CII); + } catch (UnsupportedOperationException e) { + fail("UnsupportedOperationException should not happen: " + e.getMessage()); + } catch (IllegalArgumentException e) { + fail("IllegalArgumentException should not happen: " + e.getMessage()); + } + + try { + assertTrue(ByteArraySearcher.startsWith(Files.readAllBytes(Paths.get(TARGET_PDF_CII)), new byte[]{'%', 'P', 'D', 'F'})); + assertTrue(ByteArraySearcher.contains(Files.readAllBytes(Paths.get(TARGET_PDF_CII)), "de".getBytes())); + } catch (IOException e) { + fail("IOException should not occur"); + } + } + + public void testPDFVisualizationCIIEnglish() { + File CIIinputFile = getResourceAsFile("cii/01.01a-INVOICE.cii.xml"); + + try { + ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer(); + zvi.toPDF(CIIinputFile.getAbsolutePath(), TARGET_PDF_CII, Language.EN); + } catch (UnsupportedOperationException e) { + fail("UnsupportedOperationException should not happen: " + e.getMessage()); + } catch (IllegalArgumentException e) { + fail("IllegalArgumentException should not happen: " + e.getMessage()); + } + + try { + assertTrue(ByteArraySearcher.startsWith(Files.readAllBytes(Paths.get(TARGET_PDF_CII)), new byte[]{'%', 'P', 'D', 'F'})); + assertTrue(ByteArraySearcher.contains(Files.readAllBytes(Paths.get(TARGET_PDF_CII)), "en".getBytes())); + } catch (IOException e) { + fail("IOException should not occur"); + } + } + + public void testPDFVisualizationCIIFrench() { + File CIIinputFile = getResourceAsFile("cii/01.01a-INVOICE.cii.xml"); + + try { + ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer(); + zvi.toPDF(CIIinputFile.getAbsolutePath(), TARGET_PDF_CII, Language.FR); + } catch (UnsupportedOperationException e) { + fail("UnsupportedOperationException should not happen: " + e.getMessage()); + } catch (IllegalArgumentException e) { + fail("IllegalArgumentException should not happen: " + e.getMessage()); + } + + try { + assertTrue(ByteArraySearcher.startsWith(Files.readAllBytes(Paths.get(TARGET_PDF_CII)), new byte[]{'%', 'P', 'D', 'F'})); + assertTrue(ByteArraySearcher.contains(Files.readAllBytes(Paths.get(TARGET_PDF_CII)), "fr".getBytes())); + } catch (IOException e) { + fail("IOException should not occur"); + } + } + + public void testPDFVisualizationUBL() { + + File UBLinputFile = getResourceAsFile("ubl/01.01a-INVOICE.ubl.xml"); + + // the writing part + String sourceFilename = "factur-x.xml"; + + String expected = null; + String result = null; + try { + ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer(); + zvi.toPDF(UBLinputFile.getAbsolutePath(), TARGET_PDF_UBL); + } catch (UnsupportedOperationException e) { + fail("UnsupportedOperationException should not happen: " + e.getMessage()); + } catch (IllegalArgumentException e) { + fail("IllegalArgumentException should not happen: " + e.getMessage()); + } + + + try { + assertTrue(ByteArraySearcher.startsWith(Files.readAllBytes(Paths.get(TARGET_PDF_CII)), new byte[]{'%', 'P', 'D', 'F'})); + } catch (IOException e) { + fail("IOException should not occur"); + } + } + + public void testPDFVisualizationUBLCreditNote() { + + File UBLinputFile = getResourceAsFile("ubl/UBL-CreditNote-2.1-Example.ubl.xml"); + + // the writing part + String sourceFilename = "factur-x.xml"; + + String expected = null; + String result = null; + try { + ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer(); + zvi.toPDF(UBLinputFile.getAbsolutePath(), TARGET_PDF_UBL); + } catch (UnsupportedOperationException e) { + fail("UnsupportedOperationException should not happen: " + e.getMessage()); + } catch (IllegalArgumentException e) { + fail("IllegalArgumentException should not happen: " + e.getMessage()); + } + + + try { + assertTrue(ByteArraySearcher.startsWith(Files.readAllBytes(Paths.get(TARGET_PDF_CII)), new byte[]{'%', 'P', 'D', 'F'})); + } catch (IOException e) { + fail("IOException should not occur"); + } + } + +} diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java index 38d4ec84..3cddc296 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ZF2PushTest.java @@ -544,7 +544,7 @@ public class ZF2PushTest extends TestCase { .addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(19)), price, new BigDecimal(1.0))) .addItem(new Item(new Product("Testprodukt", "", "H87", new BigDecimal(19)), price, new BigDecimal(1.0))) .addCharge(new Charge(new BigDecimal(0.5)).setTaxPercent(new BigDecimal(19)).setReasonCode("ABK")) - .addAllowance(new Allowance(new BigDecimal(0.2)).setTaxPercent(new BigDecimal(19)).setReasonCode("ABK")) + .addAllowance(new Allowance(new BigDecimal(0.2)).setTaxPercent(new BigDecimal(19)).setReasonCode("95")) ); String theXML = new String(ze.getProvider().getXML(), StandardCharsets.UTF_8); assertTrue(theXML.contains("1.0 */ try { - final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks" - factory.setXIncludeAware(false); - - final DocumentBuilder builder = factory.newDocumentBuilder(); + final DocumentBuilder builder = XMLTools.getDocumentBuilder(false); final InputSource is = new InputSource(new StringReader(xmp)); docXMP = builder.parse(is); diff --git a/validator/src/main/java/org/mustangproject/validator/Validator.java b/validator/src/main/java/org/mustangproject/validator/Validator.java index 513e996f..c4484b71 100644 --- a/validator/src/main/java/org/mustangproject/validator/Validator.java +++ b/validator/src/main/java/org/mustangproject/validator/Validator.java @@ -7,9 +7,8 @@ import java.net.URL; import javax.xml.XMLConstants; import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; -import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; +import org.mustangproject.XMLTools; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.SAXException; @@ -59,11 +58,8 @@ public abstract class Validator { protected void validateSchema(byte[] xmlRawData, String schemaPath, int section, EPart part) throws IrrecoverableValidationError { URL schemaFile = Thread.currentThread().getContextClassLoader().getResource("schema/" + schemaPath); Source xmlData = new StreamSource(new ByteArrayInputStream(xmlRawData)); - SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); try { - schemaFactory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); - Schema schema = schemaFactory.newSchema(schemaFile); - javax.xml.validation.Validator validator = schema.newValidator(); + javax.xml.validation.Validator validator = XMLTools.getValidator(schemaFile); validator.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); validator.validate(xmlData); } catch (SAXException e) { diff --git a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java index 61565f61..0cb668a7 100644 --- a/validator/src/main/java/org/mustangproject/validator/XMLValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/XMLValidator.java @@ -13,9 +13,7 @@ import java.text.ParseException; import java.util.Calendar; import java.util.Set; -import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.stream.StreamSource; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; @@ -156,26 +154,7 @@ public class XMLValidator extends Validator { * */ - 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 db = dbf.newDocumentBuilder(); + final DocumentBuilder db = XMLTools.getDocumentBuilder(true); final InputSource is = new InputSource(new StringReader(zfXML)); final Document doc = db.parse(is); @@ -213,8 +192,10 @@ public class XMLValidator extends Validator { boolean isExtended = false; boolean isXRechnung = false; String currentZFVersionDir = "ZF_240"; + String currentXPZ12VersionDir ="XP_Z12_012"; int mainSchematronSectionErrorTypeCode = 4; String xsltFilename = null; + boolean runFrenchCiiSchematron = false; // urn:ferd:CrossIndustryDocument:invoice:1p0:extended, // urn:ferd:CrossIndustryDocument:invoice:1p0:comfort, // urn:ferd:CrossIndustryDocument:invoice:1p0:basic, @@ -234,6 +215,11 @@ public class XMLValidator extends Validator { } else if (root.getLocalName().equalsIgnoreCase("CrossIndustryInvoice")) { // ZUGFeRD 2.0 or Factur-X context.setGeneration("2"); + final String sellerCountry = getXPathString(doc, + "//*[local-name() = 'CrossIndustryInvoice']//*[local-name() = 'SupplyChainTradeTransaction']//*[local-name() = 'ApplicableHeaderTradeAgreement']//*[local-name() = 'SellerTradeParty']//*[local-name() = 'PostalTradeAddress']/*[local-name() = 'CountryID']/text()"); + final String buyerCountry = getXPathString(doc, + "//*[local-name() = 'CrossIndustryInvoice']//*[local-name() = 'SupplyChainTradeTransaction']//*[local-name() = 'ApplicableHeaderTradeAgreement']//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'PostalTradeAddress']/*[local-name() = 'CountryID']/text()"); + runFrenchCiiSchematron = "FR".equalsIgnoreCase(sellerCountry) && "FR".equalsIgnoreCase(buyerCountry); isMiniumum = contextProfile.contains("minimum"); isBasic = contextProfile.contains("basic"); @@ -391,6 +377,11 @@ public class XMLValidator extends Validator { // main schematron validation validateSchematron(zfXML, xsltFilename, mainSchematronSectionErrorTypeCode, ESeverity.error); + if (runFrenchCiiSchematron) { + String xsltFRFilename = "/xslt/" + currentXPZ12VersionDir + "/20260216_BR-FR-Flux2-Schematron-CII_V1.3.0.xsl"; + validateSchematron(zfXML, xsltFRFilename, mainSchematronSectionErrorTypeCode, ESeverity.error); + } + } if ("CII".equals(context.getFormat()) && ("2".equals(context.getGeneration()))) { @@ -546,6 +537,16 @@ public class XMLValidator extends Validator { } } + private String getXPathString(Document doc, String expression) throws IrrecoverableValidationError { + try { + XPath xpath = XPathFactory.newInstance().newXPath(); + String value = (String) xpath.compile(expression).evaluate(doc, XPathConstants.STRING); + return value == null ? "" : value.trim(); + } catch (XPathExpressionException e) { + throw new IrrecoverableValidationError(e.getMessage()); + } + } + public void validateXR(String xml, ESeverity errorImpact) throws IrrecoverableValidationError { //Guideline ID=urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_1.2 or diff --git a/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java b/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java index dcd64c7d..f71b79dd 100644 --- a/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java +++ b/validator/src/main/java/org/mustangproject/validator/ZUGFeRDValidator.java @@ -17,9 +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; import org.apache.commons.io.IOUtils; import org.dom4j.DocumentException; @@ -143,24 +141,7 @@ public class ZUGFeRDValidator { boolean isXML = false; 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(); + DocumentBuilder db = XMLTools.getDocumentBuilder(true); content = XMLTools.removeBOM(content); xmlAsString = new String(content, StandardCharsets.UTF_8); diff --git a/validator/src/main/resources/xslt/XP_Z12_012/20260216_BR-FR-Flux2-Schematron-CII_V1.3.0.xsl b/validator/src/main/resources/xslt/XP_Z12_012/20260216_BR-FR-Flux2-Schematron-CII_V1.3.0.xsl new file mode 100644 index 00000000..15408488 --- /dev/null +++ b/validator/src/main/resources/xslt/XP_Z12_012/20260216_BR-FR-Flux2-Schematron-CII_V1.3.0.xsl @@ -0,0 +1,5617 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + / + + + + + + *: + + [namespace-uri()=' + + '] + + + + [ + + ] + + + + / + + @ + + + @*[local-name()=' + + ' and namespace-uri()=' + + '] + + + + + + + + / + + + [ + + ] + + + + /@ + + + + + + + / + + + [ + + ] + + + + /@ + + + + + + + + + + + + + + + + + + + + + + + . + + + + U + + U + + + + U. + + n + + + + U. + + _ + + _ + + + + + + + + +   +   +   + + + + + + + + + + + + BR-FR-01_BR-FR-02 + BR-FR-01 — Validation de la longueur et du format des identifiants de facture + + + + + + + + BR-FR-03 + BR-FR-03 — Validation de l'année dans les dates (2000–2099) + + + + + + + + BR-FR-04 + BR-FR-04 — Validation du code type de document + + + + + + + + BR-FR-05 + BR-FR-05 — Présence obligatoire des mentions légales dans les notes (BG-1) + + + + + + + + BR-FR-06 + BR-FR-06 — Unicité des codes sujets dans les notes (BG-1) + + + + + + + + BR-FR-08 + BR-FR-08 — Validation du mode de facturation (BT-23) + + + + + + + + BR-FR-09 + BR-FR-09 — Cohérence entre SIRET (GlobalID) et SIREN (ID) + + + + + + + + BR-FR-10 + BR-FR-10 — SIREN du vendeur obligatoire et valide (BT-30) + + + + + + + + BR-FR-11 + BR-FR-11 — SIREN obligatoire et valide si traitement BAR/B2B (BT-47) + + + + + + + + BR-FR-12 + BR-FR-12 — Vérification de la présence du BT-49 + + + + + + + + BR-FR-13 + BR-FR-13 — Vérification de la présence du BT-34 + + + + + + + + BR-FR-15 + BR-FR-15 — Vérification des codes de catégorie de TVA (BT-95, BT-102, BT-118, BT-151) + + + + + + + + BR-FR-16 + BR-FR-16 — Vérification des taux de TVA autorisés (BT-96, BT-103, BT-119, BT-152) + + + + + + + + BR-FR-17 + BR-FR-17 — Codes autorisés pour qualifier les pièces jointes (BT-123) + + + + + + + + BR-FR-18 + BR-FR-18 — Un seul document additionnel avec la description 'LISIBLE' (BT-123) + + + + + + + + BR-FR-20 + BR-FR-20 — Vérification du traitement associé à une note avec code sujet 'BAR' (BT-21) + + + + + + + + BR-FR-21 + BR-FR-21 — Vérification du BT-49 en cas de traitement BAR/B2B et hors cas autofacture + + + + + + + + BR-FR-22 + BR-FR-22 — Vérification du BT-34 en cas de traitement BAR/B2B et en autofacture + + + + + + + + BR-FR-23 + BR-FR-23 — Validation du format des adresses électroniques avec schemeID = 0225 (CII) + + + + + + + + BR-FR-24 + BR-FR-24 — Validation du format des identifiants privés avec schemeID = 0224 (CII) + + + + + + + + BR-FR-25 + BR-FR-25 — Longueur maximale des adresses électroniques (CII) + + + + + + + + BR-FR-26 + BR-FR-26 — Longueur maximale des identifiants privés avec schemeID = 0224 + + + + + + + + BR-FR-27 + BR-FR-27 — Validation du groupe Attribut d’article (BG-32) + + + + + + + + BR-FR-28 + BR-FR-28 — Validation de la valeur d’attribut d’article (BG-32) + + + + + + + + BR-FR-29 + BR-FR-29 — Vérification des identifiants d’objets facturés (BT-18) + + + + + + + + BR-FR-30 + BR-FR-30 — Vérification des identifiants d’objets facturés à la ligne (BT-128) + + + + + + + + BR-FR-31 + BR-FR-31 — Note avec code sujet BAR : une seule valeur possible dans la liste + + + + + + + + BR-FR-CO-03 + BR-FR-CO-03 — Présence obligatoire du contrat et de la période de facturation si type de facture = 262 (Avoir Remise Globale) + + + + + + + + BR-FR-CO-04 + BR-FR-CO-04 — Une seule référence à une facture antérieure obligatoire pour les factures rectificatives (BT-3) + + + + + + + + BR-FR-CO-05 + BR-FR-CO-05 — Référence obligatoire à une facture antérieure pour les avoirs (BT-3) + + + + + + + + BR-FR-CO-07 + BR-FR-CO-07 — La date d’échéance (BT-9) doit être postérieure ou égale à la date de facture (BT-2), sauf cas particuliers + + + + + + + + BR-FR-CO-08 + BR-FR-CO-08 — Incompatibilité entre cadre de facturation (BT-23) et type de facture (BT-3) + + + + + + + + BR-FR-CO-09 + BR-FR-CO-09 — Contrôle des montants et de la date d’échéance pour les factures déjà payées (BT-23) + + + + + + + + BR-FR-CO-10 + BR-FR-CO-10 — Vérification de la présence du schéma d’identifiant global (BT-29 et équivalents) et unicité du schemeID + + + + + + + + BR-FR-CO-12 + BR-FR-CO-12 — Contrôle des devises et du montant de TVA en comptabilité si la facture n’est pas en EUR + + + + + + + + BR-FR-CO-14 + BR-FR-CO-14 — Vérification de la note TXD pour les vendeurs membres d’un assujetti unique + + + + + + + + BR-FR-CO-15 + BR-FR-CO-15 — Présence du représentant fiscal si le vendeur est membre d’un assujetti unique (BT-29-1 = 0231) + + + + + + + + BR-FR-DEC-01 + BR-FR-DEC-01 — Format des montants numériques (max 19 caractères, 2 décimales, séparateur « . ») + + + + + + + + BR-FR-DEC-02 + BR-FR-DEC-02 — Format des quantités numériques (max 19 caractères, 4 décimales, séparateur « . ») + + + + + + + + BR-FR-DEC-03 + BR-FR-DEC-03 — Format des montants positifs (max 19 caractères, 6 décimales, séparateur « . ») + + + + + + + + BR-FR-DEC-04 + BR-FR-DEC-04 — Format des taux de TVA (max 4 caractères, 2 décimales, séparateur « . ») + + + + + + + + BR-FR-MV-01 + BR-FR-MV-01 — Vérification du sous-type de ligne lorsque le cadre de facturation est S8, B8 ou M8 + + + + + + + + BR-FR-MV-02 + BR-FR-MV-02 — Vérification de la présence d'une ligne GROUP sans parent lorsque le cadre de facturation est S8, B8 ou M8 + + + + + + + + BR-FR-MV-03 + BR-FR-MV-03 — Vérification des données obligatoires pour les lignes GROUP sans parent lorsque le cadre de facturation est S8, B8 ou M8 + + + + + + + + BR-FR-MV-05 + BR-FR-MV-05 — Vérification de la cohérence des totaux HT entre la ligne GROUP et ses lignes enfants + + + + + + + + BR-FR-MV-06 + BR-FR-MV-06 — Vérification de la cohérence de l'identifiant légal du vendeur entre une ligne et sa ligne parent + + + + + + + + BR-FR-MV-07 + BR-FR-MV-07 — Vérification de la cohérence du numéro de facture codifié AFL entre une ligne et sa ligne parent + + + + + + + + BR-FR-MV-08 + BR-FR-MV-08 — Vérification de la raison d'exemption TVA contenant le numéro de sous-facture en ligne entre # + + + + + + + + BR-FR-MV-09 + BR-FR-MV-09 — Vérification de la cohérence du montant total TVA pour une ligne GROUP avec la somme des ventilations TVA liées + + + + + + + + BR-FR-MV-10 + BR-FR-MV-10 — Vérification de la cohérence du montant total avec TVA pour une ligne GROUP + + + + + + + + BR-FR-MV-11 + BR-FR-MV-11 — Vérification de la cohérence entre l'identifiant de facture à la ligne (AFL) et le numéro de facture (BT-1) pour le Vendeur principal + + + + + + + + BR-FR-MV-12 + BR-FR-MV-12 — Vérification de l'unicité des numéros de facture AFL pour les lignes GROUP sans parent + + + + + + + + BR-FR-MV-13 + BR-FR-MV-13 — Vérification que le code type de facture (BT-3) n'est pas un type auto-facturé interdit + + + + + + + + + BR-FR-01 — Validation de la longueur et du format des identifiants de facture + + + + + + + + + BR-FR-01_BT-1 + warning + + + + + BR-FR-01/BT-1 : L'identifiant de facture (ram:ID) ne doit pas dépasser 35 caractères. Valeur actuelle : " + + ". + Veuillez vérifier que l'identifiant respecte cette limite. + + + + + + + + + + BR-FR-02_BT-1 + warning + + + + + BR-FR-02/BT-1 : L'identifiant de facture (ram:ID) contient des caractères non autorisés. Valeur actuelle : " + + ". + Seuls les caractères alphanumériques et les symboles + - _ / sont autorisés, sans espaces. + + + + + + + + + + + + + + + BR-FR-01_BT-25 + warning + + + + + BR-FR-01/BT-25 : L'identifiant de facture référencée (ram:IssuerAssignedID) ne doit pas dépasser 35 caractères. Valeur actuelle : " + + ". + Veuillez vérifier que l'identifiant respecte cette limite. + + + + + + + + + + BR-FR-02_BT-25 + warning + + + + + BR-FR-02/BT-25 : L'identifiant de facture référencée (ram:IssuerAssignedID) contient des caractères non autorisés. Valeur actuelle : " + + ". + Seuls les caractères alphanumériques et les symboles + - _ / sont autorisés, sans espaces. + + + + + + + + + + + + + + + BR-FR-01_EXT-FR-FE-136 + warning + + + + + BR-FR-01/EXT-FR-FE-136 : L'identifiant de facture référencée en ligne (ram:IssuerAssignedID) ne doit pas dépasser 35 caractères. Valeur actuelle : " + + ". + Veuillez vérifier que l'identifiant respecte cette limite. + + + + + + + + + + BR-FR-02_EXT-FR-FE-136 + warning + + + + + BR-FR-02/EXT-FR-FE-136 : L'identifiant de facture référencée en ligne (ram:IssuerAssignedID) contient des caractères non autorisés. Valeur actuelle : " + + ". + Seuls les caractères alphanumériques et les symboles + - _ / sont autorisés, sans espaces. + + + + + + + + + + + + BR-FR-03 — Validation de l'année dans les dates (2000–2099) + + + + + + + + + BR-FR-03_BT-2 + warning + + + + + BR-FR-03/BT-2 : La date d’émission (udt:DateTimeString) doit contenir une année comprise entre 2000 et 2099, au format AAAAMMJJ. Valeur actuelle : " + + ". + Veuillez vérifier que l’année est correcte et que le format est conforme. + + + + + + + + + + + + + + + BR-FR-03_BT-7 + warning + + + + + BR-FR-03/BT-7 : La date de fait générateur de la taxe (udt:DateString) doit contenir une année entre 2000 et 2099. Valeur actuelle : " + + ". + Veuillez vérifier la validité de la date. + + + + + + + + + + + + + + + BR-FR-03_BT-9 + warning + + + + + BR-FR-03/BT-9 : La date d’échéance (udt:DateTimeString) doit contenir une année entre 2000 et 2099. Valeur actuelle : " + + ". + Veuillez vérifier la validité de la date. + + + + + + + + + + + + + + + BR-FR-03_BT-26 + warning + + + + + BR-FR-03/BT-26 : La date d’émission de la facture référencée (qdt:DateTimeString) doit contenir une année entre 2000 et 2099. Valeur actuelle : " + + ". + Veuillez vérifier la validité de la date. + + + + + + + + + + + + + + + BR-FR-03_BT-72 + warning + + + + + BR-FR-03/BT-72 : La date de livraison effective (udt:DateTimeString) doit contenir une année entre 2000 et 2099. Valeur actuelle : " + + ". + Veuillez vérifier la validité de la date. + + + + + + + + + + + + + + + BR-FR-03_BT-73 + warning + + + + + BR-FR-03/BT-73 : La date de début de période de facturation (udt:DateTimeString) doit contenir une année entre 2000 et 2099. Valeur actuelle : " + + ". + Veuillez vérifier la validité de la date. + + + + + + + + + + + + + + + BR-FR-03_BT-74 + warning + + + + + BR-FR-03/BT-74 : La date de fin de période de facturation (udt:DateTimeString) doit contenir une année entre 2000 et 2099. Valeur actuelle : " + + ". + Veuillez vérifier la validité de la date. + + + + + + + + + + + + + + + BR-FR-03_EXT-FR-FE-138 + warning + + + + + BR-FR-03/EXT-FR-FE-138 : La date d’émission de la facture référencée en ligne (qdt:DateTimeString) doit contenir une année entre 2000 et 2099. Valeur actuelle : " + + ". + Veuillez vérifier la validité de la date. + + + + + + + + + + + + + + + BR-FR-03_EXT-FR-FE-158 + warning + + + + + BR-FR-03/EXT-FR-FE-158 : La date de livraison effective en ligne (udt:DateTimeString) doit contenir une année entre 2000 et 2099. Valeur actuelle : " + + ". + Veuillez vérifier la validité de la date. + + + + + + + + + + + + + + + BR-FR-03_EXT-FR-FE-134 + warning + + + + + BR-FR-03/BT-134 : La date de début de période de facturation en ligne (udt:DateTimeString) doit contenir une année entre 2000 et 2099. Valeur actuelle : " + + ". + Veuillez vérifier la validité de la date. + + + + + + + + + + + + + + + BR-FR-03_EXT-FR-FE-135 + warning + + + + + BR-FR-03/BT-135 : La date de fin de période de facturation en ligne (udt:DateTimeString) doit contenir une année entre 2000 et 2099. Valeur actuelle : " + + ". + Veuillez vérifier la validité de la date. + + + + + + + + + + + + BR-FR-04 — Validation du code type de document + + + + + + + + + BR-FR-04_BT-3 + warning + + + + + BR-FR-04/BT-3 : Le code type de document (ram:TypeCode) n’est pas autorisé. Valeurs acceptées : + 380, 389, 393, 501, 386, 500, 384, 471, 472, 473, 261, 262, 381, 396, 502, 503. + Valeur actuelle : " + + ". + Veuillez utiliser un code conforme à la liste autorisée. Les autres codes définis dans la norme UNTDID 1001 ne doivent pas être utilisés. + + + + + + + + + + + + + + + BR-FR-04_EXT-FR-FE-02 + warning + + + + + BR-FR-04/EXT-FR-FE-02 : Le code type de document référencé (ram:TypeCode) n’est pas autorisé. Valeurs acceptées : + 380, 389, 393, 501, 386, 500, 384, 471, 472, 473, 261, 262, 381, 396, 502, 503. + Valeur actuelle : " + + ". + Veuillez utiliser un code conforme à la liste autorisée. + + + + + + + + + + + + + + + BR-FR-04_EXT-FR-FE-137 + warning + + + + + BR-FR-04/EXT-FR-FE-137 : Le code type de document référencé en ligne (ram:TypeCode) n’est pas autorisé. Valeurs acceptées : + 380, 389, 393, 501, 386, 500, 384, 471, 472, 473, 261, 262, 381, 396, 502, 503. + Valeur actuelle : " + + ". + Veuillez utiliser un code conforme à la liste autorisée. + + + + + + + + + + + + BR-FR-05 — Présence obligatoire des mentions légales dans les notes (BG-1) + + + + + + + + + + BR-FR-05_BT-22_PMT + warning + + + + + BR-FR-05/BT-22 : La mention relative aux frais de recouvrement (code PMT) est absente. Elle est obligatoire dans les notes (BG-1). + + + + + + + + + + BR-FR-05_BT-22_PMD + warning + + + + + BR-FR-05/BT-22 : La mention relative aux pénalités de retard (code PMD) est absente. Elle est obligatoire dans les notes (BG-1). + + + + + + + + + + BR-FR-05_BT-22_AAB + warning + + + + + BR-FR-05/BT-22 : La mention relative à l’escompte ou à son absence (code AAB) est absente. Elle est obligatoire dans les notes (BG-1). + + + + + + + + + + + + BR-FR-06 — Unicité des codes sujets dans les notes (BG-1) + + + + + + + + + + BR-FR-06_BT-21_PMT + warning + + + + + BR-FR-06/BT-21 : Le code sujet PMT (indemnité forfaitaire pour frais de recouvrement) ne doit apparaître qu'une seule fois dans les notes (BG-1). + + + + + + + + + + BR-FR-06_BT-21_PMD + warning + + + + + BR-FR-06/BT-21 : Le code sujet PMD (pénalités de retard) ne doit apparaître qu'une seule fois dans les notes (BG-1). + + + + + + + + + + BR-FR-06_BT-21_AAB + warning + + + + + BR-FR-06/BT-21 : Le code sujet AAB (mention d’escompte ou d’absence d’escompte) ne doit apparaître qu'une seule fois dans les notes (BG-1). + + + + + + + + + + BR-FR-06_BT-21_TXD + warning + + + + + BR-FR-06/BT-21 : Le code sujet TXD (mention de taxe) ne doit apparaître qu'une seule fois dans les notes (BG-1). + + + + + + + + + + + + BR-FR-08 — Validation du mode de facturation (BT-23) + + + + + + + + + BR-FR-08_BT-23 + warning + + + + + BR-FR-08/BT-23 : La valeur du mode de facturation (ram:ID) n’est pas autorisée. Valeurs acceptées : B1, S1, M1, B2, S2, M2, B4, S4, M4, S5, S6, B7, S7. + Valeur actuelle : " + + ". + Veuillez utiliser une valeur conforme à la liste des modes de facturation autorisés. + + + + + + + + + + + + BR-FR-09 — Cohérence entre SIRET (GlobalID) et SIREN (ID) + + + + + + + + + + + BR-FR-09_BT-29 + warning + + + + + BR-FR-09/BT-29 : Le SIRET doit contenir 14 chiffres et commencer par le SIREN. SIRET : " + + ", SIREN : " + + ". + + + + + + + + + + + + + + + + + BR-FR-09_BT-46 + warning + + + + + BR-FR-09/BT-46 : Le SIRET doit contenir 14 chiffres et commencer par le SIREN. SIRET : " + + ", SIREN : " + + ". + + + + + + + + + + + + + + + + + BR-FR-09_BT-60 + warning + + + + + BR-FR-09/BT-60 : Le SIRET doit contenir 14 chiffres et commencer par le SIREN. SIRET : " + + ", SIREN : " + + ". + + + + + + + + + + + + + + + + + BR-FR-09_EXT-FR-FE-06 + warning + + + + + BR-FR-09/EXT-FR-FE-06 : Le SIRET doit contenir 14 chiffres et commencer par le SIREN. SIRET : " + + ", SIREN : " + + ". + + + + + + + + + + + + + + + + + BR-FR-09_EXT-FR-FE-46 + warning + + + + + BR-FR-09/EXT-FR-FE-46 : Le SIRET doit contenir 14 chiffres et commencer par le SIREN. SIRET : " + + ", SIREN : " + + ". + + + + + + + + + + + + + + + + + BR-FR-09_EXT-FR-FE-69 + warning + + + + + BR-FR-09/EXT-FR-FE-69 : Le SIRET doit contenir 14 chiffres et commencer par le SIREN. SIRET : " + + ", SIREN : " + + ". + + + + + + + + + + + + + + + + + BR-FR-09_EXT-FR-FE-92 + warning + + + + + BR-FR-09/EXT-FR-FE-92 : Le SIRET doit contenir 14 chiffres et commencer par le SIREN. SIRET : " + + ", SIREN : " + + ". + + + + + + + + + + + + + + + + + BR-FR-09_EXT-FR-FE-115 + warning + + + + + BR-FR-09/EXT-FR-FE-115 : Le SIRET doit contenir 14 chiffres et commencer par le SIREN. SIRET : " + + ", SIREN : " + + ". + + + + + + + + + + + + + + + + + BR-FR-09_BT-71 + warning + + + + + BR-FR-09/BT-71 : Le SIRET doit contenir 14 chiffres et commencer par le SIREN. SIRET : " + + ", SIREN : " + + ". + + + + + + + + + + + + + + + + + BR-FR-09_EXT-FR-FE-146 + warning + + + + + BR-FR-09/EXT-FR-FE-146 : Le SIRET doit contenir 14 chiffres et commencer par le SIREN. SIRET : " + + ", SIREN : " + + ". + + + + + + + + + + + + BR-FR-10 — SIREN du vendeur obligatoire et valide (BT-30) + + + + + + + + + + BR-FR-10_BT-30 + warning + + + + + BR-FR-10/BT-30 : Le SIREN du vendeur (ram:ID) est obligatoire et doit être composé exactement de 9 chiffres. Valeur actuelle : " + + ". + Veuillez renseigner un identifiant SIREN valide. + + + + + + + + + + + + BR-FR-11 — SIREN obligatoire et valide si traitement BAR/B2B (BT-47) + + + + + + + + + + + BR-FR-11_BT-47 + warning + + + + + BR-FR-11/BT-47 : Si une note contient le code sujet BAR avec la valeur 'B2B', alors le SIREN de l’acheteur (BT-47, ram:ID) est obligatoire et doit être composé exactement de 9 chiffres. Valeur actuelle : " + + ". + Veuillez renseigner un identifiant SIREN valide. + + + + + + + + + + + + BR-FR-12 — Vérification de la présence du BT-49 + + + + + + + + + + BR-FR-12_BT-49 + warning + + + + + BR-FR-12/BT-49 : Le BT-49 est obligatoire. + Valeur actuelle : BT-49=" + + ". + + + + + + + + + + + + BR-FR-13 — Vérification de la présence du BT-34 + + + + + + + + + + BR-FR-13_BT-34 + warning + + + + + BR-FR-13/BT-34 : Le BT-34 est obligatoire. + Valeur actuelle : BT-34=" + + ". + + + + + + + + + + + + BR-FR-15 — Vérification des codes de catégorie de TVA (BT-95, BT-102, BT-118, BT-151) + + + + + + + + + + BR-FR-15_BT-95_BT-102 + warning + + + + + BR-FR-15/BT-95 ou BT-102 : Code de catégorie de TVA invalide. Valeur actuelle : " + + ". Veuillez utiliser un code valide : S, E, AE, K, G, O, Z. + + + + + + + + + + BR-FR-15_BT-95_BT-102_LM + warning + + + + + BR-FR-15/BT-95 ou BT-102 : Les codes 'L' et 'M' ne sont pas pertinents en France. Valeur actuelle : " + + ". + + + + + + + + + + + + + + + + BR-FR-15_BT-95_BT-118 + warning + + + + + BR-FR-15/BT-118 : Code de catégorie de TVA invalide. Valeur actuelle : " + + ". Veuillez utiliser un code valide : S, E, AE, K, G, O, Z. + + + + + + + + + + BR-FR-15_BT-95_BT-118_LM + warning + + + + + BR-FR-15/BT-118 : Les codes 'L' et 'M' ne sont pas pertinents en France. Valeur actuelle : " + + ". + + + + + + + + + + + + + + + + BR-FR-15_BT-95_BT-151 + warning + + + + + BR-FR-15/BT-151 : Code de catégorie de TVA invalide. Valeur actuelle : " + + ". Veuillez utiliser un code valide : S, E, AE, K, G, O, Z. + + + + + + + + + + BR-FR-15_BT-95_BT-151_LM + warning + + + + + BR-FR-15/BT-151 : Les codes 'L' et 'M' ne sont pas pertinents en France. Valeur actuelle : " + + ". + + + + + + + + + + + + BR-FR-16 — Vérification des taux de TVA autorisés (BT-96, BT-103, BT-119, BT-152) + + + + + + + + + + BR-FR-16_BT-96_BT-103 + warning + + + + + BR-FR-16/BT-96 ou BT-103 : Le taux de TVA (BT-96) doit être exprimé en pourcentage sans symbole « % » et faire partie des valeurs autorisées. Taux fourni : " + + ". + + + + + + + + + + + + + + + + BR-FR-16_BT-119 + warning + + + + + BR-FR-16/BT-119 : Le taux de TVA (BT-119) doit être exprimé en pourcentage sans symbole « % » et faire partie des valeurs autorisées. Taux fourni : " + + ". + + + + + + + + + + + + + + + + BR-FR-16_BT-152 + warning + + + + + BR-FR-16/BT-152 : Le taux de TVA (BT-152) doit être exprimé en pourcentage sans symbole « % » et faire partie des valeurs autorisées. Taux fourni : " + + ". + + + + + + + + + + + + BR-FR-17 — Codes autorisés pour qualifier les pièces jointes (BT-123) + + + + + + + + + + BR-FR-17_BT-123 + warning + + + + + BR-FR-17/BT-123 : Le code de qualification de la pièce jointe " + + " est invalide. Il doit appartenir à la liste des codes autorisés. Veuillez corriger la valeur de BT-123. + + + + + + + + + + + + BR-FR-18 — Un seul document additionnel avec la description 'LISIBLE' (BT-123) + + + + + + + + + + BR-FR-18_BT-123 + warning + + + + + BR-FR-18/BT-123 : Il ne peut y avoir **qu’un seul** document additionnel (BG-24) dont la description (BT-123) est "LISIBLE". + Nombre de documents trouvés : + + . + Veuillez supprimer les doublons ou corriger les descriptions. + + + + + + + + + + + + BR-FR-20 — Vérification du traitement associé à une note avec code sujet 'BAR' (BT-21) + + + + + + + + + + BR-FR-20_BT-21 + warning + + + + + BR-FR-20/BT-21 : Lorsqu’une note a pour code sujet « BAR » (BT-21), la valeur associée (BT-22, contenu de la note) doit être l’une des suivantes : B2B, B2BINT, B2C, OUTOFSCOPE, ARCHIVEONLY. + Valeur fournie : " + + ". Veuillez corriger la valeur ou retirer le code sujet « BAR ». + + + + + + + + + + + + BR-FR-21 — Vérification du BT-49 en cas de traitement BAR/B2B et hors cas autofacture + + + + + + + + + + + + + + + + BR-FR-21_BT-49 + warning + + + + + BR-FR-21/BT-49 : Si le traitement est BAR/B2B et que le type de document (BT-3) n’est pas en autofacture (389, 501, 500, 471, 473, 261, 502), alors le BT-49 (EndpointID) doit commencer par le SIREN (BT-47) et le BT-49-1 (schemeID) doit être égal à "0225". + Valeurs actuelles : EndpointID=" + + ", schemeID=" + + ", SIREN=" + + ". + + + + + + + + + + + + BR-FR-22 — Vérification du BT-34 en cas de traitement BAR/B2B et en autofacture + + + + + + + + + + + + + + + + BR-FR-22_BT-34 + warning + + + + + BR-FR-22/BT-34 : Si le traitement est BAR/B2B et que le type de document (BT-3) est en autofacture (389, 501, 500, 471, 473, 261, 502), alors le BT-34 (EndpointID du vendeur) doit commencer par le SIREN (BT-30) et le BT-34-1 (schemeID) doit être égal à "0225". + Valeurs actuelles : EndpointID=" + + ", schemeID=" + + ", SIREN=" + + ". + + + + + + + + + + + + BR-FR-23 — Validation du format des adresses électroniques avec schemeID = 0225 (CII) + + + + + + + + + BR-FR-23_BT-34 + warning + + + + + BR-FR-23/BT-34 : L'adresse électronique (ram:URIID) ne respecte pas le format autorisé. Valeur actuelle : " + + ". + Seuls les caractères alphanumériques et les symboles "-", "_", "." sont autorisés. + + + + + + + + + + + + + + + BR-FR-23_BT-49 + warning + + + + + BR-FR-23/BT-49 : L'adresse électronique (ram:URIID) ne respecte pas le format autorisé. Valeur actuelle : " + + ". + Seuls les caractères alphanumériques et les symboles "-", "_", "." sont autorisés. + + + + + + + + + + + + + + + BR-FR-23_EXT-FR-FE-12 + warning + + + + + BR-FR-23/EXT-FR-FE-12 : L'adresse électronique (ram:URIID) ne respecte pas le format autorisé. Valeur actuelle : " + + ". + Seuls les caractères alphanumériques et les symboles "-", "_", "." sont autorisés. + + + + + + + + + + + + + + + BR-FR-23_EXT-FR-FE-29 + warning + + + + + BR-FR-23/EXT-FR-FE-29 : L'adresse électronique (ram:URIID) ne respecte pas le format autorisé. Valeur actuelle : " + + ". + Seuls les caractères alphanumériques et les symboles "-", "_", "." sont autorisés. + + + + + + + + + + + + + + + BR-FR-23_EXT-FR-FE-52 + warning + + + + + BR-FR-23/EXT-FR-FE-52 : L'adresse électronique (ram:URIID) ne respecte pas le format autorisé. Valeur actuelle : " + + ". + Seuls les caractères alphanumériques et les symboles "-", "_", "." sont autorisés. + + + + + + + + + + + + + + + BR-FR-23_EXT-FR-FE-75 + warning + + + + + BR-FR-23/EXT-FR-FE-75 : L'adresse électronique (ram:URIID) ne respecte pas le format autorisé. Valeur actuelle : " + + ". + Seuls les caractères alphanumériques et les symboles "-", "_", "." sont autorisés. + + + + + + + + + + + + + + + BR-FR-23_EXT-FR-FE-98 + warning + + + + + BR-FR-23/EXT-FR-FE-98 : L'adresse électronique (ram:URIID) ne respecte pas le format autorisé. Valeur actuelle : " + + ". + Seuls les caractères alphanumériques et les symboles "-", "_", "." sont autorisés. + + + + + + + + + + + + + + + BR-FR-23_EXT-FR-FE-121 + warning + + + + + BR-FR-23/EXT-FR-FE-121 : L'adresse électronique (ram:URIID) ne respecte pas le format autorisé. Valeur actuelle : " + + ". + Seuls les caractères alphanumériques et les symboles "-", "_", "." sont autorisés. + + + + + + + + + + + + BR-FR-24 — Validation du format des identifiants privés avec schemeID = 0224 (CII) + + + + + + + + + BR-FR-24_BT-29 + warning + + + + + BR-FR-24/BT-29 : L'identifiant privé (ram:GlobalID) ne respecte pas le format autorisé. Valeur actuelle : " + + ". + Seuls les caractères alphanumériques et les symboles "-", "_", "." sont autorisés. + + + + + + + + + + + + + + + BR-FR-24_BT-46 + warning + + + + + BR-FR-24/BT-46 : L'identifiant privé (ram:GlobalID) ne respecte pas le format autorisé. Valeur actuelle : " + + ". + Seuls les caractères alphanumériques et les symboles "-", "_", "." sont autorisés. + + + + + + + + + + + + BR-FR-25 — Longueur maximale des adresses électroniques (CII) + + + + + + + + + BR-FR-25_BT-34 + warning + + + + + BR-FR-25/BT-34 : L'adresse électronique (ram:URIID) dépasse 125 caractères. Valeur actuelle : " + + ". + Veuillez raccourcir cette valeur pour respecter la limite. + + + + + + + + + + + + + + + BR-FR-25_BT-49 + warning + + + + + BR-FR-25/BT-49 : L'adresse électronique (ram:URIID) dépasse 125 caractères. Valeur actuelle : " + + ". + Veuillez raccourcir cette valeur pour respecter la limite. + + + + + + + + + + + + + + + BR-FR-25_EXT-FR-FE-12 + warning + + + + + BR-FR-25/EXT-FR-FE-12 : L'adresse électronique (ram:URIID) dépasse 125 caractères. Valeur actuelle : " + + ". + Veuillez raccourcir cette valeur pour respecter la limite. + + + + + + + + + + + + + + + BR-FR-25_EXT-FR-FE-29 + warning + + + + + BR-FR-25/EXT-FR-FE-29 : L'adresse électronique (ram:URIID) dépasse 125 caractères. Valeur actuelle : " + + ". + Veuillez raccourcir cette valeur pour respecter la limite. + + + + + + + + + + + + + + + BR-FR-25_EXT-FR-FE-52 + warning + + + + + BR-FR-25/EXT-FR-FE-52 : L'adresse électronique (ram:URIID) dépasse 125 caractères. Valeur actuelle : " + + ". + Veuillez raccourcir cette valeur pour respecter la limite. + + + + + + + + + + + + + + + BR-FR-25_EXT-FR-FE-75 + warning + + + + + BR-FR-25/EXT-FR-FE-75 : L'adresse électronique (ram:URIID) dépasse 125 caractères. Valeur actuelle : " + + ". + Veuillez raccourcir cette valeur pour respecter la limite. + + + + + + + + + + + + + + + BR-FR-25_EXT-FR-FE-98 + warning + + + + + BR-FR-25/EXT-FR-FE-98 : L'adresse électronique (ram:URIID) dépasse 125 caractères. Valeur actuelle : " + + ". + Veuillez raccourcir cette valeur pour respecter la limite. + + + + + + + + + + + + + + + BR-FR-25_EXT-FR-FE-121 + warning + + + + + BR-FR-25/EXT-FR-FE-121 : L'adresse électronique (ram:URIID) dépasse 125 caractères. Valeur actuelle : " + + ". + Veuillez raccourcir cette valeur pour respecter la limite. + + + + + + + + + + + + BR-FR-26 — Longueur maximale des identifiants privés avec schemeID = 0224 + + + + + + + + + BR-FR-26_BT-29 + warning + + + + + BR-FR-26/BT-29 : L'identifiant privé (ram:GlobalID) dépasse 100 caractères. Valeur actuelle : " + + ". + Veuillez raccourcir cette valeur pour respecter la limite. + + + + + + + + + + + + + + + BR-FR-26_BT-46 + warning + + + + + BR-FR-26/BT-46 : L'identifiant privé (ram:GlobalID) dépasse 100 caractères. Valeur actuelle : " + + ". + Veuillez raccourcir cette valeur pour respecter la limite. + + + + + + + + + + + + BR-FR-27 — Validation du groupe Attribut d’article (BG-32) + + + + + + + + + BR-FR-27_BG-32 + warning + + + + + BR-FR-27/BG-32 : Le groupe Attribut d’article (BG-32) doit contenir soit un nom d’attribut d’article (BT-160 : ram:Description), soit un code d’attribut d’article (EXT-FR-FE-159 : ram:TypeCode). + Aucun des deux éléments n’a été trouvé dans le contexte ApplicableProductCharacteristic. + Veuillez ajouter au moins l’un des deux éléments pour respecter la structure attendue. + + + + + + + + + + + + + + + BR-FR-27_BT-160 + warning + + + + + BR-FR-27/BT-160 : Le nom d’attribut d’article (ram:Description) ne doit pas être vide. + Valeur actuelle : " + + ". + Veuillez fournir un nom d’attribut valide ou utiliser un code à la place. + + + + + + + + + + + + + + + BR-FR-27_EXT-FR-FE-159 + warning + + + + + BR-FR-27/EXT-FR-FE-159 : Le code d’attribut d’article (ram:TypeCode) ne doit pas être vide. + Valeur actuelle : " + + ". + Veuillez fournir un code d’attribut valide ou utiliser un nom à la place. + + + + + + + + + + + + BR-FR-28 — Validation de la valeur d’attribut d’article (BG-32) + + + + + + + + + BR-FR-28_BT-161-1 + warning + + + + + BR-FR-28/BT-161 : Le groupe Attribut d’article (BG-32) doit contenir soit une valeur d’attribut (BT-161 : ram:Value), soit une valeur d’attribut avec unité de mesure (EXT-FR-FE-160 : ram:ValueMeasure) accompagnée de son unité (EXT-FR-FE-161 : @unitCode), et pas les deux. + Veuillez fournir une valeur d’attribut ou une valeur mesurée avec son unité, et pas les deux. + + + + + + + + + + + + + + + BR-FR-28_BT-161-2 + warning + + + + + BR-FR-28/BT-161 : La valeur d’attribut (ram:Value) ne doit pas être vide. + Valeur actuelle : " + + ". + Veuillez fournir une valeur d’attribut valide ou utiliser une mesure avec unité. + + + + + + + + + + + + + + + BR-FR-28_EXT-FR-FE-160 + warning + + + + + BR-FR-28/EXT-FR-FE-160 : La valeur mesurée (ram:ValueMeasure) ne doit pas être vide. + Valeur actuelle : " + + ". + Veuillez fournir une valeur mesurée valide accompagnée de son unité. + + + + + + + + + + + + + + + BR-FR-28_EXT-FR-FE-161 + warning + + + + + BR-FR-28/EXT-FR-FE-161 : L’unité de mesure (@unitCode) ne doit pas être vide lorsqu’une valeur mesurée est fournie. + Valeur actuelle : " + + ". + Veuillez spécifier une unité de mesure conforme. + + + + + + + + + + + BR-FR-29 — Vérification des identifiants d’objets facturés (BT-18) + + + + + + + + + BR-FR-29_BT-18 + warning + + + + + BR-FR-29/BT-18 : Parmi les identifiants d’objets facturés (BT-18), les schémas d’identification "AFL" et "AVV" ne doivent être présents qu’une seule fois chacun. + Actuellement : AFL = + + occurrence(s), AVV = + + occurrence(s). + Veuillez supprimer les doublons pour respecter la règle. + + + + + + + + + + + + + + + BR-FR-29_AFL + warning + + + + + BR-FR-29/AFL : L’identifiant associé au schéma "AFL" (ram:IssuerAssignedID) ne doit pas être vide. + Valeur actuelle : " + + ". + + + + + + + + + + + + + + + BR-FR-29_AVV + warning + + + + + BR-FR-29/AVV : L’identifiant associé au schéma "AVV" (ram:IssuerAssignedID) ne doit pas être vide. + Valeur actuelle : " + + ". + + + + + + + + + + + + BR-FR-30 — Vérification des identifiants d’objets facturés à la ligne (BT-128) + + + + + + + + + BR-FR-30_BT-128 + warning + + + + + BR-FR-30/BT-128 : Parmi les identifiants d’objets facturés à la ligne (BT-128), les schémas d’identification "AFL" et "AVV" ne doivent être présents qu’une seule fois chacun. + Actuellement : AFL = + + occurrence(s), AVV = + + occurrence(s). + Veuillez supprimer les doublons pour respecter la règle. + + + + + + + + + + + + + + + BR-FR-30_AFL + warning + + + + + BR-FR-30/AFL : L’identifiant associé au schéma "AFL" (ram:IssuerAssignedID) ne doit pas être vide. + Valeur actuelle : " + + ". + + + + + + + + + + + + + + + BR-FR-30_AVV + warning + + + + + BR-FR-30/AVV : L’identifiant associé au schéma "AVV" (ram:IssuerAssignedID) ne doit pas être vide. + Valeur actuelle : " + + ". + + + + + + + + + + + + BR-FR-31 — Note avec code sujet BAR : une seule valeur possible dans la liste + + + + + + + + + + + BR-FR-30_BT-21 + warning + + + + + BR-FR-30/BT-21 : Lorsque plusieurs notes ont le code sujet « BAR » (BT-21), Il ne peut y avoir qu'une seule valeur associée (BT-22, contenu de la note) parmi l’une des suivantes : B2B, B2BINT, B2C, OUTOFSCOPE, ARCHIVEONLY. + Valeur fournie : " + + " , Nombre de valeurs présentes (pas plus de 1) : + + "/>". Veuillez corriger la valeur ou retirer le code sujet « BAR ». + + + + + + + + + + + + BR-FR-CO-03 — Présence obligatoire du contrat et de la période de facturation si type de facture = 262 (Avoir Remise Globale) + + + + + + + + + + + + + BR-FR-CO-03_BT-3 + warning + + + + + BR-FR-CO-03/BT-3 : Si le code type de la facture (BT-3) est égal à 262 (Avoir Remise Globale), alors : + - Le numéro de contrat (BT-12) doit être présent + - La période de facturation (BG-14) doit être renseignée (dates de début et de fin). + Valeurs actuelles : BT-12=" + + ", période=" + + à + + ". + + + + + + + + + + + + BR-FR-CO-04 — Une seule référence à une facture antérieure obligatoire pour les factures rectificatives (BT-3) + + + + + + + + + + + + BR-FR-CO-04_BT-4 + warning + + + + + BR-FR-CO-04/BT-3 : Si le type de facture (BT-3) est une facture rectificative (384, 471, 472, 473), alors **une et une seule** référence à une facture antérieure (BT-25) avec sa date (BT-26) doit être présente. + Nombre de références valides trouvées : + + . + + + + + + + + + + + + BR-FR-CO-05 — Référence obligatoire à une facture antérieure pour les avoirs (BT-3) + + + + + + + + + + + + + BR-FR-CO-05_BT-3 + warning + + + + + BR-FR-CO-05/BT-3 : Si le type de facture (BT-3) est un avoir (261, 381, 396, 502, 503), alors : + - soit au moins une référence à une facture antérieure (BT-25) avec sa date (BT-26) doit être présente au niveau entête, + - soit chaque ligne (BG-25) doit contenir une référence à une facture antérieure (EXT-FR-FE-136) avec sa date (EXT-FR-FE-138). + Références entête trouvées : + + . + + + + + + + + + + + + BR-FR-CO-07 — La date d’échéance (BT-9) doit être postérieure ou égale à la date de facture (BT-2), sauf cas particuliers + + + + + + + + + + + + + BR-FR-CO-07_BT-9 + warning + + + + + BR-FR-CO-07/BT-9 : La date d’échéance (BT-9), si présente, doit être postérieure ou égale à la date de facture (BT-2), + sauf si la facture est de type acompte (386, 500, 503) ou si le cadre de facturation (BT-23) est B2, S2 ou M2. + Valeurs actuelles : Date facture = " + + ", Date échéance = " + + ", Type = " + + ", Cadre = " + + ". + + + + + + + + + + + + BR-FR-CO-08 — Incompatibilité entre cadre de facturation (BT-23) et type de facture (BT-3) + + + + + + + + + + + BR-FR-CO-08_BT-23 + warning + + + + + BR-FR-CO-08/BT-23 : Si le cadre de facturation (BT-23) est B4, S4 ou M4 (factures définitives après acompte), alors le type de facture (BT-3) ne peut pas être une facture ou un avoir d’acompte (386, 500, 503). + Valeurs actuelles : BT-23=" + + ", BT-3=" + + ". + + + + + + + + + + + + BR-FR-CO-09 — Contrôle des montants et de la date d’échéance pour les factures déjà payées (BT-23) + + + + + + + + + + + + + + + BR-FR-CO-09_BT-23-1 + warning + + + + + BR-FR-CO-09/BT-23 : Si le cadre de facturation (BT-23) est B2, S2 ou M2 (facture déjà payée), alors le montant déjà payé (BT-113) doit être égal au montant total TTC (BT-112). + Montant payé : + + , Montant total : + + . + + + + + + + + + + BR-FR-CO-09_BT-23-2 + warning + + + + + BR-FR-CO-09/BT-23 : Si le cadre de facturation (BT-23) est B2, S2 ou M2, alors le net à payer (BT-115) doit être égal à 0. + Net à payer : + + . + + + + + + + + + + BR-FR-CO-09_BT-23-3 + warning + + + + + BR-FR-CO-09/BT-23 : Si le cadre de facturation (BT-23) est B2, S2 ou M2, alors la date d’échéance (BT-9) doit être renseignée et correspondre à la date de paiement. + Date d’échéance actuelle : + + . + + + + + + + + + + + + BR-FR-CO-10 — Vérification de la présence du schéma d’identifiant global (BT-29 et équivalents) et unicité du schemeID + + + + + + + + + BR-FR-CO-10_BT-29-1 + warning + + + + + BR-FR-CO-10/BT-29 : Si l’identifiant global du vendeur (BT-29) est renseigné, alors son schéma (BT-29-1) doit également être renseigné. + + + + + + + + + + BR-FR-CO-10_BT-29-2 + warning + + + + + BR-FR-CO-10/BT-29 : Chaque schemeID ne peut apparaître qu’une seule fois dans l'identifiant privé du vendeur (BT-29). + + + + + + + + + + + + + + + BR-FR-CO-10_BT-46-1 + warning + + + + + BR-FR-CO-10/BT-46 : Si l’identifiant global de l'acheteur (BT-46) est renseigné, alors son schéma (BT-46-1) doit également être renseigné. + + + + + + + + + + BR-FR-CO-10_BT-46-2 + warning + + + + + BR-FR-CO-10/BT-46 : Chaque schemeID ne peut apparaître qu’une seule fois dans l'identifiant privé de l'acheteur (BT-46). + + + + + + + + + + + + + + + BR-FR-CO-10_BT-60-1 + warning + + + + + BR-FR-CO-10/BT-60 : Si l’identifiant global du bénéficiaire (BT-60) est renseigné, alors son schéma (BT-60-1) doit également être renseigné. + + + + + + + + + + BR-FR-CO-10_BT-60-2 + warning + + + + + BR-FR-CO-10/BT-60 : Chaque schemeID ne peut apparaître qu’une seule fois dans l'identifiant privé du bénéficiaire (BT-60). + + + + + + + + + + + + + + + BR-FR-CO-10_EXT-FR-FE-06-1 + warning + + + + + BR-FR-CO-10/EXT-FR-FE-06 : Si l’identifiant global de l'agent d'acheteur (EXT-FR-FE-06) est renseigné, alors son schéma (EXT-FR-FE-07) doit également être renseigné. + + + + + + + + + + BR-FR-CO-10_EXT-FR-FE-06-2 + warning + + + + + BR-FR-CO-10/EXT-FR-FE-06 : Chaque schemeID ne peut apparaître qu’une seule fois ans l'identifiant privé de l'agent d'acheteur (EXT-FR-FE-06). + + + + + + + + + + + + + + + BR-FR-CO-10_EXT-FR-FE-46-1 + warning + + + + + BR-FR-CO-10/EXT-FR-FE-46 : Si l’identifiant global du payeur (EXT-FR-FE-46) est renseigné, alors son schéma (EXT-FR-FE-47) doit également être renseigné. + + + + + + + + + + BR-FR-CO-10_EXT-FR-FE-46-2 + warning + + + + + BR-FR-CO-10/EXT-FR-FE-46 : Chaque schemeID ne peut apparaître qu’une seule fois ans l'identifiant privé du payeur (EXT-FR-FE-46). + + + + + + + + + + + + + + + BR-FR-CO-10_EXT-FR-FE-69-1 + warning + + + + + BR-FR-CO-10/EXT-FR-FE-69 : Si l’identifiant global de l'agent du vendeur (EXT-FR-FE-69) est renseigné, alors son schéma (EXT-FR-FE-70) doit également être renseigné. + + + + + + + + + + BR-FR-CO-10_EXT-FR-FE-69-2 + warning + + + + + BR-FR-CO-10/EXT-FR-FE-69 : Chaque schemeID ne peut apparaître qu’une seule fois ans l'identifiant privé de l'agent du vendeur (EXT-FR-FE-69). + + + + + + + + + + + + + + + BR-FR-CO-10_EXT-FR-FE-92-1 + warning + + + + + BR-FR-CO-10/EXT-FR-FE-92 : Si l’identifiant global du facturé à (EXT-FR-FE-92) est renseigné, alors son schéma (EXT-FR-FE-92-1) doit également être renseigné. + + + + + + + + + + BR-FR-CO-10_EXT-FR-FE-92-2 + warning + + + + + BR-FR-CO-10/EXT-FR-FE-92 : Chaque schemeID ne peut apparaître qu’une seule fois ans l'identifiant privé du facturé à (EXT-FR-FE-92). + + + + + + + + + + + + + + + BR-FR-CO-10_EXT-FR-FE-115-1 + warning + + + + + BR-FR-CO-10/EXT-FR-FE-115 : Si l’identifiant global du facturant (EXT-FR-FE-115) est renseigné, alors son schéma (EXT-FR-FE-116) doit également être renseigné. + + + + + + + + + + BR-FR-CO-10_EXT-FR-FE-115-2 + warning + + + + + BR-FR-CO-10/EXT-FR-FE-115 : Chaque schemeID ne peut apparaître qu’une seule fois ans l'identifiant privé du facturant (EXT-FR-FE-115). + + + + + + + + + + + + + + + BR-FR-CO-10_BT-71-1 + warning + + + + + BR-FR-CO-10/BT-71 : Si l’identifiant global du livré à (BT-71) est renseigné, alors son schéma (BT-71-1) doit également être renseigné. + + + + + + + + + + BR-FR-CO-10_BT-71-2 + warning + + + + + BR-FR-CO-10/BT-71 : Chaque schemeID ne peut apparaître qu’une seule fois ans l'identifiant privé du livré à (BT-71). + + + + + + + + + + + + + + + BR-FR-CO-10_EXT-FR-FE-146-1 + warning + + + + + BR-FR-CO-10/EXT-FR-FE-146 : Si l’identifiant global du livré à à la ligne (EXT-FR-FE-146 ) est renseigné, alors son schéma (EXT-FR-FE-147) doit également être renseigné. + + + + + + + + + + BR-FR-CO-10_EXT-FR-FE-146-2 + warning + + + + + BR-FR-CO-10/EXT-FR-FE-146 : Chaque schemeID ne peut apparaître qu’une seule fois ans l'identifiant privé du livré à à la ligne (EXT-FR-FE-146 ). + + + + + + + + + + + + BR-FR-CO-12 — Contrôle des devises et du montant de TVA en comptabilité si la facture n’est pas en EUR + + + + + + + + + + + + BR-FR-CO-12_BT-5 + warning + + + + + BR-FR-CO-12/BT-5 : Si la devise de facture (BT-5) est différente de EUR, alors : + - la devise de comptabilité (BT-6) doit être présente et égale à EUR, + - le montant de TVA en devise de comptabilité (BT-111) doit être renseigné, + - et sa devise (BT-111-1) doit être égale à EUR. + Valeurs actuelles : BT-5=" + + ", BT-6=" + + ", BT-111=" + + "/>". + + + + + + + + + + + + BR-FR-CO-14 — Vérification de la note TXD pour les vendeurs membres d’un assujetti unique + + + + + + + + + + + BR-FR-CO-14_BT-29-1 + warning + + + + + BR-FR-CO-14/BT-29-1 : Si le schéma d’identification du vendeur (BT-29-1) est '0231', cela signifie qu’il est membre d’un assujetti unique. + Dans ce cas, une note (BG-1) avec le code sujet 'TXD' (BT-21) et le texte 'MEMBRE_ASSUJETTI_UNIQUE' (BT-22) doit être présente. + + + + + + + + + + + + BR-FR-CO-15 — Présence du représentant fiscal si le vendeur est membre d’un assujetti unique (BT-29-1 = 0231) + + + + + + + + + + + + BR-FR-CO-15_BT-29-1 + warning + + + + + BR-FR-CO-15/BT-29-1 : Si le vendeur est membre d’un assujetti unique (BT-29-1 = 0231), alors le bloc représentant fiscal (BG-11) doit être présent et contenir le numéro de TVA de l’assujetti unique (BT-63). + État actuel : représentant fiscal + + , numéro de TVA = " + + ". + + + + + + + + + + + + BR-FR-DEC-01 — Format des montants numériques (max 19 caractères, 2 décimales, séparateur « . ») + + + + + + + + + + BR-FR-DEC-01_BT-92_BT-99 + warning + + + + BR-FR-DEC-01/BT-92/BT-99 : Le montant « <value-of select="."/> » est invalide. Il doit comporter au plus 2 décimales, être exprimé avec un point comme séparateur, et ne pas dépasser 19 caractères (hors séparateur). Le signe « - » est autorisé. Veuillez corriger ce montant. + + + + + + + + + + + + + + + BR-FR-DEC-01_BT-93_BT-100 + warning + + + + BR-FR-DEC-01/BT-93 : Le montant « <value-of select="."/> » est invalide. Il doit comporter au plus 2 décimales, être exprimé avec un point comme séparateur, et ne pas dépasser 19 caractères (hors séparateur). Le signe « - » est autorisé. Veuillez corriger ce montant. + + + + + + + + + + + + + + + BR-FR-DEC-01_BT-106 + warning + + + + BR-FR-DEC-01/BT-106 : Le montant « <value-of select="."/> » est invalide. Il doit comporter au plus 2 décimales, être exprimé avec un point comme séparateur, et ne pas dépasser 19 caractères (hors séparateur). Le signe « - » est autorisé. Veuillez corriger ce montant. + + + + + + + + + + + + + + + BR-FR-DEC-01_BT-107 + warning + + + + BR-FR-DEC-01/BT-107 : Le montant « <value-of select="."/> » est invalide. Il doit comporter au plus 2 décimales, être exprimé avec un point comme séparateur, et ne pas dépasser 19 caractères (hors séparateur). Le signe « - » est autorisé. Veuillez corriger ce montant. + + + + + + + + + + + + + + + BR-FR-DEC-01_BT-108 + warning + + + + BR-FR-DEC-01/BT-108 : Le montant « <value-of select="."/> » est invalide. Il doit comporter au plus 2 décimales, être exprimé avec un point comme séparateur, et ne pas dépasser 19 caractères (hors séparateur). Le signe « - » est autorisé. Veuillez corriger ce montant. + + + + + + + + + + + + + + + BR-FR-DEC-01_BT-109 + warning + + + + BR-FR-DEC-01/BT-109 : Le montant « <value-of select="."/> » est invalide. Il doit comporter au plus 2 décimales, être exprimé avec un point comme séparateur, et ne pas dépasser 19 caractères (hors séparateur). Le signe « - » est autorisé. Veuillez corriger ce montant. + + + + + + + + + + + + + + + BR-FR-DEC-01_BT-110 + warning + + + + BR-FR-DEC-01/BT-110 : Le montant « <value-of select="."/> » est invalide. Il doit comporter au plus 2 décimales, être exprimé avec un point comme séparateur, et ne pas dépasser 19 caractères (hors séparateur). Le signe « - » est autorisé. Veuillez corriger ce montant. + + + + + + + + + + + + + + + BR-FR-DEC-01_BT-111 + warning + + + + BR-FR-DEC-01/BT-111 : Le montant « <value-of select="."/> » est invalide. Il doit comporter au plus 2 décimales, être exprimé avec un point comme séparateur, et ne pas dépasser 19 caractères (hors séparateur). Le signe « - » est autorisé. Veuillez corriger ce montant. + + + + + + + + + + + + + + + BR-FR-DEC-01_BT-112 + warning + + + + + BR-FR-DEC-01/BT-112 : Le montant « + + » est invalide. Il doit comporter au plus 2 décimales, être exprimé avec un point comme séparateur, et ne pas dépasser 19 caractères (hors séparateur). Le signe « - » est autorisé. Veuillez corriger ce montant. + + + + + + + + + + + + + + + + BR-FR-DEC-01_BT-113 + warning + + + + + BR-FR-DEC-01/BT-113 : Le montant « + + » est invalide. Il doit comporter au plus 2 décimales, être exprimé avec un point comme séparateur, et ne pas dépasser 19 caractères (hors séparateur). Le signe « - » est autorisé. Veuillez corriger ce montant. + + + + + + + + + + + + + + + + BR-FR-DEC-01_BT-114 + warning + + + + + BR-FR-DEC-01/BT-114 : Le montant « + + » est invalide. Il doit comporter au plus 2 décimales, être exprimé avec un point comme séparateur, et ne pas dépasser 19 caractères (hors séparateur). Le signe « - » est autorisé. Veuillez corriger ce montant. + + + + + + + + + + + + + + + + BR-FR-DEC-01_BT-115 + warning + + + + + BR-FR-DEC-01/BT-115 : Le montant « + + » est invalide. Il doit comporter au plus 2 décimales, être exprimé avec un point comme séparateur, et ne pas dépasser 19 caractères (hors séparateur). Le signe « - » est autorisé. Veuillez corriger ce montant. + + + + + + + + + + + + + + + + BR-FR-DEC-01_BT-116 + warning + + + + + BR-FR-DEC-01/BT-116 : Le montant « + + » est invalide. Il doit comporter au plus 2 décimales, être exprimé avec un point comme séparateur, et ne pas dépasser 19 caractères (hors séparateur). Le signe « - » est autorisé. Veuillez corriger ce montant. + + + + + + + + + + + + + + + + BR-FR-DEC-01_BT-117 + warning + + + + + BR-FR-DEC-01/BT-117 : Le montant « + + » est invalide. Il doit comporter au plus 2 décimales, être exprimé avec un point comme séparateur, et ne pas dépasser 19 caractères (hors séparateur). Le signe « - » est autorisé. Veuillez corriger ce montant. + + + + + + + + + + + + + + + + BR-FR-DEC-01_BT-131 + warning + + + + + BR-FR-DEC-01/BT-131 : Le montant « + + » est invalide. Il doit comporter au plus 2 décimales, être exprimé avec un point comme séparateur, et ne pas dépasser 19 caractères (hors séparateur). Le signe « - » est autorisé. Veuillez corriger ce montant. + + + + + + + + + + + + + + + + BR-FR-DEC-01_BT-136_BT-141 + warning + + + + + BR-FR-DEC-01/BT-136 : Le montant « + + » est invalide. Il doit comporter au plus 2 décimales, être exprimé avec un point comme séparateur, et ne pas dépasser 19 caractères (hors séparateur). Le signe « - » est autorisé. Veuillez corriger ce montant. + + + + + + + + + + + + + + + + BR-FR-DEC-01_BT-137_BT-142 + warning + + + + + BR-FR-DEC-01/BT-137 : Le montant « + + » est invalide. Il doit comporter au plus 2 décimales, être exprimé avec un point comme séparateur, et ne pas dépasser 19 caractères (hors séparateur). Le signe « - » est autorisé. Veuillez corriger ce montant. + + + + + + + + + + + + BR-FR-DEC-02 — Format des quantités numériques (max 19 caractères, 4 décimales, séparateur « . ») + + + + + + + + + + BR-FR-DEC-02_BT-129 + warning + + + + + BR-FR-DEC-02/BT-129 : La quantité « + + » est invalide. Elle doit : + - être un nombre avec au plus 4 décimales (séparateur « . »), + - contenir au maximum 19 caractères (hors séparateur), + - et peut commencer par un signe « - ». + Veuillez corriger cette quantité pour respecter le format requis. + + + + + + + + + + + + + + + + BR-FR-DEC-02_BT-149 + warning + + + + + BR-FR-DEC-02/BT-149 : La quantité « + + » est invalide. Elle doit : + - être un nombre avec au plus 4 décimales (séparateur « . »), + - contenir au maximum 19 caractères (hors séparateur), + - et peut commencer par un signe « - ». + Veuillez corriger cette quantité pour respecter le format requis. + + + + + + + + + + + + BR-FR-DEC-03 — Format des montants positifs (max 19 caractères, 6 décimales, séparateur « . ») + + + + + + + + + + BR-FR-DEC-03_BT-146 + warning + + + + + BR-FR-DEC-03/BT-146 : Le montant « + + » est invalide. Il doit : + - être un nombre strictement positif (sans signe « - »), + - comporter au plus 6 décimales (séparateur « . »), + - contenir au maximum 19 caractères (hors séparateur). + Veuillez corriger ce montant pour respecter le format requis. + + + + + + + + + + + + + + + + BR-FR-DEC-03_BT-147 + warning + + + + + BR-FR-DEC-03/BT-147 : Le montant « + + » est invalide. Il doit : + - être un nombre strictement positif (sans signe « - »), + - comporter au plus 6 décimales (séparateur « . »), + - contenir au maximum 19 caractères (hors séparateur). + Veuillez corriger ce montant pour respecter le format requis. + + + + + + + + + + + + + + + + BR-FR-DEC-03_BT-148 + warning + + + + + BR-FR-DEC-03/BT-148 : Le montant « + + » est invalide. Il doit : + - être un nombre strictement positif (sans signe « - »), + - comporter au plus 6 décimales (séparateur « . »), + - contenir au maximum 19 caractères (hors séparateur). + Veuillez corriger ce montant pour respecter le format requis. + + + + + + + + + + + + BR-FR-DEC-04 — Format des taux de TVA (max 4 caractères, 2 décimales, séparateur « . ») + + + + + + + + + + BR-FR-DEC-04_BT-96_BT-103 + warning + + + + + BR-FR-DEC-04/BT-96 ou BT-103 : Le taux de TVA « + + » est invalide. Il doit : + - être un nombre strictement positif (sans signe « - »), + - comporter au plus 2 décimales (séparateur « . »), + - contenir au maximum 4 caractères (hors séparateur). + Veuillez corriger ce taux pour respecter le format requis. + + + + + + + + + + + + + + + + BR-FR-DEC-04_BT-119 + warning + + + + + BR-FR-DEC-04/BT-119 : Le taux de TVA « + + » est invalide. Il doit : + - être un nombre strictement positif (sans signe « - »), + - comporter au plus 2 décimales (séparateur « . »), + - contenir au maximum 4 caractères (hors séparateur). + Veuillez corriger ce taux pour respecter le format requis. + + + + + + + + + + + + + + + + BR-FR-DEC-04_BT-152 + warning + + + + + BR-FR-DEC-04/BT-152 : Le taux de TVA « + + » est invalide. Il doit : + - être un nombre strictement positif (sans signe « - »), + - comporter au plus 2 décimales (séparateur « . »), + - contenir au maximum 4 caractères (hors séparateur). + Veuillez corriger ce taux pour respecter le format requis. + + + + + + + + + + + + BR-FR-MV-01 — Vérification du sous-type de ligne lorsque le cadre de facturation est S8, B8 ou M8 + + + + + + + + + BR-FR-MV-01_EXT-FR-FE-163 + fatal + + + + + BR-FR-MV-01/EXT-FR-FE-163 : Lorsque le cadre de facturation (BT-23) est S8, B8 ou M8, chaque ligne (BG-25) doit contenir un sous-type de ligne (ram:LineStatusReasonCode). Valeur actuelle : " + + ". + Veuillez vérifier que le sous-type est renseigné, Veuillez vérifier que le sous-type est renseigné pour toutes les lignes. + + + + + + + + + + + + BR-FR-MV-02 — Vérification de la présence d'une ligne GROUP sans parent lorsque le cadre de facturation est S8, B8 ou M8 + + + + + + + + + BR-FR-MV-02_EXT-FR-FE-163 + fatal + + + + + BR-FR-MV-02/EXT-FR-FE-163 : Lorsque le cadre de facturation (BT-23) est S8, B8 ou M8, la facture doit contenir au moins une ligne (BG-25) avec le sous-type de ligne (ram:LineStatusReasonCode) égal à "GROUP" et sans identifiant de ligne parent (ram:ParentLineID). + Veuillez vérifier que cette ligne est présente. + + + + + + + + + + + + BR-FR-MV-03 — Vérification des données obligatoires pour les lignes GROUP sans parent lorsque le cadre de facturation est S8, B8 ou M8 + + + + + + + + + BR-FR-MV-03_EXT-FR-FE-164 + fatal + + + + BR-FR-MV-03/EXT-FR-FE-164 : + Ligne : + + : Valeur actuelle : " + + ". + Lorsque le cadre de facturation (BT-23) est S8, B8 ou M8 et que la ligne est de type GROUP sans parent, le nom du vendeur (ram:Name) doit être renseigné. + + + + + + + + + + BR-FR-MV-03_EXT-FR-FE-167 + fatal + + + + BR-FR-MV-03/EXT-FR-FE-167 : + Ligne : + + : Valeur actuelle : " + + ". + Lorsque le cadre de facturation (BT-23) est S8, B8 ou M8 et que la ligne est de type GROUP sans parent, l'identifiant du vendeur (ram:ID) doit être renseigné. + + + + + + + + + + BR-FR-MV-03_EXT-FR-FE-177 + fatal + + + + BR-FR-MV-03/EXT-FR-FE-177 : + Ligne : + + : Valeur actuelle : " + + '. + Lorsque le cadre de facturation (BT-23) est S8, B8 ou M8 et que la ligne est de type GROUP sans parent, le code pays du vendeur (ram:CountryID) doit être renseigné. + + + + + + + + + + + BR-FR-MV-03_EXT-FR-FE-181 + fatal + + + + BR-FR-MV-03/EXT-FR-FE-181 : + Ligne : + + , Valeur actuelle : " + + ". + Pour une ligne GROUP sans parent, le montant total avec TVA (ram:TaxTotalAmount) doit être renseigné en devise de facture. + + + + + + + + + + + BR-FR-MV-03_EXT-FR-FE-182 + fatal + + + + BR-FR-MV-03/EXT-FR-FE-182 : + Ligne : + + , Devise de comptabilité : " + + ", TVA en ligne : " + + ". + Pour une ligne GROUP sans parent, le montant total avec TVA (ram:TaxTotalAmount) doit être renseigné en devise de comptabilisation (BT-6) si elle est renseignée. + + + + + + + + + + + + + + + BR-FR-MV-03_BT-128_AFL + fatal + + + + + BR-FR-MV-03/BT-128 : Pour une ligne GROUP sans parent, une valeur d'objet facturé (ram:IssuerAssignedID) avec identifiant de schéma (ram:ReferenceTypeCode) = AFL doit être présente. Valeur actuelle : " + + ". + + + + + + + + + + + + + + + BR-FR-MV-03_BT-128_AVV + fatal + + + + + BR-FR-MV-03/BT-128 : Pour une ligne GROUP sans parent, une valeur d'objet facturé (BT-128 : ram:IssuerAssignedID) avec identifiant de schéma (BT-128-1 : ram:ReferenceTypeCode) = AVV doit être présente et différente de M8/S8/B8. Valeur actuelle : " + + ". + + + + + + + + + + + + BR-FR-MV-05 — Vérification de la cohérence des totaux HT entre la ligne GROUP et ses lignes enfants + + + + + + + + + + + + BR-FR-MV-05_EXT-FR-FE-BG-12 + fatal + + + + + BR-FR-MV-05/EXT-FR-FE-BG-12 : Ligne GROUP : + + , Somme Sous lignes : + + , Nbre sous-lignes: + + . Valeur actuelle : " + + ". + Lorsque le cadre de facturation (BT-23) est S8, B8 ou M8, le total HT (BT-131 : ram:LineTotalAmount) de la ligne GROUP doit être égal (tolérance ±0,01 * nombre de sous-lignes) à la somme des totaux HT des lignes enfants dont le ParentLineID correspond à l'identifiant de la ligne GROUP (ram:LineID). + + + + + + + + + + + + BR-FR-MV-06 — Vérification de la cohérence de l'identifiant légal du vendeur entre une ligne et sa ligne parent + + + + + + + + + + + + BR-FR-MV-06_EXT-FR-FE-167 + fatal + + + + + BR-FR-MV-06/EXT-FR-FE-167 : IDligne + + , ID parent ligne + + , legalID : + + , ParentlegalID : + + . + Lorsque le cadre de facturation (BT-23) est S8, B8 ou M8, chaque ligne (BG-25) doit contenir un identifiant légal de vendeur (ram:ID). Si la ligne a un identifiant de ligne parent (ram:ParentLineID), cet identifiant doit être identique à celui de la ligne parent. + + + + + + + + + + + + BR-FR-MV-07 — Vérification de la cohérence du numéro de facture codifié AFL entre une ligne et sa ligne parent + + + + + + + + + + + + BR-FR-MV-07_BT-128 + fatal + + + + + BR-FR-MV-07/BT-128 : IDligne + + , ID parent ligne + + , numfact en ligne + + , numfact ligne parent : + + . + Lorsque le cadre de facturation (BT-23) est S8, B8 ou M8, chaque ligne (BG-25) doit contenir un numéro de facture codifié AFL (ram:IssuerAssignedID). Si la ligne a un identifiant de ligne parent (ram:ParentLineID), ce numéro doit être identique à celui de la ligne parent. + + + + + + + + + + + + BR-FR-MV-08 — Vérification de la raison d'exemption TVA contenant le numéro de sous-facture en ligne entre # + + + + + + + + + + BR-FR-MV-08_BT-128 + fatal + + + + + BR-FR-MV-08/BT-128 : IDligne + + , Raison exemption : + + / Num fact ligne: + + . Lorsque le cadre de facturation (BT-23) est S8, B8 ou M8, chaque ligne (BG-25) doit contenir une raison d'exemption TVA en texte commençant par le numéro de sous-facture en ligne entre #. + + + + + + + + + + + + BR-FR-MV-09 — Vérification de la cohérence du montant total TVA pour une ligne GROUP avec la somme des ventilations TVA liées + + + + + + + + + + + + BR-FR-MV-09_EXT-FR-FE-181 + fatal + + + + + BR-FR-MV-09/EXT-FR-FE-181 : Id Line Group : + + , Numfact: + + , Total TVA : + + , Somme TVA : + + . + Lorsque le cadre de facturation (BT-23) est S8, B8 ou M8, le montant total TVA de la ligne GROUP (EXT-FR-FE-181) doit être égal à la somme des montants de TVA des ventilations TVA (BT-117) dont la raison d'exemption (ram:ExemptionReason) commence par le numéro de facture en ligne (BT-128 avec ReferenceTypeCode = AFL) entre #. + + + + + + + + + + + + BR-FR-MV-10 — Vérification de la cohérence du montant total avec TVA pour une ligne GROUP + + + + + + + + + + + + BR-FR-MV-10_EXT-FR-FE-184 + fatal + + + + + BR-FR-MV-10/EXT-FR-FE-184 : Id Line Group : + + , nb sous-ligne : + + , + TTC : + + , + TVA : + + , + HT : + + + Lorsque le cadre de facturation (BT-23) est S8, B8 ou M8, si le montant total avec TVA (ram:GrandTotalAmount) est présent pour une ligne GROUP sans parent, alors la différence entre ce montant et la somme du montant HT (ram:LineTotalAmount) et du montant TVA (ram:TaxTotalAmount) doit être inférieure ou égale à 0,01 × le nombre de sous-lignes DETAIL. Valeur actuelle : " + + '. + + + + + + + + + + + + BR-FR-MV-11 — Vérification de la cohérence entre l'identifiant de facture à la ligne (AFL) et le numéro de facture (BT-1) pour le Vendeur principal + + + + + + + + + + + + BR-FR-MV-11_BT-128 + fatal + + + + + BR-FR-MV-11/BT-128 : ID Seller : + + , NB ligne GROUP seller : + + + Numero de facture (BT-1) : + + , num fact en ligne + + . + Lorsque le cadre de facturation (BT-23) est S8, B8 ou M8 et que le vendeur principal (BG-4) dispose d'un groupe de lignes, l'identifiant de facture à la ligne (ram:IssuerAssignedID avec ReferenceTypeCode = AFL) doit être identique au numéro de facture (ram:ID dans ExchangedDocument). + + + + + + + + + + + + BR-FR-MV-12 — Vérification de l'unicité des numéros de facture AFL pour les lignes GROUP sans parent + + + + + + + + + BR-FR-MV-12_BT-128 + fatal + + + + + BR-FR-MV-12/BT-128 : Lorsque le cadre de facturation (BT-23) est S8, B8 ou M8, les numéros de facture à la ligne (ram:IssuerAssignedID avec ReferenceTypeCode = AFL) pour les lignes GROUP sans parent doivent être uniques. Veuillez vérifier que chaque numéro est distinct. + + + + + + + + + + + + BR-FR-MV-13 — Vérification que le code type de facture (BT-3) n'est pas un type auto-facturé interdit + + + + + + + + + BR-FR-MV-13_BT-3 + fatal + + + + + BR-FR-MV-13/BT-3 : Lorsque le cadre de facturation (BT-23) est S8, B8 ou M8, le code type de facture (ram:TypeCode) ne doit pas être l'un des types auto-facturés suivants : 389, 261, 501, 500, 502, 471, 473. Valeur actuelle : " + + '. + + + + + + + + + + + diff --git a/validator/src/main/resources/xslt/ZF_240/FACTUR-X_BASIC-WL.xslt b/validator/src/main/resources/xslt/ZF_240/FACTUR-X_BASIC-WL.xslt index c30bd62d..2d97527c 100644 --- a/validator/src/main/resources/xslt/ZF_240/FACTUR-X_BASIC-WL.xslt +++ b/validator/src/main/resources/xslt/ZF_240/FACTUR-X_BASIC-WL.xslt @@ -2025,9 +2025,9 @@ - + - + FX-SCH-A-000118 @@ -2040,9 +2040,9 @@ - + - + FX-SCH-A-000119 diff --git a/validator/src/main/resources/xslt/ZF_240/FACTUR-X_BASIC.xslt b/validator/src/main/resources/xslt/ZF_240/FACTUR-X_BASIC.xslt index 6d688013..66f6faa1 100644 --- a/validator/src/main/resources/xslt/ZF_240/FACTUR-X_BASIC.xslt +++ b/validator/src/main/resources/xslt/ZF_240/FACTUR-X_BASIC.xslt @@ -2633,9 +2633,9 @@ - + - + FX-SCH-A-000226 @@ -2708,9 +2708,9 @@ - + - + FX-SCH-A-000118 @@ -2723,9 +2723,9 @@ - + - + FX-SCH-A-000119 diff --git a/validator/src/main/resources/xslt/ZF_240/FACTUR-X_EN16931.xslt b/validator/src/main/resources/xslt/ZF_240/FACTUR-X_EN16931.xslt index 0a0e0c64..d216c8a9 100644 --- a/validator/src/main/resources/xslt/ZF_240/FACTUR-X_EN16931.xslt +++ b/validator/src/main/resources/xslt/ZF_240/FACTUR-X_EN16931.xslt @@ -2703,9 +2703,9 @@ - + - + FX-SCH-A-000226 @@ -2778,9 +2778,9 @@ - + - + FX-SCH-A-000118 @@ -2793,9 +2793,9 @@ - + - + FX-SCH-A-000119 diff --git a/validator/src/main/resources/xslt/ZF_240/FACTUR-X_EXTENDED.xslt b/validator/src/main/resources/xslt/ZF_240/FACTUR-X_EXTENDED.xslt index 2e5b60fa..32882558 100644 --- a/validator/src/main/resources/xslt/ZF_240/FACTUR-X_EXTENDED.xslt +++ b/validator/src/main/resources/xslt/ZF_240/FACTUR-X_EXTENDED.xslt @@ -2903,9 +2903,9 @@ - + - + FX-SCH-A-000365 @@ -3008,9 +3008,9 @@ - + - + FX-SCH-A-000366 diff --git a/validator/src/main/resources/xslt/en16931schematron/EN16931-CII-validation.xslt b/validator/src/main/resources/xslt/en16931schematron/EN16931-CII-validation.xslt index 7ab90522..3e184293 100644 --- a/validator/src/main/resources/xslt/en16931schematron/EN16931-CII-validation.xslt +++ b/validator/src/main/resources/xslt/en16931schematron/EN16931-CII-validation.xslt @@ -4,7 +4,7 @@ Licensed under European Union Public Licence (EUPL) version 1.2. --> - + @@ -175,6 +175,9 @@ + + + EN16931-CII-Model EN16931-CII-Model @@ -184,6 +187,9 @@ + + + EN16931-CII-Syntax EN16931-CII-Syntax @@ -193,6 +199,9 @@ + + + EN16931-Codes EN16931-Codes @@ -208,28 +217,7 @@ - - - - - - - - - BR-66 - fatal - - - - [BR-66]-Each Specified Trade Allowance Charge (BG-20)(BG-21) shall contain a Charge Indicator. - - - - - - - - + @@ -250,7 +238,7 @@ - + @@ -271,7 +259,7 @@ - + @@ -292,8 +280,8 @@ - - + + @@ -328,7 +316,7 @@ - + @@ -349,7 +337,7 @@ - + @@ -460,7 +448,7 @@ - + @@ -571,7 +559,7 @@ - + @@ -681,7 +669,7 @@ - + BR-CO-12 @@ -877,7 +865,7 @@ - + @@ -1288,7 +1276,7 @@ - + @@ -1458,9 +1446,9 @@ - + - + BR-DEC-23 fatal @@ -1474,7 +1462,7 @@ - + @@ -1570,7 +1558,7 @@ - + @@ -1666,7 +1654,7 @@ - + @@ -1702,7 +1690,7 @@ - + @@ -1738,7 +1726,7 @@ - + @@ -1759,7 +1747,7 @@ - + @@ -1780,7 +1768,7 @@ - + @@ -1797,12 +1785,18 @@ + + + + + + - + - + BR-CO-27 fatal @@ -1929,7 +1923,7 @@ - + BR-CO-14 @@ -1971,9 +1965,9 @@ - + - + BR-AE-08 fatal @@ -2355,9 +2349,9 @@ - + - + BR-AG-05 fatal @@ -2391,9 +2385,9 @@ - + - + BR-AG-06 fatal @@ -2427,9 +2421,9 @@ - + - + BR-AG-07 fatal @@ -2448,9 +2442,9 @@ - + - + BR-E-08 fatal @@ -2607,9 +2601,9 @@ - + - + BR-G-08 fatal @@ -2766,9 +2760,9 @@ - + - + BR-IC-08 fatal @@ -3066,9 +3060,9 @@ - + - + BR-O-03 fatal @@ -3102,9 +3096,9 @@ - + - + BR-O-04 fatal @@ -3138,9 +3132,9 @@ - + - + BR-O-02 fatal @@ -3620,7 +3614,28 @@ - + + + + + + + + + CII-SR-464 + warning + + + + [CII-SR-464] - Only one BT-86 element is allowed on an invoice. + + + + + + + + @@ -3776,7 +3791,7 @@ - + @@ -4067,7 +4082,7 @@ - + @@ -4088,7 +4103,7 @@ - + @@ -4169,7 +4184,7 @@ - + @@ -4265,7 +4280,7 @@ - + @@ -5111,7 +5126,7 @@ - + @@ -5147,7 +5162,7 @@ - + @@ -5587,21 +5602,6 @@ - - - - CII-SR-440 - fatal - - - - [CII-SR-440] - ActualAmount should exist maximum once - - - - - - @@ -5932,6 +5932,48 @@ + + + + + + + + + + CII-SR-463 + fatal + + + + [CII-SR-463]-Each Specified Trade Allowance Charge (BG-20)(BG-21) shall contain a Charge Indicator. + + + + + + + + + + + + + + + + CII-SR-440 + fatal + + + + [CII-SR-440] - ActualAmount should exist maximum once + + + + + + @@ -6995,8 +7037,8 @@ - - + + @@ -7900,9 +7942,9 @@ - + - + CII-SR-282 warning @@ -8384,7 +8426,7 @@ CII-SR-459 - warning + fatal @@ -8399,7 +8441,7 @@ CII-SR-460 - warning + fatal @@ -8407,6 +8449,36 @@ + + + + + + + CII-SR-465 + warning + + + + [CII-SR-465] - Only one BT-41 element is allowed on an invoice. + + + + + + + + + + CII-SR-466 + warning + + + + [CII-SR-466] - Only one BT-56 element is allowed on an invoice. + + + @@ -10237,9 +10309,9 @@ - + - + CII-SR-462 fatal @@ -10496,59 +10568,8 @@ - - - - - - - - - CII-DT-005 - fatal - - - - [CII-DT-005] - schemeID should not be present - - - - - - - - - - CII-DT-006 - fatal - - - - [CII-DT-006] - schemeAgencyID should not be present - - - - - - - - - - CII-DT-007 - fatal - - - - [CII-DT-007] - schemeVersionID should not be present - - - - - - - - - + + @@ -10609,6 +10630,117 @@ + + + + + + + CII-DT-005 + fatal + + + + [CII-DT-005] - schemeID should not be present + + + + + + + + + + CII-DT-006 + fatal + + + + [CII-DT-006] - schemeAgencyID should not be present + + + + + + + + + + CII-DT-007 + fatal + + + + [CII-DT-007] - schemeVersionID should not be present + + + + + + + + + + + + + + + + CII-DT-101 + fatal + + + + [CII-DT-101] - schemeName should not be present + + + + + + + + + + CII-DT-102 + fatal + + + + [CII-DT-102] - schemeAgencyName should not be present + + + + + + + + + + CII-DT-103 + fatal + + + + [CII-DT-103] - schemeDataURI should not be present + + + + + + + + + + CII-DT-104 + fatal + + + + [CII-DT-104] - schemeURI should not be present + + + @@ -10771,7 +10903,7 @@ - + CII-DT-015 @@ -12005,9 +12137,9 @@ - + - + BR-CL-01 fatal @@ -12026,9 +12158,9 @@ - + - + BR-CL-03 fatal @@ -12047,9 +12179,9 @@ - + - + BR-CL-04 fatal @@ -12068,9 +12200,9 @@ - + - + BR-CL-05 fatal @@ -12110,9 +12242,9 @@ - + - + BR-CL-07 fatal @@ -12152,9 +12284,9 @@ - + - + BR-CL-10 fatal @@ -12173,9 +12305,9 @@ - + - + BR-CL-11 fatal @@ -12194,9 +12326,9 @@ - + - + BR-CL-13 fatal @@ -12362,9 +12494,9 @@ - + - + BR-CL-21 fatal @@ -12384,9 +12516,9 @@ - + - + BR-CL-22 fatal @@ -12447,9 +12579,9 @@ - + - + BR-CL-25 fatal @@ -12468,9 +12600,9 @@ - + - + BR-CL-26 fatal diff --git a/validator/src/main/resources/xslt/en16931schematron/EN16931-UBL-validation.xslt b/validator/src/main/resources/xslt/en16931schematron/EN16931-UBL-validation.xslt index bc209719..07c2f7ba 100644 --- a/validator/src/main/resources/xslt/en16931schematron/EN16931-UBL-validation.xslt +++ b/validator/src/main/resources/xslt/en16931schematron/EN16931-UBL-validation.xslt @@ -4,7 +4,7 @@ Licensed under European Union Public Licence (EUPL) version 1.2. --> - + @@ -177,6 +177,9 @@ + + + UBL-model UBL-model @@ -186,6 +189,9 @@ + + + UBL-syntax UBL-syntax @@ -195,6 +201,9 @@ + + + Codesmodel Codesmodel @@ -231,8 +240,8 @@ - - + + @@ -341,7 +350,7 @@ - + BR-31 @@ -989,36 +998,6 @@ - - - - BR-66 - fatal - - - - [BR-66]-An Invoice shall contain maximum one Payment Card account (BG-18). - - - - - - - - - - BR-67 - fatal - - - - [BR-67]-An Invoice shall contain maximum one Payment Mandate (BG-19). - - - - - - @@ -1367,12 +1346,12 @@ - BR-IG-01 + BR-AF-01 fatal - [BR-IG-01]-An Invoice that contains an Invoice line (BG-25), a Document level allowance (BG-20) or a Document level charge (BG-21) where the VAT category code (BT-151, BT-95 or BT-102) is "IGIC" shall contain in the VAT breakdown (BG-23) at least one VAT category code (BT-118) equal with "IGIC". + [BR-AF-01]-An Invoice that contains an Invoice line (BG-25), a Document level allowance (BG-20) or a Document level charge (BG-21) where the VAT category code (BT-151, BT-95 or BT-102) is "IGIC" shall contain in the VAT breakdown (BG-23) at least one VAT category code (BT-118) equal with "IGIC". @@ -1382,12 +1361,12 @@ - BR-IG-02 + BR-AF-02 fatal - [BR-IG-02]-An Invoice that contains an Invoice line (BG-25) where the Invoiced item VAT category code (BT-151) is "IGIC" shall contain the Seller VAT Identifier (BT-31), the Seller tax registration identifier (BT-32) and/or the Seller tax representative VAT identifier (BT-63). + [BR-AF-02]-An Invoice that contains an Invoice line (BG-25) where the Invoiced item VAT category code (BT-151) is "IGIC" shall contain the Seller VAT Identifier (BT-31), the Seller tax registration identifier (BT-32) and/or the Seller tax representative VAT identifier (BT-63). @@ -1397,12 +1376,12 @@ - BR-IG-03 + BR-AF-03 fatal - [BR-IG-03]-An Invoice that contains a Document level allowance (BG-20) where the Document level allowance VAT category code (BT-95) is "IGIC" shall contain the Seller VAT Identifier (BT-31), the Seller tax registration identifier (BT-32) and/or the Seller tax representative VAT identifier (BT-63). + [BR-AF-03]-An Invoice that contains a Document level allowance (BG-20) where the Document level allowance VAT category code (BT-95) is "IGIC" shall contain the Seller VAT Identifier (BT-31), the Seller tax registration identifier (BT-32) and/or the Seller tax representative VAT identifier (BT-63). @@ -1412,12 +1391,12 @@ - BR-IG-04 + BR-AF-04 fatal - [BR-IG-04]-An Invoice that contains a Document level charge (BG-21) where the Document level charge VAT category code (BT-102) is "IGIC" shall contain the Seller VAT Identifier (BT-31), the Seller Tax registration identifier (BT-32) and/or the Seller tax representative VAT identifier (BT-63). + [BR-AF-04]-An Invoice that contains a Document level charge (BG-21) where the Document level charge VAT category code (BT-102) is "IGIC" shall contain the Seller VAT Identifier (BT-31), the Seller Tax registration identifier (BT-32) and/or the Seller tax representative VAT identifier (BT-63). @@ -1427,12 +1406,12 @@ - BR-IP-01 + BR-AG-01 fatal - [BR-IP-01]-An Invoice that contains an Invoice line (BG-25), a Document level allowance (BG-20) or a Document level charge (BG-21) where the VAT category code (BT-151, BT-95 or BT-102) is "IPSI" shall contain in the VAT breakdown (BG-23) at least one VAT category code (BT-118) equal with "IPSI". + [BR-AG-01]-An Invoice that contains an Invoice line (BG-25), a Document level allowance (BG-20) or a Document level charge (BG-21) where the VAT category code (BT-151, BT-95 or BT-102) is "IPSI" shall contain in the VAT breakdown (BG-23) at least one VAT category code (BT-118) equal with "IPSI". @@ -1442,12 +1421,12 @@ - BR-IP-02 + BR-AG-02 fatal - [BR-IP-02]-An Invoice that contains an Invoice line (BG-25) where the Invoiced item VAT category code (BT-151) is "IPSI" shall contain the Seller VAT Identifier (BT-31), the Seller tax registration identifier (BT-32) and/or the Seller tax representative VAT identifier (BT-63). + [BR-AG-02]-An Invoice that contains an Invoice line (BG-25) where the Invoiced item VAT category code (BT-151) is "IPSI" shall contain the Seller VAT Identifier (BT-31), the Seller tax registration identifier (BT-32) and/or the Seller tax representative VAT identifier (BT-63). @@ -1457,12 +1436,12 @@ - BR-IP-03 + BR-AG-03 fatal - [BR-IP-03]-An Invoice that contains a Document level allowance (BG-20) where the Document level allowance VAT category code (BT-95) is "IPSI" shall contain the Seller VAT Identifier (BT-31), the Seller Tax registration identifier (BT-32) and/or the Seller tax representative VAT identifier (BT-63). + [BR-AG-03]-An Invoice that contains a Document level allowance (BG-20) where the Document level allowance VAT category code (BT-95) is "IPSI" shall contain the Seller VAT Identifier (BT-31), the Seller Tax registration identifier (BT-32) and/or the Seller tax representative VAT identifier (BT-63). @@ -1472,12 +1451,12 @@ - BR-IP-04 + BR-AG-04 fatal - [BR-IP-04]-An Invoice that contains a Document level charge (BG-21) where the Document level charge VAT category code (BT-102) is "IPSI" shall contain the Seller VAT Identifier (BT-31), the Seller Tax registration identifier (BT-32) and/or the Seller tax representative VAT identifier (BT-63). + [BR-AG-04]-An Invoice that contains a Document level charge (BG-21) where the Document level charge VAT category code (BT-102) is "IPSI" shall contain the Seller VAT Identifier (BT-31), the Seller Tax registration identifier (BT-32) and/or the Seller tax representative VAT identifier (BT-63). @@ -2196,8 +2175,8 @@ - - + + @@ -3110,12 +3089,12 @@ - BR-IG-08 + BR-AF-08 fatal - [BR-IG-08]-For each different value of VAT category rate (BT-119) where the VAT category code (BT-118) is "IGIC", the VAT category taxable amount (BT-116) in a VAT breakdown (BG-23) shall equal the sum of Invoice line net amounts (BT-131) plus the sum of document level charge amounts (BT-99) minus the sum of document level allowance amounts (BT-92) where the VAT category code (BT-151, BT-102, BT-95) is "IGIC" and the VAT rate (BT-152, BT-103, BT-96) equals the VAT category rate (BT-119). + [BR-AF-08]-For each different value of VAT category rate (BT-119) where the VAT category code (BT-118) is "IGIC", the VAT category taxable amount (BT-116) in a VAT breakdown (BG-23) shall equal the sum of Invoice line net amounts (BT-131) plus the sum of document level charge amounts (BT-99) minus the sum of document level allowance amounts (BT-92) where the VAT category code (BT-151, BT-102, BT-95) is "IGIC" and the VAT rate (BT-152, BT-103, BT-96) equals the VAT category rate (BT-119). @@ -3125,12 +3104,12 @@ - BR-IG-09 + BR-AF-09 fatal - [BR-IG-09]-The VAT category tax amount (BT-117) in a VAT breakdown (BG-23) where VAT category code (BT-118) is "IGIC" shall equal the VAT category taxable amount (BT-116) multiplied by the VAT category rate (BT-119). + [BR-AF-09]-The VAT category tax amount (BT-117) in a VAT breakdown (BG-23) where VAT category code (BT-118) is "IGIC" shall equal the VAT category taxable amount (BT-116) multiplied by the VAT category rate (BT-119). @@ -3140,12 +3119,12 @@ - BR-IG-10 + BR-AF-10 fatal - [BR-IG-10]-A VAT breakdown (BG-23) with VAT Category code (BT-118) "IGIC" shall not have a VAT exemption reason code (BT-121) or VAT exemption reason text (BT-120). + [BR-AF-10]-A VAT breakdown (BG-23) with VAT Category code (BT-118) "IGIC" shall not have a VAT exemption reason code (BT-121) or VAT exemption reason text (BT-120). @@ -3161,12 +3140,12 @@ - BR-IG-06 + BR-AF-06 fatal - [BR-IG-06]-In a Document level allowance (BG-20) where the Document level allowance VAT category code (BT-95) is "IGIC" the Document level allowance VAT rate (BT-96) shall be 0 (zero) or greater than zero. + [BR-AF-06]-In a Document level allowance (BG-20) where the Document level allowance VAT category code (BT-95) is "IGIC" the Document level allowance VAT rate (BT-96) shall be 0 (zero) or greater than zero. @@ -3182,12 +3161,12 @@ - BR-IG-07 + BR-AF-07 fatal - [BR-IG-07]-In a Document level charge (BG-21) where the Document level charge VAT category code (BT-102) is "IGIC" the Document level charge VAT rate (BT-103) shall be 0 (zero) or greater than zero. + [BR-AF-07]-In a Document level charge (BG-21) where the Document level charge VAT category code (BT-102) is "IGIC" the Document level charge VAT rate (BT-103) shall be 0 (zero) or greater than zero. @@ -3203,12 +3182,12 @@ - BR-IG-05 + BR-AF-05 fatal - [BR-IG-05]-In an Invoice line (BG-25) where the Invoiced item VAT category code (BT-151) is "IGIC" the invoiced item VAT rate (BT-152) shall be 0 (zero) or greater than zero. + [BR-AF-05]-In an Invoice line (BG-25) where the Invoiced item VAT category code (BT-151) is "IGIC" the invoiced item VAT rate (BT-152) shall be 0 (zero) or greater than zero. @@ -3224,12 +3203,12 @@ - BR-IP-08 + BR-AG-08 fatal - [BR-IP-08]-For each different value of VAT category rate (BT-119) where the VAT category code (BT-118) is "IPSI", the VAT category taxable amount (BT-116) in a VAT breakdown (BG-23) shall equal the sum of Invoice line net amounts (BT-131) plus the sum of document level charge amounts (BT-99) minus the sum of document level allowance amounts (BT-92) where the VAT category code (BT-151, BT-102, BT-95) is "IPSI" and the VAT rate (BT-152, BT-103, BT-96) equals the VAT category rate (BT-119). + [BR-AG-08]-For each different value of VAT category rate (BT-119) where the VAT category code (BT-118) is "IPSI", the VAT category taxable amount (BT-116) in a VAT breakdown (BG-23) shall equal the sum of Invoice line net amounts (BT-131) plus the sum of document level charge amounts (BT-99) minus the sum of document level allowance amounts (BT-92) where the VAT category code (BT-151, BT-102, BT-95) is "IPSI" and the VAT rate (BT-152, BT-103, BT-96) equals the VAT category rate (BT-119). @@ -3239,12 +3218,12 @@ - BR-IP-09 + BR-AG-09 fatal - [BR-IP-09]-The VAT category tax amount (BT-117) in a VAT breakdown (BG-23) where VAT category code (BT-118) is "IPSI" shall equal the VAT category taxable amount (BT-116) multiplied by the VAT category rate (BT-119). + [BR-AG-09]-The VAT category tax amount (BT-117) in a VAT breakdown (BG-23) where VAT category code (BT-118) is "IPSI" shall equal the VAT category taxable amount (BT-116) multiplied by the VAT category rate (BT-119). @@ -3254,12 +3233,12 @@ - BR-IP-10 + BR-AG-10 fatal - [BR-IP-10]-A VAT breakdown (BG-23) with VAT Category code (BT-118) "IPSI" shall not have a VAT exemption reason code (BT-121) or VAT exemption reason text (BT-120). + [BR-AG-10]-A VAT breakdown (BG-23) with VAT Category code (BT-118) "IPSI" shall not have a VAT exemption reason code (BT-121) or VAT exemption reason text (BT-120). @@ -3275,12 +3254,12 @@ - BR-IP-06 + BR-AG-06 fatal - [BR-IP-06]-In a Document level allowance (BG-20) where the Document level allowance VAT category code (BT-95) is "IPSI" the Document level allowance VAT rate (BT-96) shall be 0 (zero) or greater than zero. + [BR-AG-06]-In a Document level allowance (BG-20) where the Document level allowance VAT category code (BT-95) is "IPSI" the Document level allowance VAT rate (BT-96) shall be 0 (zero) or greater than zero. @@ -3296,12 +3275,12 @@ - BR-IP-07 + BR-AG-07 fatal - [BR-IP-07]-In a Document level charge (BG-21) where the Document level charge VAT category code (BT-102) is "IPSI" the Document level charge VAT rate (BT-103) shall be 0 (zero) or greater than zero. + [BR-AG-07]-In a Document level charge (BG-21) where the Document level charge VAT category code (BT-102) is "IPSI" the Document level charge VAT rate (BT-103) shall be 0 (zero) or greater than zero. @@ -3317,12 +3296,12 @@ - BR-IP-05 + BR-AG-05 fatal - [BR-IP-05]-In an Invoice line (BG-25) where the Invoiced item VAT category code (BT-151) is "IPSI" the Invoiced item VAT rate (BT-152) shall be 0 (zero) or greater than zero. + [BR-AG-05]-In an Invoice line (BG-25) where the Invoiced item VAT category code (BT-151) is "IPSI" the Invoiced item VAT rate (BT-152) shall be 0 (zero) or greater than zero. @@ -3679,8 +3658,8 @@ - - + + @@ -3700,7 +3679,7 @@ - + @@ -3721,7 +3700,7 @@ - + @@ -3757,7 +3736,7 @@ - + @@ -3778,7 +3757,7 @@ - + @@ -3814,7 +3793,7 @@ - + @@ -3835,7 +3814,7 @@ - + @@ -3856,7 +3835,7 @@ - + @@ -3876,6 +3855,27 @@ + + + + + + + + + + UBL-SR-53 + fatal + + + + [UBL-SR-53]- CompanyID (VAT Identifier) must be stated when providing the PartyTaxScheme/TaxScheme/ID. + + + + + + @@ -12410,7 +12410,7 @@ - [UBL-CR-572]-A UBL invoice should not include the InvoiceLine Item BuyersItemIdentification BarecodeSymbologyID + [UBL-CR-572]-A UBL invoice should not include the InvoiceLine Item BuyersItemIdentification BarcodeSymbologyID @@ -12485,7 +12485,7 @@ - [UBL-CR-577]-A UBL invoice should not include the InvoiceLine Item SellersItemIdentification BarecodeSymbologyID + [UBL-CR-577]-A UBL invoice should not include the InvoiceLine Item SellersItemIdentification BarcodeSymbologyID @@ -12575,7 +12575,7 @@ - [UBL-CR-583]-A UBL invoice should not include the InvoiceLine Item StandardItemIdentification BarecodeSymbologyID + [UBL-CR-583]-A UBL invoice should not include the InvoiceLine Item StandardItemIdentification BarcodeSymbologyID @@ -13287,21 +13287,6 @@ - - - - UBL-CR-631 - warning - - - - [UBL-CR-631]-A UBL invoice should not include the InvoiceLine Item Price OrderableUnitFactorRate - - - - - - @@ -14754,6 +14739,36 @@ + + + + + + + UBL-SR-54 + fatal + + + + [UBL-SR-54]-An Invoice shall contain maximum one Payment Card account (BG-18). + + + + + + + + + + UBL-SR-55 + fatal + + + + [UBL-SR-55]-An Invoice shall contain maximum one Payment Mandate (BG-19). + + + @@ -15012,9 +15027,9 @@ - + - + UBL-SR-22 fatal @@ -15076,9 +15091,9 @@ - + - + BR-CL-01 fatal @@ -15097,9 +15112,9 @@ - + - + BR-CL-03 fatal @@ -15118,9 +15133,9 @@ - + - + BR-CL-04 fatal @@ -15139,9 +15154,9 @@ - + - + BR-CL-05 fatal @@ -15181,9 +15196,9 @@ - + - + BR-CL-07 fatal @@ -15202,9 +15217,9 @@ - + - + BR-CL-10 fatal @@ -15223,9 +15238,9 @@ - + - + BR-CL-11 fatal @@ -15244,9 +15259,9 @@ - + - + BR-CL-13 fatal @@ -15413,9 +15428,9 @@ - + - + BR-CL-21 fatal @@ -15434,9 +15449,9 @@ - + - + BR-CL-22 fatal @@ -15498,9 +15513,9 @@ - + - + BR-CL-25 fatal @@ -15519,9 +15534,9 @@ - + - + BR-CL-26 fatal diff --git a/validator/src/test/java/org/mustangproject/validator/LibraryTest.java b/validator/src/test/java/org/mustangproject/validator/LibraryTest.java index 8a072a0c..e101b4fc 100644 --- a/validator/src/test/java/org/mustangproject/validator/LibraryTest.java +++ b/validator/src/test/java/org/mustangproject/validator/LibraryTest.java @@ -331,4 +331,29 @@ public class LibraryTest extends ResourceCase { .isEqualTo(0); } + public void testSubInvoiceLineExportValidity() { + File tempFile = new File("../library/target/testout-SubInvoiceLines.pdf"); + assertTrue(tempFile.exists()); + ZUGFeRDValidator zfv = new ZUGFeRDValidator(); + + String res = zfv.validate(tempFile.getAbsolutePath()); + + assertThat(res).valueByXPath("/validation/pdf/summary/@status") + .isEqualTo("valid"); + + assertThat(res).valueByXPath("/validation/xml/summary/@status") + .isEqualTo("valid"); + + assertThat(res).valueByXPath("/validation/summary/@status") + .isEqualTo("valid"); + + assertThat(res).valueByXPath("count(//error)") + .asInt() + .isEqualTo(0); + + assertThat(res).valueByXPath("count(//warning)") + .asInt() + .isEqualTo(0); + } + } diff --git a/validator/src/test/java/org/mustangproject/validator/PDFValidatorTest.java b/validator/src/test/java/org/mustangproject/validator/PDFValidatorTest.java index 08daa3ec..2bc09042 100644 --- a/validator/src/test/java/org/mustangproject/validator/PDFValidatorTest.java +++ b/validator/src/test/java/org/mustangproject/validator/PDFValidatorTest.java @@ -179,6 +179,15 @@ public class PDFValidatorTest extends ResourceCase { assertEquals(true, actual .contains("XMP Metadata: Could not parse XMP metadata (XML invalid)")); + + contents = getResourceAsByteArray("103767-Invoice-2026-03-24-1.pdf"); + + pv.setFilenameAndContents("103767-Invoice-2026-03-24-1.pdf", contents); + vc.clear(); + pv.validate(); + actual = pv.getXMLResult(); + + assertEquals(false, actual.contains(""; + assertThat(s).valueByXPath("count(//error)") + .asInt() + .isEqualTo(0); + assertThat(s).valueByXPath("count(//warning)") + .asInt() + .isEqualTo(12); + assertThat(s).valueByXPath("count(//warning[contains(text(),'XP_Z12_012')])") + .asInt() + .isEqualTo(9); + assertThat(s).valueByXPath("/validation/summary/@status") + .asString() + .isEqualTo("valid"); + } catch (final IrrecoverableValidationError e) { + fail(e.getMessage()); + } + } + public void testUBLValidation() { ValidationContext ctx = new ValidationContext(null); @@ -378,6 +405,38 @@ public class XMLValidatorTest extends ResourceCase { } + public void testBRDEC23Regression() { + // Regression test: BR-DEC-23 must fire for XRechnung CII invoices whose + // LineTotalAmount (BT-131) has more than 2 decimal places. + // The bug existed in CEN Schematron <=1.3.12 due to a wrong XPath context + // (SpecifiedTradeSettlement instead of SpecifiedLineTradeSettlement). + final ValidationContext ctx = new ValidationContext(null); + final XMLValidator xv = new XMLValidator(ctx); + final XPathEngine xpath = new JAXPXPathEngine(); + + File tempFile = getResourceAsFile("invalidXRV30_BR-DEC-23.xml"); + boolean noExceptions = true; + try { + xv.setFilename(tempFile.getAbsolutePath()); + xv.validate(); + + String s = "" + xv.getXMLResult() + ""; + Source source = Input.fromString(s).build(); + + // must be invalid overall + String status = xpath.evaluate("/validation/summary/@status", source); + assertEquals("invalid", status); + + // must contain the BR-DEC-23 rule ID in at least one error + assertThat(s).valueByXPath("count(//error[contains(text(),'BR-DEC-23')])") + .asInt() + .isGreaterThanOrEqualTo(1); + } catch (IrrecoverableValidationError e) { + noExceptions = false; + } + assertTrue(noExceptions); + } + public void testSubInvoiceLineHierarchy() { final ValidationContext ctx = new ValidationContext(null); final XMLValidator xv = new XMLValidator(ctx); diff --git a/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java b/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java index 35a30d9c..4f594721 100644 --- a/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java +++ b/validator/src/test/java/org/mustangproject/validator/ZUGFeRDValidatorTest.java @@ -261,4 +261,27 @@ public class ZUGFeRDValidatorTest extends ResourceCase { .isEqualTo("valid"); } + public void testXMLFRValidation() { + File tempFile = getResourceAsFile("validV2FR.xml"); + ZUGFeRDValidator zfv = new ZUGFeRDValidator(); + + String res = zfv.validate(tempFile.getAbsolutePath()); + + assertThat(res).valueByXPath("count(//error)") + .asInt() + .isEqualTo(0); + assertThat(res).valueByXPath("count(//warning)") + .asInt() + .isEqualTo(12); // 3 empty-element warnings plus 9 French schematron warnings + assertThat(res).valueByXPath("count(//notice)") + .asInt() + .isEqualTo(9); // 9 notices RE XRechnung 3.0 + assertThat(res).valueByXPath("/validation/summary/@status") + .asString() + .isEqualTo("valid");// expect to be valid because XR notices are, well, only notices + assertThat(res).valueByXPath("/validation/xml/summary/@status") + .asString() + .isEqualTo("valid"); + + } } diff --git a/validator/src/test/resources/103767-Invoice-2026-03-24-1.pdf b/validator/src/test/resources/103767-Invoice-2026-03-24-1.pdf new file mode 100644 index 00000000..ed751846 Binary files /dev/null and b/validator/src/test/resources/103767-Invoice-2026-03-24-1.pdf differ diff --git a/validator/src/test/resources/invalidXRV30_BR-DEC-23.xml b/validator/src/test/resources/invalidXRV30_BR-DEC-23.xml new file mode 100644 index 00000000..28b7c839 --- /dev/null +++ b/validator/src/test/resources/invalidXRV30_BR-DEC-23.xml @@ -0,0 +1,114 @@ + + + + + + urn:fdc:peppol.eu:2017:poacc:billing:01:1.0 + + + urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0 + + + + BR-DEC-23-REGRESSION + 380 + + 20240101 + + + + + + 1 + + + Test product + + + + 288.79 + + + + 1 + + + + VAT + S + 19 + + + + 288.7934 + + + + + 04011000-12345-03 + + Test Seller GmbH + + 12345 + Seller Street 1 + Berlin + DE + + + seller@test.de + + + DE123456789 + + + + Test Buyer AG + + 10115 + Buyer Street 2 + Berlin + DE + + + buyer@test.de + + + + + + EUR + + 58 + + DE75512108001245126199 + + + + 54.87 + VAT + 288.79 + S + 19 + + + Zahlbar sofort ohne Abzug. + + + 288.79 + 288.79 + 54.87 + 343.66 + 343.66 + + + + diff --git a/validator/src/test/resources/validV2FR.xml b/validator/src/test/resources/validV2FR.xml new file mode 100644 index 00000000..6efd4c4b --- /dev/null +++ b/validator/src/test/resources/validV2FR.xml @@ -0,0 +1,186 @@ + + + + + urn:cen.eu:en16931:2017 + + + + FAC-20171118-506 + 380 + 20171118 + + + + + 1 + + + Prestation graphique + + + + + 160.0000 + 1.0000 + + + 160.0000 + 1.0000 + + + + 1.0000 + + + + VAT + S + 7.00 + + + 160.00 + + + + + + 2 + + + Ballon decoratif 500 ml + + + + + 0.7900 + 1.0000 + + + 0.7900 + 1.0000 + + + + 400.0000 + + + + VAT + S + 19.00 + + + 316.00 + + + + + + 3 + + + Helium par litre + + + + + 0.1000 + 1.0000 + + + 0.1000 + 1.0000 + + + + 200.0000 + + + + VAT + S + 19.00 + + + 20.00 + + + + + + Atelier du Ballon SARL + + 75001 + 1 rue du Louvre + Paris + FR + + + 552081317 + + + FR44552081317 + + + + Maison de Evenements SAS + + 69002 + 18 rue de Brest + Lyon + FR + + + FR19787878784 + + + + + + 20171117 + + + + FAC-20171118-506 + EUR + + 42 + Virement bancaire + + FR76 3000 6000 0112 3456 7890 189 + + + AGRIFRPPXXX + + + + 11.20 + VAT + 160.00 + S + 7.00 + + + 63.84 + VAT + 336.00 + S + 19.00 + + + Paiement sans escompte avant le 09.12.2017 + 20171209 + + + 496.00 + 0.00 + 0.00 + 496.00 + 75.04 + 571.04 + 571.04 + + + +