Merge branch 'master' into bugfix/1062
This commit is contained in:
33
History.md
33
History.md
@@ -1,5 +1,30 @@
|
|||||||
#1049
|
2.23.0 "Bonne Journées de la Facture Électronique"
|
||||||
#1044
|
=======
|
||||||
|
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
|
2.22.0
|
||||||
=======
|
=======
|
||||||
@@ -98,7 +123,7 @@ corrections
|
|||||||
- support parsing of BT-90 CreditorReferenceID
|
- support parsing of BT-90 CreditorReferenceID
|
||||||
- #871 schema validation does not ignore external entities
|
- #871 schema validation does not ignore external entities
|
||||||
- #868 Fix wrong version in History.md
|
- #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)
|
- #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)
|
- #731 Got a broken translation key when visualizing XML into PDF (xr:Business_process_type)
|
||||||
- #865 Add sevdesk signature to PDF creators
|
- #865 Add sevdesk signature to PDF creators
|
||||||
@@ -199,7 +224,7 @@ corrections
|
|||||||
- #679 validation of a XR does not ignore whitespace
|
- #679 validation of a XR does not ignore whitespace
|
||||||
- #681 IBAN assigned to invoice sender not recipient on direct debit
|
- #681 IBAN assigned to invoice sender not recipient on direct debit
|
||||||
- #689 incorrect element order when both charge reason and reasoncode are specified
|
- #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
|
- updated verapdf from 1.26.1 to 1.26.2
|
||||||
- cashDiscount JSON now corrently ignores values for cii and xr methods
|
- cashDiscount JSON now corrently ignores values for cii and xr methods
|
||||||
|
|
||||||
|
|||||||
@@ -44,74 +44,73 @@ public class Main {
|
|||||||
|
|
||||||
private static String getUsage() {
|
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"
|
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 license display open source license and notice\n"
|
||||||
+ " --action metrics\n"
|
+ " --action metrics\n"
|
||||||
+ " -d, --directory count ZUGFeRD files in directory to be scanned\n"
|
+ " -d, --directory count ZUGFeRD files in directory to be scanned\n"
|
||||||
+ " If it is a directory, it will recurse.\n"
|
+ " If it is a directory, it will recurse.\n"
|
||||||
+ " -l, --listfromstdin count ZUGFeRD files from a list of linefeed separated files on runtime.\n"
|
+ " -l, --listfromstdin count ZUGFeRD files from a list of linefeed separated files on runtime.\n"
|
||||||
+ " It will start once a blank line has been entered.\n" + "\n"
|
+ " It will start once a blank line has been entered.\n" + "\n"
|
||||||
+ " Additional parameter for both count operations\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"
|
+ " [-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"
|
+ " [--disable-file-logging] disable logging to file.\n"
|
||||||
+ " --action extract extract Factur-X PDF to XML file\n"
|
+ " --action extract extract Factur-X PDF to XML file\n"
|
||||||
+ " Additional parameters (optional - user will be prompted if not defined)\n"
|
+ " Additional parameters (optional - user will be prompted if not defined)\n"
|
||||||
+ " [--source <filename>]: set input PDF file\n"
|
+ " [--source <filename>]: set input PDF file\n"
|
||||||
+ " [--out <filename>]: set output XML file\n"
|
+ " [--out <filename>]: set output XML file\n"
|
||||||
+ " --action a3only upgrade from PDF/A1 to A3 only (no ZUGFeRD data attached)\n"
|
+ " --action a3only upgrade from PDF/A1 to A3 only (no ZUGFeRD data attached)\n"
|
||||||
+ " Additional parameters (optional - user will be prompted if not defined)\n"
|
+ " Additional parameters (optional - user will be prompted if not defined)\n"
|
||||||
+ " [--source <filename>]: set input PDF file\n"
|
+ " [--source <filename>]: set input PDF file\n"
|
||||||
+ " [--out <filename>]: set output PDF file\n"
|
+ " [--out <filename>]: set output PDF file\n"
|
||||||
+ " --action combine combine XML and PDF file to Factur-X 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"
|
+ " Additional parameters (optional - user will be prompted if not defined)\n"
|
||||||
+ " [--source <filename>]: set input PDF file\n"
|
+ " [--source <filename>]: set input PDF file\n"
|
||||||
+ " [--source-xml <filename>]: set input XML file\n"
|
+ " [--source-xml <filename>]: set input XML file\n"
|
||||||
+ " [--out <filename>]: set output PDF file\n"
|
+ " [--out <filename>]: set output PDF file\n"
|
||||||
+ " [--format <fx|zf|ox|da>]: set Factur-X, ZUGFeRD, Order-X or Cross Industry Despatch Advice\n"
|
+ " [--format <fx|zf|ox|da>]: set Factur-X, ZUGFeRD, Order-X or Cross Industry Despatch Advice\n"
|
||||||
+ " [--version <1|2>]: set ZUGFeRD version\n"
|
+ " [--version <1|2>]: set ZUGFeRD version\n"
|
||||||
+ " [--profile <...>]: set ZUGFeRD profile\n"
|
+ " [--profile <...>]: set ZUGFeRD profile\n"
|
||||||
+ " For ZUGFeRD v1 or Order-X: <B>ASIC, <C>OMFORT or EX<T>ENDED\n"
|
+ " For ZUGFeRD v1 or Order-X: <B>ASIC, <C>OMFORT or EX<T>ENDED\n"
|
||||||
+ " For ZUGFeRD v2: <M>INIMUM, BASIC <W>L, <B>ASIC, <C>IUS, <E>N16931, <X>Rechnung, EX<T>ENDED\n"
|
+ " For ZUGFeRD v2: <M>INIMUM, BASIC <W>L, <B>ASIC, <C>IUS, <E>N16931, <X>Rechnung, EXTENDED-CTC-[F]R, EX<T>ENDED\n"
|
||||||
+ " [--attachments <filenames>]: list of file attachments (passing a single empty file name prevents prompting)\n"
|
+ " [--attachments <filenames>]: list of file attachments (passing a single empty file name prevents prompting)\n"
|
||||||
+ " [--no-additional-attachments]: prevent prompting for attachments\n"
|
+ " [--no-additional-attachments]: prevent prompting for attachments\n"
|
||||||
+ " --action ubl convert UN/CEFACT 2016b CII XML to UBL XML\n"
|
+ " --action ubl convert UN/CEFACT 2016b CII XML to UBL XML\n"
|
||||||
+ " [--source <filename>]: set input XML file\n"
|
+ " [--source <filename>]: set input XML file\n"
|
||||||
+ " [--out <filename>]: set output XML file\n"
|
+ " [--out <filename>]: set output XML file\n"
|
||||||
+ " --action upgrade upgrade ZUGFeRD XML to ZUGFeRD 2 XML\n"
|
+ " --action upgrade upgrade ZUGFeRD XML to ZUGFeRD 2 XML\n"
|
||||||
+ " Additional parameters (optional - user will be prompted if not defined)\n"
|
+ " Additional parameters (optional - user will be prompted if not defined)\n"
|
||||||
+ " [--source <filename>]: set input XML ZUGFeRD 1 file\n"
|
+ " [--source <filename>]: set input XML ZUGFeRD 1 file\n"
|
||||||
+ " [--out <filename>]: set output XML ZUGFeRD 2 file\n"
|
+ " [--out <filename>]: set output XML ZUGFeRD 2 file\n"
|
||||||
+ " --action validate validate XML or PDF file \n"
|
+ " --action validate validate XML or PDF file \n"
|
||||||
+ " [--no-notices]: refrain from reporting notices\n"
|
+ " [--no-notices]: refrain from reporting notices\n"
|
||||||
+ " [--logAppend <text>]: text to be added to log line\n"
|
+ " [--logAppend <text>]: text to be added to log line\n"
|
||||||
+ " Additional parameters (optional - user will be prompted if not defined)\n"
|
+ " Additional parameters (optional - user will be prompted if not defined)\n"
|
||||||
+ " [--source <filename>]: input PDF or XML file\n"
|
+ " [--source <filename>]: input PDF or XML file\n"
|
||||||
+ " [--log-as-pdf]: save log output as pdf\n"
|
+ " [--log-as-pdf]: save log output as pdf\n"
|
||||||
+ " --action validateExpectInvalid validate directory recursively expecting negative results \n"
|
+ " --action validateExpectInvalid validate directory recursively expecting negative results \n"
|
||||||
+ " [--no-notices]: refrain from reporting notices\n"
|
+ " [--no-notices]: refrain from reporting notices\n"
|
||||||
+ " Additional parameters (user will be prompted if not defined)\n"
|
+ " Additional parameters (user will be prompted if not defined)\n"
|
||||||
+ " -d, --directory to check recursively\n"
|
+ " -d, --directory to check recursively\n"
|
||||||
+ " Additional parameters (optional)\n"
|
+ " Additional parameters (optional)\n"
|
||||||
+ " --exclude: comma-separated list of filenames to ignore\n"
|
+ " --exclude: comma-separated list of filenames to ignore\n"
|
||||||
+ " --action validateExpectValid validate directory recursively expecting positive results \n"
|
+ " --action validateExpectValid validate directory recursively expecting positive results \n"
|
||||||
+ " [--no-notices]: refrain from reporting notices\n"
|
+ " [--no-notices]: refrain from reporting notices\n"
|
||||||
+ " Additional parameters (user will be prompted if not defined)\n"
|
+ " Additional parameters (user will be prompted if not defined)\n"
|
||||||
+ " -d, --directory to check recursively \n"
|
+ " -d, --directory to check recursively \n"
|
||||||
+ " Additional parameters (user will be prompted if not defined)\n"
|
+ " Additional parameters (user will be prompted if not defined)\n"
|
||||||
+ " --exclude: comma-separated list of filenames to ignore\n"
|
+ " --exclude: comma-separated list of filenames to ignore\n"
|
||||||
+ " --action visualize convert XML to HTML \n"
|
+ " --action visualize convert XML to HTML \n"
|
||||||
+ " [--language <lang>]: set output lang (en, fr or de)\n"
|
+ " [--language <lang>]: set output lang (en, fr or de)\n"
|
||||||
+ " [--source <filename>]: set input XML file\n"
|
+ " [--source <filename>]: set input XML file\n"
|
||||||
+ " [--out <filename>]: set output HTML file\n"
|
+ " [--out <filename>]: set output HTML file\n"
|
||||||
+ " --action pdf convert XML to PDF \n"
|
+ " --action pdf convert XML to PDF \n"
|
||||||
+ " [--source <filename>]: set input XML file\n"
|
+ " [--source <filename>]: set input XML file\n"
|
||||||
+ " [--out <filename>]: set output PDF file\n"
|
+ " [--out <filename>]: set output PDF file\n";
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void printHelp() {
|
private static void printHelp() {
|
||||||
System.out.println("Mustangproject.org " + org.mustangproject.ZUGFeRD.Version.VERSION + " \r\n"
|
System.out.println("Mustangproject.org " + org.mustangproject.ZUGFeRD.Version.VERSION + " \r\n"
|
||||||
+ "A Apache Public License tool for e-invoices with\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");
|
+ "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
|
* Prompts the user for a input or output filename
|
||||||
*
|
*
|
||||||
* @param prompt the text the user is asked
|
* @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 defaultFilename a default Filename. Passing an empty string
|
||||||
* @param expectedExtension will warn if filename does not match expected file extension, "or" possible with e.g. pdf|xml
|
* 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 ensureFileExists will warn if file does NOT exist (for input files)
|
||||||
* @param ensureFileNotExists will warn if file DOES exist (for output files)
|
* @param ensureFileNotExists will warn if file DOES exist (for output files)
|
||||||
* @return String
|
* @return String
|
||||||
*/
|
*/
|
||||||
protected static String getFilenameFromUser(String prompt, String defaultFilename, String expectedExtension,
|
protected static String getFilenameFromUser(String prompt, String defaultFilename, String expectedExtension,
|
||||||
boolean ensureFileExists, boolean ensureFileNotExists) {
|
boolean ensureFileExists, boolean ensureFileNotExists) {
|
||||||
boolean fileExistenceOK = false;
|
boolean fileExistenceOK = false;
|
||||||
String selectedName = "";
|
String selectedName = "";
|
||||||
do {
|
do {
|
||||||
@@ -187,7 +188,7 @@ public class Main {
|
|||||||
if (selectedName.isEmpty()) {
|
if (selectedName.isEmpty()) {
|
||||||
// pressed return without entering anything
|
// pressed return without entering anything
|
||||||
if (defaultFilename.isEmpty()) {
|
if (defaultFilename.isEmpty()) {
|
||||||
return ""; //no default -> this is an optional filename
|
return ""; // no default -> this is an optional filename
|
||||||
}
|
}
|
||||||
selectedName = defaultFilename;
|
selectedName = defaultFilename;
|
||||||
}
|
}
|
||||||
@@ -211,7 +212,7 @@ public class Main {
|
|||||||
}
|
}
|
||||||
} else if (!hasCorrectExtension) {
|
} else if (!hasCorrectExtension) {
|
||||||
System.err.println("Expected " + expectedExtension
|
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 = "";
|
String selectedAnswer = "";
|
||||||
try {
|
try {
|
||||||
selectedAnswer = buffer.readLine();
|
selectedAnswer = buffer.readLine();
|
||||||
@@ -270,6 +271,7 @@ public class Main {
|
|||||||
|
|
||||||
/***
|
/***
|
||||||
* converts from CII to UBL
|
* converts from CII to UBL
|
||||||
|
*
|
||||||
* @param xmlName the name of the xml file
|
* @param xmlName the name of the xml file
|
||||||
* @param outName the name of the output file
|
* @param outName the name of the output file
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
@@ -309,7 +311,8 @@ public class Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Plain Java
|
// 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) {
|
private static String convertInputStreamToString(InputStream is) {
|
||||||
try (InputStream inputStream = is) {
|
try (InputStream inputStream = is) {
|
||||||
int DEFAULT_BUFFER_SIZE = 8192;
|
int DEFAULT_BUFFER_SIZE = 8192;
|
||||||
@@ -333,7 +336,9 @@ public class Main {
|
|||||||
|
|
||||||
/***
|
/***
|
||||||
* the main function of the commandline tool...
|
* 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) {
|
public static void main(String[] args) {
|
||||||
try {
|
try {
|
||||||
@@ -347,12 +352,14 @@ public class Main {
|
|||||||
options.addOption(new Option("f", "format", true, "which format to output"));
|
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("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("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");
|
Option attachmentOpt = new Option("attachments", "attachments", true, "File attachments");
|
||||||
attachmentOpt.setValueSeparator(',');
|
attachmentOpt.setValueSeparator(',');
|
||||||
attachmentOpt.setArgs(Option.UNLIMITED_VALUES);
|
attachmentOpt.setArgs(Option.UNLIMITED_VALUES);
|
||||||
options.addOption(attachmentOpt);
|
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.setValueSeparator(',');
|
||||||
excludeOpt.setArgs(Option.UNLIMITED_VALUES);
|
excludeOpt.setArgs(Option.UNLIMITED_VALUES);
|
||||||
options.addOption(excludeOpt);
|
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("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("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("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(
|
||||||
options.addOption(new Option("disable-file-logging", "disable-file-logging", false, "suppress logging to file"));
|
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("d", "directory", true, "which directory to operate on"));
|
||||||
options.addOption(new Option("i", "ignorefileextension", false, "ignore non-matching file extensions"));
|
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"));
|
options.addOption(new Option("l", "listfromstdin", false, "take list of files from commandline"));
|
||||||
@@ -378,7 +387,7 @@ public class Main {
|
|||||||
// Retrieve all options
|
// Retrieve all options
|
||||||
action = cmd.getOptionValue("action");
|
action = cmd.getOptionValue("action");
|
||||||
String directoryName = cmd.getOptionValue("directory");
|
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 ignoreFileExt = cmd.hasOption("ignorefileextension");
|
||||||
boolean noAttachments = cmd.hasOption("no-additional-attachments");
|
boolean noAttachments = cmd.hasOption("no-additional-attachments");
|
||||||
boolean helpRequested = cmd.hasOption("help") || ((action != null) && (action.equals("help")));
|
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[] attachmentFilenames = cmd.hasOption("attachments") ? cmd.getOptionValues("attachments") : null;
|
||||||
String[] excludedFilenames = cmd.hasOption("exclude") ? cmd.getOptionValues("exclude") : null;
|
String[] excludedFilenames = cmd.hasOption("exclude") ? cmd.getOptionValues("exclude") : null;
|
||||||
|
|
||||||
|
|
||||||
ArrayList<FileAttachment> attachments = new ArrayList<>();
|
ArrayList<FileAttachment> attachments = new ArrayList<>();
|
||||||
/*
|
/*
|
||||||
setting system property to disable FILE appender and
|
* setting system property to disable FILE appender and
|
||||||
suppress creation of files and folders.
|
* suppress creation of files and folders.
|
||||||
*/
|
*/
|
||||||
System.setProperty("FILE_APPENDER_ENABLED", ((Boolean) (disableFileLogging == false)).toString());
|
System.setProperty("FILE_APPENDER_ENABLED", ((Boolean) (disableFileLogging == false)).toString());
|
||||||
|
|
||||||
@@ -411,14 +419,17 @@ public class Main {
|
|||||||
if (helpRequested) {
|
if (helpRequested) {
|
||||||
printHelp();
|
printHelp();
|
||||||
optionsRecognized = true;
|
optionsRecognized = true;
|
||||||
} /* else if ((action!=null)&&(action.equals("license"))) {
|
} /*
|
||||||
printLicense();
|
* else if ((action!=null)&&(action.equals("license"))) {
|
||||||
optionsRecognized=true;
|
* printLicense();
|
||||||
} */ else if ((action != null) && (action.equals("metrics"))) {
|
* optionsRecognized=true;
|
||||||
|
* }
|
||||||
|
*/ else if ((action != null) && (action.equals("metrics"))) {
|
||||||
performMetrics(directoryName, filesFromStdIn, ignoreFileExt);
|
performMetrics(directoryName, filesFromStdIn, ignoreFileExt);
|
||||||
optionsRecognized = true;
|
optionsRecognized = true;
|
||||||
} else if ((action != null) && (action.equals("combine"))) {
|
} 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;
|
optionsRecognized = true;
|
||||||
} else if ((action != null) && (action.equals("extract"))) {
|
} else if ((action != null) && (action.equals("extract"))) {
|
||||||
performExtract(sourceName, outName);
|
performExtract(sourceName, outName);
|
||||||
@@ -439,7 +450,8 @@ public class Main {
|
|||||||
performUBL(sourceName, outName);
|
performUBL(sourceName, outName);
|
||||||
optionsRecognized = true;
|
optionsRecognized = true;
|
||||||
} else if ((action != null) && (action.equals("validate"))) {
|
} 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"))) {
|
} else if ((action != null) && (action.equals("validateExpectValid"))) {
|
||||||
optionsRecognized = performValidateExpect(true, directoryName, excludedFilenames);
|
optionsRecognized = performValidateExpect(true, directoryName, excludedFilenames);
|
||||||
} else if ((action != null) && (action.equals("validateExpectInvalid"))) {
|
} 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;
|
boolean optionsRecognized;
|
||||||
if (sourceName == null) {
|
if (sourceName == null) {
|
||||||
sourceName = getFilenameFromUser("Source PDF or XML", "invoice.pdf", "pdf|xml", true, false);
|
sourceName = getFilenameFromUser("Source PDF or XML", "invoice.pdf", "pdf|xml", true, false);
|
||||||
@@ -482,7 +495,7 @@ public class Main {
|
|||||||
String validationResultXML = zfv.validate(sourceName);
|
String validationResultXML = zfv.validate(sourceName);
|
||||||
System.out.println(validationResultXML);
|
System.out.println(validationResultXML);
|
||||||
|
|
||||||
if( createLogAsPDF) {
|
if (createLogAsPDF) {
|
||||||
ValidationLogVisualizer vlvi = new ValidationLogVisualizer();
|
ValidationLogVisualizer vlvi = new ValidationLogVisualizer();
|
||||||
String fileBasename = FilenameUtils.getBaseName(sourceName);
|
String fileBasename = FilenameUtils.getBaseName(sourceName);
|
||||||
|
|
||||||
@@ -516,7 +529,6 @@ public class Main {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static void performConvert(String pdfName, String outName) throws IOException {
|
private static void performConvert(String pdfName, String outName) throws IOException {
|
||||||
/*
|
/*
|
||||||
* ZUGFeRDExporter ze= new ZUGFeRDExporterFromA1Factory()
|
* ZUGFeRDExporter ze= new ZUGFeRDExporterFromA1Factory()
|
||||||
@@ -541,9 +553,9 @@ public class Main {
|
|||||||
|
|
||||||
// All params are good! continue...
|
// All params are good! continue...
|
||||||
try (ZUGFeRDExporterFromA1 ze = new ZUGFeRDExporterFromA1()) {
|
try (ZUGFeRDExporterFromA1 ze = new ZUGFeRDExporterFromA1()) {
|
||||||
ze.convertOnly().load(pdfName);
|
ze.convertOnly().load(pdfName);
|
||||||
ze.export(outName);
|
ze.export(outName);
|
||||||
System.out.println("Written to " + 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,
|
private static void performCombine(String pdfName, String xmlName, String outName, String format, String zfVersion,
|
||||||
String zfProfile, Boolean ignoreInputErrors, String[] attachmentFilenames, ArrayList<FileAttachment> attachments, Boolean noAttachments) throws Exception {
|
String zfProfile, Boolean ignoreInputErrors, String[] attachmentFilenames,
|
||||||
|
ArrayList<FileAttachment> attachments, Boolean noAttachments) throws Exception {
|
||||||
/*
|
/*
|
||||||
* ZUGFeRDExporter ze= new ZUGFeRDExporterFromA1Factory()
|
* ZUGFeRDExporter ze= new ZUGFeRDExporterFromA1Factory()
|
||||||
* .setProducer("toecount") .setCreator(System.getProperty("user.name"))
|
* .setProducer("toecount") .setCreator(System.getProperty("user.name"))
|
||||||
@@ -611,11 +624,13 @@ public class Main {
|
|||||||
byte attachmentContents[] = null;
|
byte attachmentContents[] = null;
|
||||||
String attachmentFilename, attachmentMime;
|
String attachmentFilename, attachmentMime;
|
||||||
if (!noAttachments) {
|
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) {
|
if (attachmentFilename.length() != 0) {
|
||||||
attachmentContents = Files.readAllBytes(Paths.get(attachmentFilename));
|
attachmentContents = Files.readAllBytes(Paths.get(attachmentFilename));
|
||||||
attachmentMime = Files.probeContentType(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 {
|
} else {
|
||||||
@@ -624,15 +639,17 @@ public class Main {
|
|||||||
if (!attachmentFilename.isEmpty()) {
|
if (!attachmentFilename.isEmpty()) {
|
||||||
byte[] attachmentContents = Files.readAllBytes(Paths.get(attachmentFilename));
|
byte[] attachmentContents = Files.readAllBytes(Paths.get(attachmentFilename));
|
||||||
String attachmentMime = Files.probeContentType(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) {
|
if (format == null) {
|
||||||
try {
|
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) {
|
} catch (Exception e) {
|
||||||
LOGGER.error(e.getMessage(), e);
|
LOGGER.error(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
@@ -642,7 +659,7 @@ public class Main {
|
|||||||
|
|
||||||
if (zfVersion == null) {
|
if (zfVersion == null) {
|
||||||
try {
|
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) {
|
} catch (Exception e) {
|
||||||
LOGGER.error(e.getMessage(), e);
|
LOGGER.error(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
@@ -659,8 +676,10 @@ public class Main {
|
|||||||
zfProfile = getStringFromUser("Profile (p)ilot", "p", "P|p");
|
zfProfile = getStringFromUser("Profile (p)ilot", "p", "P|p");
|
||||||
} else {
|
} else {
|
||||||
zfProfile = getStringFromUser(
|
zfProfile = getStringFromUser(
|
||||||
"Profile [M]INIMUM, BASIC [W]L, [B]ASIC,\n" + "[C]IUS, [E]N16931, EX[T]ENDED or [X]RECHNUNG", "E",
|
"Profile [M]INIMUM, BASIC [W]L, [B]ASIC,\n"
|
||||||
"M|m|W|w|B|b|C|c|E|e|T|t|X|x|");
|
+ "[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) {
|
} catch (Exception e) {
|
||||||
LOGGER.error(e.getMessage(), e);
|
LOGGER.error(e.getMessage(), e);
|
||||||
@@ -670,7 +689,6 @@ public class Main {
|
|||||||
}
|
}
|
||||||
zfProfile = zfProfile.toLowerCase();
|
zfProfile = zfProfile.toLowerCase();
|
||||||
|
|
||||||
|
|
||||||
// Verify params
|
// Verify params
|
||||||
ensureFileExists(pdfName);
|
ensureFileExists(pdfName);
|
||||||
ensureFileExists(xmlName);
|
ensureFileExists(xmlName);
|
||||||
@@ -712,6 +730,8 @@ public class Main {
|
|||||||
zfConformanceLevelProfile = Profiles.getByName(standard, "CIUS", zfIntVersion);
|
zfConformanceLevelProfile = Profiles.getByName(standard, "CIUS", zfIntVersion);
|
||||||
} else if (zfProfile.equals("e")) {
|
} else if (zfProfile.equals("e")) {
|
||||||
zfConformanceLevelProfile = Profiles.getByName(standard, "EN16931", zfIntVersion);
|
zfConformanceLevelProfile = Profiles.getByName(standard, "EN16931", zfIntVersion);
|
||||||
|
} else if (zfProfile.equals("f")) {
|
||||||
|
zfConformanceLevelProfile = Profiles.getByName(standard, "EXTENDED-CTC-FR", zfIntVersion);
|
||||||
} else if (zfProfile.equals("t")) {
|
} else if (zfProfile.equals("t")) {
|
||||||
zfConformanceLevelProfile = Profiles.getByName(standard, "EXTENDED", zfIntVersion);
|
zfConformanceLevelProfile = Profiles.getByName(standard, "EXTENDED", zfIntVersion);
|
||||||
} else if (zfProfile.equals("x")) {
|
} else if (zfProfile.equals("x")) {
|
||||||
@@ -738,7 +758,8 @@ public class Main {
|
|||||||
} else {
|
} else {
|
||||||
if (format.equals("fx")) {
|
if (format.equals("fx")) {
|
||||||
zfIntVersion = 2;// actually we are talking of generation, not version
|
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();
|
ze = new ZUGFeRDExporterFromPDFA();
|
||||||
if (ignoreInputErrors) {
|
if (ignoreInputErrors) {
|
||||||
@@ -748,16 +769,16 @@ public class Main {
|
|||||||
|
|
||||||
ze.load(pdfName);
|
ze.load(pdfName);
|
||||||
ze.setProducer("Mustang-cli")
|
ze.setProducer("Mustang-cli")
|
||||||
.setZUGFeRDVersion(zfIntVersion)
|
.setZUGFeRDVersion(zfIntVersion)
|
||||||
.setCreator(System.getProperty("user.name")).setProfile(zfConformanceLevelProfile);
|
.setCreator(System.getProperty("user.name")).setProfile(zfConformanceLevelProfile);
|
||||||
|
|
||||||
if (format.equals("zf")) {
|
if (format.equals("zf")) {
|
||||||
ze.disableFacturX();
|
ze.disableFacturX();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
for (FileAttachment attachment : attachments) {
|
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)));
|
ze.setXML(Files.readAllBytes(Paths.get(xmlName)));
|
||||||
@@ -774,7 +795,7 @@ public class Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void performMetrics(String directoryName, Boolean filesFromStdIn, Boolean ignoreFileExt)
|
private static void performMetrics(String directoryName, Boolean filesFromStdIn, Boolean ignoreFileExt)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
StatRun sr = new StatRun();
|
StatRun sr = new StatRun();
|
||||||
if (ignoreFileExt) {
|
if (ignoreFileExt) {
|
||||||
@@ -818,16 +839,14 @@ public class Main {
|
|||||||
} else {
|
} else {
|
||||||
System.out.println("ZUGFeRD XML source set to " + sourceName);
|
System.out.println("ZUGFeRD XML source set to " + sourceName);
|
||||||
}
|
}
|
||||||
if (!intoPDF) {
|
if (lang == null) {
|
||||||
if (lang == null) {
|
try {
|
||||||
try {
|
lang = getStringFromUser("Output language", "en", "en|de|fr");
|
||||||
lang = getStringFromUser("Output language", "en", "en|de|fr");
|
} catch (Exception e) {
|
||||||
} catch (Exception e) {
|
LOGGER.error(e.getMessage(), e);
|
||||||
LOGGER.error(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
System.out.println("Output language set to " + lang);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
System.out.println("Output language set to " + lang);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outName == null) {
|
if (outName == null) {
|
||||||
@@ -855,18 +874,18 @@ public class Main {
|
|||||||
ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer();
|
ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer();
|
||||||
String xml = null;
|
String xml = null;
|
||||||
try {
|
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) {
|
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);
|
xml = zvi.visualize(sourceName, langCode);
|
||||||
Files.write(Paths.get(outName), xml.getBytes());
|
Files.write(Paths.get(outName), xml.getBytes());
|
||||||
} else {
|
} else {
|
||||||
zvi.toPDF(sourceName, outName);
|
zvi.toPDF(sourceName, outName, langCode);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.error(e.getMessage(), e);
|
LOGGER.error(e.getMessage(), e);
|
||||||
@@ -880,13 +899,13 @@ public class Main {
|
|||||||
ExportResource("/xrechnung-viewer.css");
|
ExportResource("/xrechnung-viewer.css");
|
||||||
ExportResource("/xrechnung-viewer.js");
|
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) {
|
} catch (Exception e) {
|
||||||
LOGGER.error(e.getMessage(), e);
|
LOGGER.error(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -894,11 +913,14 @@ public class Main {
|
|||||||
*
|
*
|
||||||
* @param resourceName ie.: "/SmartLibrary.dll"
|
* @param resourceName ie.: "/SmartLibrary.dll"
|
||||||
* @return The path to the exported resource
|
* @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 {
|
static public String ExportResource(String resourceName) throws Exception {
|
||||||
String jarFolder;
|
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) {
|
if (stream == null) {
|
||||||
throw new Exception("Cannot get resource \"" + resourceName + "\" from Jar file.");
|
throw new Exception("Cannot get resource \"" + resourceName + "\" from Jar file.");
|
||||||
}
|
}
|
||||||
@@ -907,9 +929,9 @@ public class Main {
|
|||||||
byte[] buffer = new byte[4096];
|
byte[] buffer = new byte[4096];
|
||||||
jarFolder = System.getProperty("user.dir");
|
jarFolder = System.getProperty("user.dir");
|
||||||
try (FileOutputStream resStreamOut = new FileOutputStream(jarFolder + resourceName)) {
|
try (FileOutputStream resStreamOut = new FileOutputStream(jarFolder + resourceName)) {
|
||||||
while ((readBytes = stream.read(buffer)) > 0) {
|
while ((readBytes = stream.read(buffer)) > 0) {
|
||||||
resStreamOut.write(buffer, 0, readBytes);
|
resStreamOut.write(buffer, 0, readBytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
|||||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||||
public class Invoice implements IExportableTransaction {
|
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 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 Date issueDate = null, dueDate = null, deliveryDate = null;
|
||||||
protected TradeParty sender = null, recipient = null, deliveryAddress = null, payee = null, invoicer = null, invoicee = 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.
|
protected String creditorReferenceID; // required when direct debit is used.
|
||||||
private BigDecimal roundingAmount=null;
|
private BigDecimal roundingAmount=null;
|
||||||
private String paymentReference; // Remittance information / Verwendungszweck, BT-83
|
private String paymentReference; // Remittance information / Verwendungszweck, BT-83
|
||||||
|
private String businessProcessId;
|
||||||
public Invoice() {
|
public Invoice() {
|
||||||
ZFItems = new ArrayList<>();
|
ZFItems = new ArrayList<>();
|
||||||
cashDiscounts = new ArrayList<>();
|
cashDiscounts = new ArrayList<>();
|
||||||
setCurrency("EUR");
|
setCurrency("EUR");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getTestIndicator() {
|
||||||
|
return testIndicator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Invoice setTestIndicator() {
|
||||||
|
this.testIndicator = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDocumentName() {
|
public String getDocumentName() {
|
||||||
return documentName;
|
return documentName;
|
||||||
@@ -197,7 +208,18 @@ public class Invoice implements IExportableTransaction {
|
|||||||
setObjectIdentifierReferencedDocument(dr);
|
setObjectIdentifierReferencedDocument(dr);
|
||||||
return this;
|
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) {
|
public Invoice setNumber(String number) {
|
||||||
this.number = number;
|
this.number = number;
|
||||||
@@ -1156,4 +1178,14 @@ public class Invoice implements IExportableTransaction {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Invoice setBusinessProcessId(String id) {
|
||||||
|
this.businessProcessId = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBusinessProcessId() {
|
||||||
|
return businessProcessId;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
|
import java.net.URL;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
@@ -11,6 +12,19 @@ import org.apache.commons.io.IOUtils;
|
|||||||
import org.dom4j.io.XMLWriter;
|
import org.dom4j.io.XMLWriter;
|
||||||
import org.mustangproject.ZUGFeRD.ZUGFeRDDateFormat;
|
import org.mustangproject.ZUGFeRD.ZUGFeRDDateFormat;
|
||||||
import org.w3c.dom.Node;
|
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 {
|
public class XMLTools extends XMLWriter {
|
||||||
@Override
|
@Override
|
||||||
@@ -23,6 +37,86 @@ public class XMLTools extends XMLWriter {
|
|||||||
return super.escapeElementEntities(s);
|
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) {
|
public static String nDigitFormat(BigDecimal value, int scale) {
|
||||||
/*
|
/*
|
||||||
* I needed 123.45, locale independent.I tried
|
* I needed 123.45, locale independent.I tried
|
||||||
|
|||||||
@@ -49,12 +49,6 @@ public class CustomXMLProvider implements IXMLProvider {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setTest() {
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Profile getProfile() {
|
public Profile getProfile() {
|
||||||
return profile;
|
return profile;
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ public class DXExporterFromA1 extends DXExporterFromA3 {
|
|||||||
public DXExporterFromA1 setConformanceLevel(PDFAConformanceLevel newLevel) {
|
public DXExporterFromA1 setConformanceLevel(PDFAConformanceLevel newLevel) {
|
||||||
return (DXExporterFromA1) super.setConformanceLevel(newLevel);
|
return (DXExporterFromA1) super.setConformanceLevel(newLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DXExporterFromA1 setProducer(String producer){
|
public DXExporterFromA1 setProducer(String producer){
|
||||||
return (DXExporterFromA1) super.setProducer(producer);
|
return (DXExporterFromA1) super.setProducer(producer);
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ import jakarta.activation.FileDataSource;
|
|||||||
public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||||
|
|
||||||
protected PDFAConformanceLevel conformanceLevel = PDFAConformanceLevel.UNICODE;
|
protected PDFAConformanceLevel conformanceLevel = PDFAConformanceLevel.UNICODE;
|
||||||
|
protected boolean compressionEnabled = false;
|
||||||
protected ArrayList<FileAttachment> fileAttachments = new ArrayList<>();
|
protected ArrayList<FileAttachment> fileAttachments = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -319,8 +320,8 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
|||||||
dict.setString("Desc", description);
|
dict.setString("Desc", description);
|
||||||
|
|
||||||
ByteArrayInputStream fakeFile = new ByteArrayInputStream(data);
|
ByteArrayInputStream fakeFile = new ByteArrayInputStream(data);
|
||||||
PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile);
|
COSName filter = compressionEnabled ? COSName.FLATE_DECODE : null;
|
||||||
// ef.addCompression();
|
PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile, filter);
|
||||||
ef.setSubtype(subType);
|
ef.setSubtype(subType);
|
||||||
ef.setSize(data.length);
|
ef.setSize(data.length);
|
||||||
ef.setCreationDate(new GregorianCalendar());
|
ef.setCreationDate(new GregorianCalendar());
|
||||||
@@ -440,7 +441,6 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DXExporterFromA3 setCreator(String creator) {
|
public DXExporterFromA3 setCreator(String creator) {
|
||||||
this.creator = creator;
|
this.creator = creator;
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
|
|||||||
import org.mustangproject.FileAttachment;
|
import org.mustangproject.FileAttachment;
|
||||||
import org.mustangproject.IncludedNote;
|
import org.mustangproject.IncludedNote;
|
||||||
import org.mustangproject.ReferencedDocument;
|
import org.mustangproject.ReferencedDocument;
|
||||||
import org.mustangproject.TradeParty;
|
|
||||||
import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants;
|
import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants;
|
||||||
|
|
||||||
/***
|
/***
|
||||||
@@ -49,6 +48,11 @@ import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants;
|
|||||||
*/
|
*/
|
||||||
public interface IExportableTransaction {
|
public interface IExportableTransaction {
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
default boolean getTestIndicator() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* appears in /rsm:CrossIndustryDocument/rsm:HeaderExchangedDocument/ram:Name
|
* appears in /rsm:CrossIndustryDocument/rsm:HeaderExchangedDocument/ram:Name
|
||||||
*
|
*
|
||||||
@@ -626,4 +630,16 @@ public interface IExportableTransaction {
|
|||||||
default String getCreditorReferenceID() {
|
default String getCreditorReferenceID() {
|
||||||
return null;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,13 +22,9 @@ public interface IXMLProvider {
|
|||||||
|
|
||||||
public byte[] getXML();
|
public byte[] getXML();
|
||||||
|
|
||||||
public void setTest();
|
|
||||||
|
|
||||||
public void generateXML(IExportableTransaction trans);
|
public void generateXML(IExportableTransaction trans);
|
||||||
|
|
||||||
public void setProfile(Profile p);
|
public void setProfile(Profile p);
|
||||||
|
|
||||||
public Profile getProfile();
|
public Profile getProfile();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ public interface IZUGFeRDExporter extends Closeable, IExporter {
|
|||||||
public IZUGFeRDExporter load(InputStream pdfSource) throws IOException;
|
public IZUGFeRDExporter load(InputStream pdfSource) throws IOException;
|
||||||
public IZUGFeRDExporter setCreator(String creator);
|
public IZUGFeRDExporter setCreator(String creator);
|
||||||
public IZUGFeRDExporter setConformanceLevel(PDFAConformanceLevel newLevel);
|
public IZUGFeRDExporter setConformanceLevel(PDFAConformanceLevel newLevel);
|
||||||
|
public IZUGFeRDExporter setEnablePDFAttachmentCompression(boolean enablePDFAttachmentCompression);
|
||||||
public IZUGFeRDExporter setProducer(String producer);
|
public IZUGFeRDExporter setProducer(String producer);
|
||||||
public IZUGFeRDExporter setZUGFeRDVersion(int version);
|
public IZUGFeRDExporter setZUGFeRDVersion(int version);
|
||||||
public boolean ensurePDFIsValid(final DataSource dataSource) throws IOException;
|
public boolean ensurePDFIsValid(final DataSource dataSource) throws IOException;
|
||||||
|
|||||||
@@ -126,7 +126,6 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
|||||||
*/
|
*/
|
||||||
protected String orderXDocumentType = "ORDER";
|
protected String orderXDocumentType = "ORDER";
|
||||||
|
|
||||||
|
|
||||||
private boolean attachZUGFeRDHeaders = true;
|
private boolean attachZUGFeRDHeaders = true;
|
||||||
|
|
||||||
/***
|
/***
|
||||||
@@ -148,6 +147,9 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
|||||||
return "fx";
|
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
|
* 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)
|
* metadata level, this will not e.g. convert graphics to JPG-2000)
|
||||||
@@ -315,8 +317,8 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
|||||||
dict.setString("Desc", description);
|
dict.setString("Desc", description);
|
||||||
|
|
||||||
ByteArrayInputStream fakeFile = new ByteArrayInputStream(data);
|
ByteArrayInputStream fakeFile = new ByteArrayInputStream(data);
|
||||||
PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile);
|
COSName filter = compressionEnabled ? COSName.FLATE_DECODE : null;
|
||||||
// ef.addCompression();
|
PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile, filter);
|
||||||
ef.setSubtype(subType);
|
ef.setSubtype(subType);
|
||||||
ef.setSize(data.length);
|
ef.setSize(data.length);
|
||||||
ef.setCreationDate(new GregorianCalendar());
|
ef.setCreationDate(new GregorianCalendar());
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ public class Profiles {
|
|||||||
{"BASICWL", new Profile("BASICWL", "urn:factur-x.eu:1p0:basicwl")},
|
{"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")},
|
{"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")},
|
{"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")},
|
{"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
|
{"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
|
||||||
|
|
||||||
|
|||||||
@@ -136,11 +136,6 @@ public class UBLDAPullProvider implements IXMLProvider {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setTest() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setProfile(Profile p) {
|
public void setProfile(Profile p) {
|
||||||
profile = p;
|
profile = p;
|
||||||
@@ -150,5 +145,4 @@ public class UBLDAPullProvider implements IXMLProvider {
|
|||||||
public Profile getProfile() {
|
public Profile getProfile() {
|
||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import org.apache.fop.configuration.ConfigurationException;
|
|||||||
import org.apache.fop.configuration.DefaultConfigurationBuilder;
|
import org.apache.fop.configuration.DefaultConfigurationBuilder;
|
||||||
import org.apache.xmlgraphics.util.MimeConstants;
|
import org.apache.xmlgraphics.util.MimeConstants;
|
||||||
import org.mustangproject.ClasspathResolverURIAdapter;
|
import org.mustangproject.ClasspathResolverURIAdapter;
|
||||||
|
import org.mustangproject.XMLTools;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -42,8 +43,7 @@ public class ValidationLogVisualizer {
|
|||||||
|
|
||||||
|
|
||||||
public ValidationLogVisualizer() {
|
public ValidationLogVisualizer() {
|
||||||
mFactory = new net.sf.saxon.TransformerFactoryImpl();
|
mFactory = XMLTools.getTransformerFactory();
|
||||||
// fact = TransformerFactory.newInstance();
|
|
||||||
mFactory.setURIResolver(new ValidationLogVisualizer.ClasspathResourceURIResolver());
|
mFactory.setURIResolver(new ValidationLogVisualizer.ClasspathResourceURIResolver());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import javax.xml.transform.URIResolver;
|
|||||||
import javax.xml.transform.stream.StreamResult;
|
import javax.xml.transform.stream.StreamResult;
|
||||||
import javax.xml.transform.stream.StreamSource;
|
import javax.xml.transform.stream.StreamSource;
|
||||||
|
|
||||||
|
import org.mustangproject.XMLTools;
|
||||||
|
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* Uses a XSLT transformation to upgrade
|
* Uses a XSLT transformation to upgrade
|
||||||
@@ -31,8 +33,7 @@ public class XMLUpgrader {
|
|||||||
private Templates mXsltTemplate = null;
|
private Templates mXsltTemplate = null;
|
||||||
|
|
||||||
public XMLUpgrader() {
|
public XMLUpgrader() {
|
||||||
mFactory = new net.sf.saxon.TransformerFactoryImpl();
|
mFactory = XMLTools.getTransformerFactory();
|
||||||
//fact = TransformerFactory.newInstance();
|
|
||||||
mFactory.setURIResolver(new ClasspathResourceURIResolver());
|
mFactory.setURIResolver(new ClasspathResourceURIResolver());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,14 +49,6 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider {
|
|||||||
private String paymentTermsDescription;
|
private String paymentTermsDescription;
|
||||||
protected Profile profile = Profiles.getByName("COMFORT", 1);
|
protected Profile profile = Profiles.getByName("COMFORT", 1);
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* enables the flag to indicate a test invoice in the XML structure
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void setTest() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String vatFormat(BigDecimal value) {
|
protected String vatFormat(BigDecimal value) {
|
||||||
return XMLTools.nDigitFormat(value, 2);
|
return XMLTools.nDigitFormat(value, 2);
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ import java.util.Date;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.dom4j.Document;
|
import org.dom4j.Document;
|
||||||
@@ -61,14 +60,6 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
|||||||
private String paymentTermsDescription;
|
private String paymentTermsDescription;
|
||||||
protected Profile profile = Profiles.getByName("EN16931");
|
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) {
|
protected String vatFormat(BigDecimal value) {
|
||||||
return XMLTools.nDigitFormat(value, 2);
|
return XMLTools.nDigitFormat(value, 2);
|
||||||
}
|
}
|
||||||
@@ -384,11 +375,17 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
|||||||
+ " xmlns:qdt=\"urn:un:unece:uncefact:data:standard:QualifiedDataType:100\">"
|
+ " xmlns:qdt=\"urn:un:unece:uncefact:data:standard:QualifiedDataType:100\">"
|
||||||
+ "<!-- generated by: mustangproject.org v" + ZUGFeRD2PullProvider.class.getPackage().getImplementationVersion() + "-->"
|
+ "<!-- generated by: mustangproject.org v" + ZUGFeRD2PullProvider.class.getPackage().getImplementationVersion() + "-->"
|
||||||
+ "<rsm:ExchangedDocumentContext>\n";
|
+ "<rsm:ExchangedDocumentContext>\n";
|
||||||
// + "
|
|
||||||
// <ram:TestIndicator><udt:Indicator>"+testBooleanStr+"</udt:Indicator></ram:TestIndicator>"
|
|
||||||
//
|
|
||||||
|
|
||||||
if (getProfile() == Profiles.getByName("XRechnung")) {
|
if (trans.getTestIndicator() && profile == Profiles.getByName("Extended")) {
|
||||||
|
xml += "<ram:TestIndicator><udt:Indicator>" + trans.getTestIndicator() + "</udt:Indicator></ram:TestIndicator>";
|
||||||
|
}
|
||||||
|
|
||||||
|
String businessProcessId = trans.getBusinessProcessId();
|
||||||
|
if (businessProcessId != null && !businessProcessId.isBlank()) {
|
||||||
|
xml += "<ram:BusinessProcessSpecifiedDocumentContextParameter>\n"
|
||||||
|
+ "<ram:ID>" + XMLTools.encodeXML(businessProcessId) + "</ram:ID>\n"
|
||||||
|
+ "</ram:BusinessProcessSpecifiedDocumentContextParameter>\n";
|
||||||
|
}else if (getProfile() == Profiles.getByName("XRechnung")) {
|
||||||
xml += "<ram:BusinessProcessSpecifiedDocumentContextParameter>\n"
|
xml += "<ram:BusinessProcessSpecifiedDocumentContextParameter>\n"
|
||||||
+ "<ram:ID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</ram:ID>\n"
|
+ "<ram:ID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</ram:ID>\n"
|
||||||
+ "</ram:BusinessProcessSpecifiedDocumentContextParameter>\n";
|
+ "</ram:BusinessProcessSpecifiedDocumentContextParameter>\n";
|
||||||
@@ -419,8 +416,16 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
|||||||
if ((getProfile() != Profiles.getByName("Minimum")) && (getProfile() != Profiles.getByName("BasicWL"))) {
|
if ((getProfile() != Profiles.getByName("Minimum")) && (getProfile() != Profiles.getByName("BasicWL"))) {
|
||||||
xml += "<ram:IncludedSupplyChainTradeLineItem>" +
|
xml += "<ram:IncludedSupplyChainTradeLineItem>" +
|
||||||
"<ram:AssociatedDocumentLineDocument>"
|
"<ram:AssociatedDocumentLineDocument>"
|
||||||
+ "<ram:LineID>" + lineIDStr + "</ram:LineID>"
|
+ "<ram:LineID>" + lineIDStr + "</ram:LineID>";
|
||||||
+ buildItemNotes(currentItem)
|
if (getProfile() == Profiles.getByName("Extended")) {
|
||||||
|
if (currentItem.getParentLineID() != null) {
|
||||||
|
xml += "<ram:ParentLineID>" + XMLTools.encodeXML(currentItem.getParentLineID()) + "</ram:ParentLineID>";
|
||||||
|
}
|
||||||
|
if (currentItem.getLineStatusReasonCode() != null) {
|
||||||
|
xml += "<ram:LineStatusReasonCode>" + XMLTools.encodeXML(currentItem.getLineStatusReasonCode()) + "</ram:LineStatusReasonCode>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xml += buildItemNotes(currentItem)
|
||||||
+ "</ram:AssociatedDocumentLineDocument>"
|
+ "</ram:AssociatedDocumentLineDocument>"
|
||||||
|
|
||||||
+ "<ram:SpecifiedTradeProduct>";
|
+ "<ram:SpecifiedTradeProduct>";
|
||||||
@@ -550,15 +555,16 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
|||||||
|
|
||||||
xml += "</ram:SpecifiedLineTradeDelivery>"
|
xml += "</ram:SpecifiedLineTradeDelivery>"
|
||||||
+ "<ram:SpecifiedLineTradeSettlement>"
|
+ "<ram:SpecifiedLineTradeSettlement>"
|
||||||
+ "<ram:ApplicableTradeTax>"
|
+ "<ram:ApplicableTradeTax>";
|
||||||
+ "<ram:TypeCode>VAT</ram:TypeCode>";
|
// <CalculatedAmount/>
|
||||||
|
xml += "<ram:TypeCode>VAT</ram:TypeCode>";
|
||||||
if (currentItem.getProduct().getTaxExemptionReason() != null) {
|
if (currentItem.getProduct().getTaxExemptionReason() != null) {
|
||||||
xml += "<ram:ExemptionReason>" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + "</ram:ExemptionReason>";
|
xml += "<ram:ExemptionReason>" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + "</ram:ExemptionReason>";
|
||||||
}
|
}
|
||||||
|
xml += "<ram:CategoryCode>" + currentItem.getProduct().getTaxCategoryCode() + "</ram:CategoryCode>";
|
||||||
if (currentItem.getProduct().getTaxExemptionReasonCode() != null) {
|
if (currentItem.getProduct().getTaxExemptionReasonCode() != null) {
|
||||||
xml += "<ram:ExemptionReasonCode>" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReasonCode()) + "</ram:ExemptionReasonCode>";
|
xml += "<ram:ExemptionReasonCode>" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReasonCode()) + "</ram:ExemptionReasonCode>";
|
||||||
}
|
}
|
||||||
xml += "<ram:CategoryCode>" + currentItem.getProduct().getTaxCategoryCode() + "</ram:CategoryCode>";
|
|
||||||
BigDecimal vatValue=BigDecimal.ZERO;
|
BigDecimal vatValue=BigDecimal.ZERO;
|
||||||
if (currentItem.getProduct().getTaxCategoryCode().equals(TaxCategoryCodeTypeConstants.ZEROTAXPRODUCTS)) {
|
if (currentItem.getProduct().getTaxCategoryCode().equals(TaxCategoryCodeTypeConstants.ZEROTAXPRODUCTS)) {
|
||||||
vatValue=BigDecimal.ZERO;
|
vatValue=BigDecimal.ZERO;
|
||||||
@@ -662,6 +668,11 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
|||||||
xml += "<ram:AdditionalReferencedDocument>"
|
xml += "<ram:AdditionalReferencedDocument>"
|
||||||
+ "<ram:IssuerAssignedID>" + XMLTools.encodeXML(trans.getObjectIdentifierReferencedDocument().getIssuerAssignedID()) + "</ram:IssuerAssignedID>"
|
+ "<ram:IssuerAssignedID>" + XMLTools.encodeXML(trans.getObjectIdentifierReferencedDocument().getIssuerAssignedID()) + "</ram:IssuerAssignedID>"
|
||||||
+ "<ram:TypeCode>130</ram:TypeCode>";
|
+ "<ram:TypeCode>130</ram:TypeCode>";
|
||||||
|
// NEW: BT-18-1 scheme identifier
|
||||||
|
String rtc = trans.getObjectIdentifierReferencedDocument().getReferenceTypeCode();
|
||||||
|
if (rtc != null && !rtc.isEmpty()) {
|
||||||
|
xml += "<ram:ReferenceTypeCode>" + XMLTools.encodeXML(rtc) + "</ram:ReferenceTypeCode>";
|
||||||
|
}
|
||||||
if (trans.getObjectIdentifierReferencedDocument().getFormattedIssueDateTime()!=null) {
|
if (trans.getObjectIdentifierReferencedDocument().getFormattedIssueDateTime()!=null) {
|
||||||
xml += "<ram:FormattedIssueDateTime>" + DATE.qdtFormat(trans.getObjectIdentifierReferencedDocument().getFormattedIssueDateTime()) + "</ram:FormattedIssueDateTime>";
|
xml += "<ram:FormattedIssueDateTime>" + DATE.qdtFormat(trans.getObjectIdentifierReferencedDocument().getFormattedIssueDateTime()) + "</ram:FormattedIssueDateTime>";
|
||||||
}
|
}
|
||||||
@@ -850,7 +861,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((trans.getZFAllowances() != null) && (trans.getZFAllowances().length > 0)) {
|
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()) {
|
for (IZUGFeRDAllowanceCharge allowance : trans.getZFAllowances()) {
|
||||||
xml += "<ram:SpecifiedTradeAllowanceCharge>" +
|
xml += "<ram:SpecifiedTradeAllowanceCharge>" +
|
||||||
"<ram:ChargeIndicator>" +
|
"<ram:ChargeIndicator>" +
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ import jakarta.activation.DataSource;
|
|||||||
import jakarta.activation.FileDataSource;
|
import jakarta.activation.FileDataSource;
|
||||||
|
|
||||||
public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporter {
|
public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporter {
|
||||||
|
|
||||||
private boolean isFacturX = true;
|
private boolean isFacturX = true;
|
||||||
|
|
||||||
public static final int DefaultZUGFeRDVersion = 2;
|
public static final int DefaultZUGFeRDVersion = 2;
|
||||||
@@ -101,6 +102,9 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
|||||||
private Profile profile = null;
|
private Profile profile = null;
|
||||||
protected boolean documentPrepared = false;
|
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,
|
* 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
|
* 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);
|
dict.setString("Desc", description);
|
||||||
|
|
||||||
ByteArrayInputStream fakeFile = new ByteArrayInputStream(data);
|
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.addCompression();
|
||||||
ef.setSubtype(subType);
|
ef.setSubtype(subType);
|
||||||
ef.setSize(data.length);
|
ef.setSize(data.length);
|
||||||
@@ -503,6 +508,12 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IZUGFeRDExporter setEnablePDFAttachmentCompression(boolean compressionEnabled) {
|
||||||
|
this.compressionEnabled = compressionEnabled;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public ZUGFeRDExporterFromA3 setCreator(String creator) {
|
public ZUGFeRDExporterFromA3 setCreator(String creator) {
|
||||||
this.creator = creator;
|
this.creator = creator;
|
||||||
|
|||||||
@@ -188,6 +188,10 @@ public class ZUGFeRDExporterFromPDFA implements IZUGFeRDExporter {
|
|||||||
return getExporter().setConformanceLevel(newLevel);
|
return getExporter().setConformanceLevel(newLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IZUGFeRDExporter setEnablePDFAttachmentCompression(boolean compressionEnabled) {
|
||||||
|
return getExporter().setEnablePDFAttachmentCompression(compressionEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
public IZUGFeRDExporter setProducer(String producer) {
|
public IZUGFeRDExporter setProducer(String producer) {
|
||||||
|
|
||||||
return getExporter().setProducer(producer);
|
return getExporter().setProducer(producer);
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import java.text.ParseException;
|
|||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
import javax.xml.xpath.XPath;
|
import javax.xml.xpath.XPath;
|
||||||
import javax.xml.xpath.XPathConstants;
|
import javax.xml.xpath.XPathConstants;
|
||||||
import javax.xml.xpath.XPathExpression;
|
import javax.xml.xpath.XPathExpression;
|
||||||
@@ -29,6 +31,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
import org.w3c.dom.NodeList;
|
import org.w3c.dom.NodeList;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter {
|
public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRDImporter.class);
|
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 {
|
public int getVersion() throws Exception {
|
||||||
if (!containsMeta) {
|
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
|
* returns an instance of PostalTradeAddress for SellerTradeParty section
|
||||||
|
|||||||
@@ -18,9 +18,7 @@ import org.w3c.dom.Node;
|
|||||||
import org.w3c.dom.NodeList;
|
import org.w3c.dom.NodeList;
|
||||||
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
import javax.xml.XMLConstants;
|
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
import javax.xml.xpath.XPath;
|
import javax.xml.xpath.XPath;
|
||||||
import javax.xml.xpath.XPathConstants;
|
import javax.xml.xpath.XPathConstants;
|
||||||
@@ -48,6 +46,7 @@ import java.util.Map;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -311,36 +310,62 @@ public class ZUGFeRDInvoiceImporter {
|
|||||||
setRawXML(rawXML, true);
|
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
|
* @return raw XML of the invoice
|
||||||
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
*/
|
||||||
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
public String getMeta() {
|
||||||
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
if (rawXML == null) {
|
||||||
// Disable external DTDs as well
|
return null;
|
||||||
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);
|
return new String(rawXML, StandardCharsets.UTF_8);
|
||||||
dbf.setExpandEntityReferences(false);
|
}
|
||||||
dbf.setNamespaceAware(true);
|
|
||||||
final DocumentBuilder builder = dbf.newDocumentBuilder();
|
/**
|
||||||
|
* 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);
|
final ByteArrayInputStream is = new ByteArrayInputStream(rawXML);
|
||||||
/// is.skip(guessBOMSize(is));
|
/// is.skip(guessBOMSize(is));
|
||||||
document = builder.parse(is);
|
try {
|
||||||
if (parseAutomatically) {
|
DocumentBuilder builder = XMLTools.getDocumentBuilder(true);
|
||||||
try {
|
document = builder.parse(is);
|
||||||
importedInvoice = new CalculatedInvoice();
|
|
||||||
extractInto(importedInvoice);
|
} catch (Exception e) {
|
||||||
} catch (XPathExpressionException e) {
|
return false;
|
||||||
throw new RuntimeException(e);
|
}
|
||||||
|
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\"]");
|
xpr = xpath.compile("//*[local-name()=\"ExchangedDocument\"]|//*[local-name()=\"HeaderExchangedDocument\"]");
|
||||||
NodeList ExchangedDocumentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
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;
|
BigDecimal expectedGrandTotal = null;
|
||||||
NodeList totalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
NodeList totalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||||
if (totalNodes.getLength() > 0) {
|
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;
|
BigDecimal expectedTaxBasis = null;
|
||||||
NodeList basisNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
NodeList basisNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||||
if (basisNodes.getLength() > 0) {
|
if (basisNodes.getLength() > 0) {
|
||||||
@@ -526,9 +551,8 @@ public class ZUGFeRDInvoiceImporter {
|
|||||||
NodeList lineDueNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
NodeList lineDueNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||||
BigDecimal duePayableAmount = null;
|
BigDecimal duePayableAmount = null;
|
||||||
if (lineDueNodes.getLength() > 0) {
|
if (lineDueNodes.getLength() > 0) {
|
||||||
String duePayableAmountStr=XMLTools.trimOrNull(lineDueNodes.item(0));
|
duePayableAmount = new BigDecimal(XMLTools.trimOrNull(lineDueNodes.item(0)));
|
||||||
if ((zpp instanceof CalculatedInvoice) && (duePayableAmountStr!=null)) {
|
if (zpp instanceof CalculatedInvoice) {
|
||||||
duePayableAmount = new BigDecimal(duePayableAmountStr);
|
|
||||||
((CalculatedInvoice) zpp).setDuePayable(duePayableAmount);
|
((CalculatedInvoice) zpp).setDuePayable(duePayableAmount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -625,14 +649,8 @@ public class ZUGFeRDInvoiceImporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String tenderReference = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"OriginatorDocumentReference\"]/*[local-name()=\"ID\"]").trim();
|
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((tenderReference != null)&&(!tenderReference.isEmpty())){
|
||||||
if((tenderReferenceDate != null)&&(!tenderReferenceDate.isEmpty())){
|
zpp.setTenderReferencedDocument(tenderReference);
|
||||||
zpp.setTenderReferencedDocument(new ReferencedDocument(tenderReference, parseDate(tenderReferenceDate, "yyyy-MM-dd")));
|
|
||||||
} else {
|
|
||||||
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();
|
String dueDt = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"DueDate\"] | /*[local-name()=\"CreditNote\"]/*[local-name()=\"PaymentMeans\"]/*[local-name()=\"PaymentDueDate\"]").trim();
|
||||||
@@ -1272,6 +1290,25 @@ public class ZUGFeRDInvoiceImporter {
|
|||||||
//appliedAmount
|
//appliedAmount
|
||||||
//AppliedTradeTax
|
//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)) {
|
if ((cd.getPercent() != null)&&(cd.getDays() != null)) {
|
||||||
zpp.addCashDiscount(cd);
|
zpp.addCashDiscount(cd);
|
||||||
}
|
}
|
||||||
@@ -1328,6 +1365,9 @@ public class ZUGFeRDInvoiceImporter {
|
|||||||
+ Stream.of(tc.trans.getZFItems())
|
+ Stream.of(tc.trans.getZFItems())
|
||||||
.map(item -> item.getCalculation().getItemTotalNetAmount().toPlainString())
|
.map(item -> item.getCalculation().getItemTotalNetAmount().toPlainString())
|
||||||
.collect(Collectors.joining(" + "));
|
.collect(Collectors.joining(" + "));
|
||||||
|
if (tc.trans.getRoundingAmount()!=null) {
|
||||||
|
moreDetails += " and rounding amount "+tc.trans.getRoundingAmount().toPlainString();
|
||||||
|
}
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
throw new ArithmeticException("Payable total in XML is " + payableTotalFromXml + ", but calculated total is " + calculatedPayableTotal + moreDetails);
|
throw new ArithmeticException("Payable total in XML is " + payableTotalFromXml + ", but calculated total is " + calculatedPayableTotal + moreDetails);
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import org.apache.fop.configuration.DefaultConfigurationBuilder;
|
|||||||
import org.apache.xmlgraphics.util.MimeConstants;
|
import org.apache.xmlgraphics.util.MimeConstants;
|
||||||
import org.mustangproject.ClasspathResolverURIAdapter;
|
import org.mustangproject.ClasspathResolverURIAdapter;
|
||||||
import org.mustangproject.EStandard;
|
import org.mustangproject.EStandard;
|
||||||
|
import org.mustangproject.XMLTools;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
@@ -39,7 +40,6 @@ import org.w3c.dom.Element;
|
|||||||
import org.xml.sax.InputSource;
|
import org.xml.sax.InputSource;
|
||||||
|
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
|
||||||
import javax.xml.transform.*;
|
import javax.xml.transform.*;
|
||||||
import javax.xml.transform.sax.SAXResult;
|
import javax.xml.transform.sax.SAXResult;
|
||||||
import javax.xml.transform.stream.StreamResult;
|
import javax.xml.transform.stream.StreamResult;
|
||||||
@@ -47,8 +47,6 @@ import javax.xml.transform.stream.StreamSource;
|
|||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@@ -85,8 +83,7 @@ public class ZUGFeRDVisualizer {
|
|||||||
private Templates mXsltZF1HTMLTemplate = null;
|
private Templates mXsltZF1HTMLTemplate = null;
|
||||||
|
|
||||||
public ZUGFeRDVisualizer() {
|
public ZUGFeRDVisualizer() {
|
||||||
mFactory = new net.sf.saxon.TransformerFactoryImpl();
|
mFactory = XMLTools.getTransformerFactory();
|
||||||
// fact = TransformerFactory.newInstance();
|
|
||||||
mFactory.setURIResolver(new ClasspathResourceURIResolver());
|
mFactory.setURIResolver(new ClasspathResourceURIResolver());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,40 +101,8 @@ public class ZUGFeRDVisualizer {
|
|||||||
String ublCreditNoteSignature = "CreditNote";
|
String ublCreditNoteSignature = "CreditNote";
|
||||||
String cioSignature = "SCRDMCCBDACIOMessageStructure";
|
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 {
|
try {
|
||||||
DocumentBuilder db = dbf.newDocumentBuilder();
|
DocumentBuilder db = XMLTools.getDocumentBuilder(true);
|
||||||
Document doc = db.parse(new InputSource(fis));
|
Document doc = db.parse(new InputSource(fis));
|
||||||
Element root = doc.getDocumentElement();
|
Element root = doc.getDocumentElement();
|
||||||
if (root.getLocalName().equals(zf1Signature)) {
|
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 {
|
throws IOException, TransformerException, ParserConfigurationException {
|
||||||
EStandard theStandard;
|
EStandard theStandard;
|
||||||
try (FileInputStream fis = new FileInputStream(xmlFilename)) {
|
try (FileInputStream fis = new FileInputStream(xmlFilename)) {
|
||||||
@@ -260,11 +225,11 @@ public class ZUGFeRDVisualizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try (FileInputStream fis = new FileInputStream(xmlFilename)) {
|
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 {
|
throws TransformerException, IOException {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -291,12 +256,16 @@ public class ZUGFeRDVisualizer {
|
|||||||
Optional<InputStream> in = copyStream(iaos);
|
Optional<InputStream> in = copyStream(iaos);
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
if (in.isPresent()) {
|
if (in.isPresent()) {
|
||||||
applyXSLTToPDF(in.get(), baos);
|
applyXSLTToPDF(in.get(), baos, lang);
|
||||||
}
|
}
|
||||||
return baos.toString(StandardCharsets.UTF_8);
|
return baos.toString(StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void toPDF(String xmlFilename, String pdfFilename) {
|
public void toPDF(String xmlFilename, String pdfFilename) {
|
||||||
|
toPDF(xmlFilename, pdfFilename, Language.DE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toPDF(String xmlFilename, String pdfFilename, Language lang) {
|
||||||
|
|
||||||
// the writing part
|
// the writing part
|
||||||
File XMLinputFile = new File(xmlFilename);
|
File XMLinputFile = new File(xmlFilename);
|
||||||
@@ -307,7 +276,7 @@ public class ZUGFeRDVisualizer {
|
|||||||
out from git with arbitrary options (which may include CSRF changes)
|
out from git with arbitrary options (which may include CSRF changes)
|
||||||
*/
|
*/
|
||||||
try {
|
try {
|
||||||
fopInput = this.toFOP(XMLinputFile.getAbsolutePath());
|
fopInput = this.toFOP(XMLinputFile.getAbsolutePath(), lang);
|
||||||
} catch (TransformerException | IOException | ParserConfigurationException e) {
|
} catch (TransformerException | IOException | ParserConfigurationException e) {
|
||||||
LOGGER.error("Failed to apply FOP", e);
|
LOGGER.error("Failed to apply FOP", e);
|
||||||
}
|
}
|
||||||
@@ -323,6 +292,10 @@ public class ZUGFeRDVisualizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public byte[] toPDF(String xmlContent) {
|
public byte[] toPDF(String xmlContent) {
|
||||||
|
return toPDF(xmlContent, Language.DE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] toPDF(String xmlContent, Language lang) {
|
||||||
|
|
||||||
String fopInput = null;
|
String fopInput = null;
|
||||||
|
|
||||||
@@ -334,7 +307,7 @@ public class ZUGFeRDVisualizer {
|
|||||||
EStandard theStandard = findOutStandardFromRootNode(fis);
|
EStandard theStandard = findOutStandardFromRootNode(fis);
|
||||||
fis = new ByteArrayInputStream(xmlContent.getBytes(StandardCharsets.UTF_8));//rewind :-(
|
fis = new ByteArrayInputStream(xmlContent.getBytes(StandardCharsets.UTF_8));//rewind :-(
|
||||||
|
|
||||||
fopInput = toFOP(fis, theStandard);
|
fopInput = toFOP(fis, theStandard, lang);
|
||||||
} catch (TransformerException | IOException | ParserConfigurationException e) {
|
} catch (TransformerException | IOException | ParserConfigurationException e) {
|
||||||
LOGGER.error("Failed to apply FOP", e);
|
LOGGER.error("Failed to apply FOP", e);
|
||||||
}
|
}
|
||||||
@@ -470,9 +443,10 @@ public class ZUGFeRDVisualizer {
|
|||||||
xmlFile.close();
|
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 {
|
throws TransformerException, IOException {
|
||||||
Transformer transformer = mXsltPDFTemplate.newTransformer();
|
Transformer transformer = mXsltPDFTemplate.newTransformer();
|
||||||
|
transformer.setParameter("lang", lang.name().toLowerCase());
|
||||||
|
|
||||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(PDFOutstream));
|
transformer.transform(new StreamSource(xmlFile), new StreamResult(PDFOutstream));
|
||||||
xmlFile.close();
|
xmlFile.close();
|
||||||
|
|||||||
254
library/src/main/resources/stylesheets/l10n/fr.xml
Normal file
254
library/src/main/resources/stylesheets/l10n/fr.xml
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<properties>
|
||||||
|
<entry key="xr:Buyer_reference" id="BT-10">Référence acheteur</entry>
|
||||||
|
<entry key="xr:Buyer_name" id="BT-44">Nom</entry>
|
||||||
|
<entry key="xr:Buyer_address_line_1" id="BT-50">Adresse ligne 1</entry>
|
||||||
|
<entry key="xr:Buyer_address_line_2" id="BT-51">Adresse ligne 2</entry>
|
||||||
|
<entry key="xr:Buyer_address_line_3" id="BT-163">Adresse ligne 3</entry>
|
||||||
|
<entry key="xr:Buyer_post_code" id="BT-53">Code postal</entry>
|
||||||
|
<entry key="xr:Buyer_city" id="BT-52">Ville</entry>
|
||||||
|
<entry key="xr:Buyer_country_code" id="BT-55">Code pays</entry>
|
||||||
|
<entry key="xr:Buyer_identifier" id="BT-46">Identifiant</entry>
|
||||||
|
<entry key="xr:Buyer_identifier/@scheme_identifier" id="BT-46_scheme">Schéma de l'identifiant</entry>
|
||||||
|
<entry key="xr:Buyer_contact_point" id="BT-56">Nom</entry>
|
||||||
|
<entry key="xr:Buyer_contact_telephone_number" id="BT-57">Téléphone</entry>
|
||||||
|
<entry key="xr:Buyer_contact_email_address" id="BT-58">Adresse e-mail</entry>
|
||||||
|
<entry key="xr:Seller_name" id="BT-27">Raison sociale</entry>
|
||||||
|
<entry key="xr:Seller_address_line_1" id="BT-35">Adresse ligne 1</entry>
|
||||||
|
<entry key="xr:Seller_address_line_2" id="BT-36">Adresse ligne 2</entry>
|
||||||
|
<entry key="xr:Seller_address_line_3" id="BT-162">Adresse ligne 3</entry>
|
||||||
|
<entry key="xr:Seller_post_code" id="BT-38">Code postal</entry>
|
||||||
|
<entry key="xr:Seller_city" id="BT-37">Ville</entry>
|
||||||
|
<entry key="xr:Seller_country_subdivision" id="BT-39">Région</entry>
|
||||||
|
<entry key="xr:Seller_country_code" id="BT-40">Code pays</entry>
|
||||||
|
<entry key="xr:Seller_identifier" id="BT-29">Identifiant</entry>
|
||||||
|
<entry key="xr:Seller_identifier/@scheme_identifier" id="BT-29_scheme">Schéma de l'identifiant</entry>
|
||||||
|
<entry key="xr:Seller_contact_point" id="BT-41">Nom</entry>
|
||||||
|
<entry key="xr:Seller_contact_telephone_number" id="BT-42">Téléphone</entry>
|
||||||
|
<entry key="xr:Seller_contact_email_address" id="BT-43">Adresse e-mail</entry>
|
||||||
|
<entry key="xr:Invoice_number" id="BT-1">Numéro de facture</entry>
|
||||||
|
<entry key="xr:Invoice_issue_date" id="BT-2">Date de facture</entry>
|
||||||
|
<entry key="xr:Invoice_type_code" id="BT-3">Type de facture</entry>
|
||||||
|
<entry key="xr:Invoice_currency_code" id="BT-5">Devise</entry>
|
||||||
|
<entry key="xr:Project_reference" id="BT-11">Numéro de projet</entry>
|
||||||
|
<entry key="xr:Contract_reference" id="BT-12">Numéro de contrat</entry>
|
||||||
|
<entry key="xr:Purchase_order_reference" id="BT-13">Numéro de commande</entry>
|
||||||
|
<entry key="xr:Sales_order_reference" id="BT-14">Référence de commande client</entry>
|
||||||
|
<!-- Be aware that these are for the whole invoice -->
|
||||||
|
<entry key="xr:Invoicing_period_start_date" id="BT-73">Du</entry>
|
||||||
|
<entry key="xr:Invoicing_period_end_date" id="BT-74">Au</entry>
|
||||||
|
<entry key="xr:Preceding_Invoice_reference" id="BT-25">Numéro de facture</entry>
|
||||||
|
<entry key="xr:Preceding_Invoice_issue_date" id="BT-26">Date de facture</entry>
|
||||||
|
<entry key="xr:Sum_of_Invoice_line_net_amount" id="BT-106">Total des lignes</entry>
|
||||||
|
<entry key="xr:Sum_of_allowances_on_document_level" id="BT-107">Total des remises</entry>
|
||||||
|
<entry key="xr:Sum_of_charges_on_document_level" id="BT-108">Total des frais</entry>
|
||||||
|
<entry key="xr:Invoice_total_amount_without_VAT" id="BT-109">Total HT</entry>
|
||||||
|
<entry key="xr:Invoice_total_VAT_amount" id="BT-110">Total TVA</entry>
|
||||||
|
<entry key="xr:Invoice_total_VAT_amount_in_accounting_currency" id="BT-111">Total TVA en devise comptable</entry>
|
||||||
|
<entry key="xr:Invoice_total_amount_with_VAT" id="BT-112">Total TTC</entry>
|
||||||
|
<entry key="xr:Paid_amount" id="BT-113">Montant payé</entry>
|
||||||
|
<entry key="xr:Rounding_amount" id="BT-114">Montant d'arrondi</entry>
|
||||||
|
<entry key="xr:Amount_due_for_payment" id="BT-115">Montant dû</entry>
|
||||||
|
<entry key="xr:VAT_category_code" id="BT-118">Catégorie de TVA</entry>
|
||||||
|
<entry key="xr:VAT_category_taxable_amount" id="BT-116">Total</entry>
|
||||||
|
<entry key="xr:VAT_category_rate" id="BT-119">Taux de TVA</entry>
|
||||||
|
<entry key="xr:VAT_category_tax_amount" id="BT-117">Montant de TVA</entry>
|
||||||
|
<entry key="xr:VAT_exemption_reason_text" id="BT-120">Motif d'exonération</entry>
|
||||||
|
<entry key="xr:VAT_exemption_reason_code" id="BT-121">Code du motif d'exonération</entry>
|
||||||
|
<!-- Remises -->
|
||||||
|
<entry key="xr:Document_level_allowance_VAT_category_code" id="BT-95">Catégorie de TVA de la remise</entry>
|
||||||
|
<entry key="xr:Document_level_allowance_base_amount" id="BT-93">Montant de base</entry>
|
||||||
|
<entry key="xr:Document_level_allowance_percentage" id="BT-94">Pourcentage</entry>
|
||||||
|
<entry key="xr:Document_level_allowance_amount" id="BT-92">Remise</entry>
|
||||||
|
<entry key="xr:Document_level_allowance_VAT_rate" id="BT-96">Taux de TVA de la remise</entry>
|
||||||
|
<entry key="xr:Document_level_allowance_reason" id="BT-97">Motif de la remise</entry>
|
||||||
|
<entry key="xr:Document_level_allowance_reason_code" id="BT-98">Code du motif de la remise</entry>
|
||||||
|
<!-- Frais -->
|
||||||
|
<entry key="xr:Document_level_charge_VAT_category_code" id="BT-102">Catégorie de TVA des frais</entry>
|
||||||
|
<entry key="xr:Document_level_charge_base_amount" id="BT-100">Montant de base</entry>
|
||||||
|
<entry key="xr:Document_level_charge_percentage" id="BT-101">Pourcentage</entry>
|
||||||
|
<entry key="xr:Document_level_charge_amount" id="BT-99">Frais</entry>
|
||||||
|
<entry key="xr:Document_level_charge_VAT_rate" id="BT-103">Taux de TVA des frais</entry>
|
||||||
|
<entry key="xr:Document_level_charge_reason" id="BT-104">Motif des frais</entry>
|
||||||
|
<entry key="xr:Document_level_charge_reason_code" id="BT-105">Code du motif des frais</entry>
|
||||||
|
<entry key="xr:Payment_terms" id="BT-20">Escompte ; autres conditions de paiement</entry>
|
||||||
|
<entry key="xr:Payment_due_date" id="BT-9">Date d'échéance</entry>
|
||||||
|
<entry key="xr:Payment_means_type_code" id="BT-81">Code du moyen de paiement</entry>
|
||||||
|
<entry key="xr:Payment_means_text" id="BT-82">Moyen de paiement</entry>
|
||||||
|
<entry key="xr:Remittance_information" id="BT-83">Référence de paiement</entry>
|
||||||
|
<entry key="xr:Payment_card_primary_account_number" id="BT-87">Numéro de carte</entry>
|
||||||
|
<entry key="xr:Payment_card_holder_name" id="BT-88">Titulaire de la carte</entry>
|
||||||
|
<!-- BG-19 Direct Debit -->
|
||||||
|
<entry key="xr:Mandate_reference_identifier" id="BT-89">Référence du mandat</entry>
|
||||||
|
<entry key="xr:Debited_account_identifier" id="BT-91">IBAN</entry>
|
||||||
|
<entry key="xr:Bank_assigned_creditor_identifier" id="BT-90">Identifiant créancier</entry>
|
||||||
|
<!-- BG-17 Credit Transfer-->
|
||||||
|
<entry key="xr:Payment_account_name" id="BT-85">Titulaire du compte</entry>
|
||||||
|
<entry key="xr:Payment_account_identifier" id="BT-84">IBAN</entry>
|
||||||
|
<entry key="xr:Payment_service_provider_identifier" id="BT-86">BIC</entry>
|
||||||
|
<entry key="xr:Invoice_note_subject_code" id="BT-21">Objet</entry>
|
||||||
|
<entry key="xr:Invoice_note" id="BT-22">Remarque</entry>
|
||||||
|
<!-- BG-4 Seller and others -->
|
||||||
|
<entry key="xr:Seller_trading_name" id="BT-28">Nom commercial</entry>
|
||||||
|
<entry key="xr:Seller_electronic_address" id="BT-34">Adresse électronique</entry>
|
||||||
|
<entry key="xr:Seller_electronic_address/@scheme_identifier" id="BT-34_scheme">Schéma de l'adresse électronique</entry>
|
||||||
|
<entry key="xr:Seller_legal_registration_identifier" id="BT-30">Numéro SIRET/SIREN</entry>
|
||||||
|
<entry key="xr:Seller_legal_registration_identifier/@scheme_identifier" id="BT-30_scheme">Schéma du numéro d'enregistrement</entry>
|
||||||
|
<entry key="xr:Seller_VAT_identifier" id="BT-31">Numéro de TVA intracommunautaire</entry>
|
||||||
|
<entry key="xr:Seller_tax_registration_identifier" id="BT-32">Numéro fiscal</entry>
|
||||||
|
<entry key="xr:Seller_additional_legal_information" id="BT-33">Informations juridiques complémentaires</entry>
|
||||||
|
<entry key="xr:VAT_accounting_currency_code" id="BT-6">Code devise de la TVA</entry>
|
||||||
|
<!-- BG 12 and 11 Tax Representative party -->
|
||||||
|
<entry key="xr:Seller_tax_representative_name" id="BT-62">Nom</entry>
|
||||||
|
<entry key="xr:Tax_representative_address_line_1" id="BT-64">Adresse ligne 1</entry>
|
||||||
|
<entry key="xr:Tax_representative_address_line_2" id="BT-65">Adresse ligne 2</entry>
|
||||||
|
<entry key="xr:Tax_representative_address_line_3" id="BT-164">Adresse ligne 3</entry>
|
||||||
|
<entry key="xr:Tax_representative_post_code" id="BT-67">Code postal</entry>
|
||||||
|
<entry key="xr:Tax_representative_city" id="BT-66">Ville</entry>
|
||||||
|
<entry key="xr:Tax_representative_country_subdivision" id="BT-68">Région</entry>
|
||||||
|
<entry key="xr:Tax_representative_country_code" id="BT-69">Code pays</entry>
|
||||||
|
<entry key="xr:Seller_tax_representative_VAT_identifier" id="BT-63">Numéro de TVA intracommunautaire</entry>
|
||||||
|
<!-- Additional buyer information from different bgs -->
|
||||||
|
<entry key="xr:Buyer_trading_name" id="BT-45">Nom commercial</entry>
|
||||||
|
<entry key="xr:Buyer_country_subdivision" id="BT-54">Région</entry>
|
||||||
|
<entry key="xr:Buyer_electronic_address" id="BT-49">Adresse électronique</entry>
|
||||||
|
<entry key="xr:Buyer_electronic_address/@scheme_identifier" id="BT-49_scheme">Schéma de l'adresse électronique</entry>
|
||||||
|
<entry key="xr:Buyer_legal_registration_identifier" id="BT-47">Numéro SIRET/SIREN</entry>
|
||||||
|
<entry key="xr:Buyer_legal_registration_identifier/@scheme_identifier" id="BT-47_scheme">Schéma du numéro d'enregistrement</entry>
|
||||||
|
<entry key="xr:Buyer_VAT_identifier" id="BT-48">Numéro de TVA intracommunautaire</entry>
|
||||||
|
<entry key="xr:Value_added_tax_point_date" id="BT-7">Date d'exigibilité de la TVA</entry>
|
||||||
|
<entry key="xr:Value_added_tax_point_date_code" id="BT-8">Code de la date d'exigibilité de la TVA</entry>
|
||||||
|
<entry key="xr:Buyer_accounting_reference" id="BT-19">Référence comptable</entry>
|
||||||
|
<entry key="xr:Deliver_to_location_identifier" id="BT-71">Identifiant du lieu de livraison</entry>
|
||||||
|
<entry key="xr:Deliver_to_location_identifier/@scheme_identifier" id="BT-71_scheme">Schéma de l'identifiant</entry>
|
||||||
|
<entry key="xr:Actual_delivery_date" id="BT-72">Date de livraison</entry>
|
||||||
|
<entry key="xr:Deliver_to_party_name" id="BT-70">Nom du destinataire</entry>
|
||||||
|
<entry key="xr:Deliver_to_address_line_1" id="BT-75">Adresse ligne 1</entry>
|
||||||
|
<entry key="xr:Deliver_to_address_line_2" id="BT-76">Adresse ligne 2</entry>
|
||||||
|
<entry key="xr:Deliver_to_address_line_3" id="BT-165">Adresse ligne 3</entry>
|
||||||
|
<entry key="xr:Deliver_to_post_code" id="BT-78">Code postal</entry>
|
||||||
|
<entry key="xr:Deliver_to_city" id="BT-77">Ville</entry>
|
||||||
|
<entry key="xr:Deliver_to_country_subdivision" id="BT-79">Région</entry>
|
||||||
|
<entry key="xr:Deliver_to_country_code" id="BT-80">Code pays</entry>
|
||||||
|
<entry key="xr:Tender_or_lot_reference" id="BT-17">Numéro d'appel d'offres</entry>
|
||||||
|
<entry key="xr:Receiving_advice_reference" id="BT-15">Référence de l'avis de réception</entry>
|
||||||
|
<entry key="xr:Despatch_advice_reference" id="BT-16">Référence de l'avis d'expédition</entry>
|
||||||
|
<entry key="xr:Business_process_type" id="BT-23">Identifiant du processus</entry>
|
||||||
|
<entry key="xr:Specification_identifier" id="BT-24">Identifiant de spécification</entry>
|
||||||
|
<entry key="xr:Invoiced_object_identifier" id="BT-18">Identifiant de l'objet</entry>
|
||||||
|
<entry key="xr:Invoiced_object_identifier/@scheme_identifier" id="BT-18_scheme">Schéma de l'identifiant de l'objet</entry>
|
||||||
|
<!-- BG 10 -->
|
||||||
|
<entry key="xr:Payee_name" id="BT-59">Nom</entry>
|
||||||
|
<entry key="xr:Payee_identifier" id="BT-60">Identifiant</entry>
|
||||||
|
<entry key="xr:Payee_identifier/@scheme_identifier" id="BT-60_scheme">Schéma de l'identifiant</entry>
|
||||||
|
<entry key="xr:Payee_legal_registration_identifier" id="BT-61">Numéro SIRET/SIREN</entry>
|
||||||
|
<entry key="xr:Payee_legal_registration_identifier/@scheme_identifier" id="BT-61_scheme">Schéma du numéro d'enregistrement</entry>
|
||||||
|
<!-- BG 24 Additonal supporting Documents -->
|
||||||
|
<entry key="xr:Supporting_document_reference" id="BT-122">Identifiant</entry>
|
||||||
|
<entry key="xr:Supporting_document_description" id="BT-123">Description</entry>
|
||||||
|
<entry key="xr:External_document_location" id="BT-124">Référence (ex. adresse Internet)</entry>
|
||||||
|
<entry key="xr:Attached_document" id="BT-125">Document joint</entry>
|
||||||
|
<entry key="xr:Attached_document/@mime_code" id="BT-125_mime_code">Format du document joint</entry>
|
||||||
|
<entry key="xr:Attached_document/@filename" id="BT-125_filename">Nom du document joint</entry>
|
||||||
|
<entry key="xrv:zeitstempel">Date/Heure</entry>
|
||||||
|
<entry key="xrv:betreff">Objet</entry>
|
||||||
|
<entry key="xrv:text">Texte</entry>
|
||||||
|
<entry key="xrv:details">Détails</entry>
|
||||||
|
<entry key="xr:Invoice_line_identifier" id="BT-126">Position</entry>
|
||||||
|
<entry key="xr:Invoice_line_note" id="BT-127">Texte libre</entry>
|
||||||
|
<entry key="xr:Invoice_line_object_identifier" id="BT-128_identifier">Identifiant de l'objet</entry>
|
||||||
|
<entry key="xr:Invoice_line_object_identifier/@scheme_identifier" id="BT-128_scheme">Schéma de l'identifiant de l'objet</entry>
|
||||||
|
<entry key="xr:Referenced_purchase_order_line_reference" id="BT-132">Numéro de ligne de commande</entry>
|
||||||
|
<entry key="xr:Invoice_line_Buyer_accounting_reference" id="BT-133">Référence comptable</entry>
|
||||||
|
<entry key="xr:Invoice_line_period_start_date" id="BT-134">Du</entry>
|
||||||
|
<entry key="xr:Invoice_line_period_end_date" id="BT-135">Au</entry>
|
||||||
|
<entry key="xr:Invoiced_quantity" id="BT-129">Quantité</entry>
|
||||||
|
<entry key="xr:Invoiced_quantity_unit_of_measure_code" id="BT-130">Unité</entry>
|
||||||
|
<entry key="xr:Item_net_price" id="BT-146">Prix unitaire (HT)</entry>
|
||||||
|
<entry key="xr:Invoice_line_net_amount" id="BT-131">Prix total (HT)</entry>
|
||||||
|
<entry key="xr:Item_price_discount" id="BT-147">Remise (HT)</entry>
|
||||||
|
<entry key="xr:Item_gross_price" id="BT-148">Prix catalogue (HT)</entry>
|
||||||
|
<entry key="xr:Item_price_base_quantity" id="BT-149">Quantité de base</entry>
|
||||||
|
<entry key="xr:Item_price_base_quantity_unit_of_measure" id="BT-150">Code de l'unité de mesure</entry>
|
||||||
|
<entry key="xr:Invoiced_item_VAT_category_code" id="BT-151">TVA</entry>
|
||||||
|
<entry key="xr:Invoiced_item_VAT_rate" id="BT-152">Taux de TVA</entry>
|
||||||
|
<entry key="xr:Item_name" id="BT-153">Désignation</entry>
|
||||||
|
<entry key="xr:Item_description" id="BT-154">Description</entry>
|
||||||
|
<entry key="xr:Item_Sellers_identifier" id="BT-155">Référence article</entry>
|
||||||
|
<entry key="xr:Item_Buyers_identifier" id="BT-156">Référence article acheteur</entry>
|
||||||
|
<entry key="xr:Item_standard_identifier" id="BT-157">Identifiant article</entry>
|
||||||
|
<entry key="xr:Item_standard_identifier/@scheme_identifier" class="BT-157_scheme">Schéma de l'identifiant article</entry>
|
||||||
|
<entry key="xr:Item_classification_identifier" id="BT-158">Code de classification article</entry>
|
||||||
|
<entry key="xr:Item_classification_identifier/@scheme_identifier" id="BT-158_scheme">Identifiant du schéma</entry>
|
||||||
|
<entry key="xr:Item_classification_identifier/@scheme_version_identifier" id="BT-158_scheme_version">Version du schéma</entry>
|
||||||
|
<entry key="xr:Item_country_of_origin" id="BT-159">Code du pays d'origine</entry>
|
||||||
|
<entry key="xr:Invoice_line_allowance_base_amount" id="BT-137">Montant de base (HT)</entry>
|
||||||
|
<entry key="xr:Invoice_line_allowance_percentage" id="BT-138">Pourcentage</entry>
|
||||||
|
<entry key="xr:Invoice_line_allowance_amount" id="BT-136">Remise (HT)</entry>
|
||||||
|
<entry key="xr:Invoice_line_allowance_reason" id="BT-139">Motif de la remise</entry>
|
||||||
|
<entry key="xr:Invoice_line_allowance_reason_code" id="BT-140">Code du motif de la remise</entry>
|
||||||
|
<entry key="xr:Invoice_line_charge_base_amount" id="BT-142">Montant de base (HT)</entry>
|
||||||
|
<entry key="xr:Invoice_line_charge_percentage" id="BT-143">Pourcentage</entry>
|
||||||
|
<entry key="xr:Invoice_line_charge_amount" id="BT-141">Frais (HT)</entry>
|
||||||
|
<entry key="xr:Invoice_line_charge_reason" id="BT-144">Motif des frais</entry>
|
||||||
|
<entry key="xr:Invoice_line_charge_reason_code" id="BT-145">Code du motif des frais</entry>
|
||||||
|
<entry key="xr:Third_party_payment_type" id="BT-DEX-001">Type de créance tierce</entry>
|
||||||
|
<entry key="xr:Third_party_payment_amount" id="BT-DEX-002">Montant de la créance tierce</entry>
|
||||||
|
<entry key="xr:Third_party_payment_description" id="BT-DEX-003">Description de la créance tierce</entry>
|
||||||
|
<entry key="uebersicht">Données de la facture électronique</entry>
|
||||||
|
<entry key="uebersichtKaeufer" id="BG-7">Informations sur l'acheteur</entry>
|
||||||
|
<entry key="uebersichtVerkaeufer" id="BG-4">Informations sur le vendeur</entry>
|
||||||
|
<entry key="uebersichtRechnungsInfo" id="invoice-data">Données de facturation</entry>
|
||||||
|
<entry key="uebersichtRechnungsuebersicht" id="BG-22">Totaux de la facture</entry>
|
||||||
|
<entry key="uebersichtFremdleistungen" id="BG-DEX-09">Créance tierce</entry>
|
||||||
|
<entry key="uebersichtUmsatzsteuer" id="BG-23">Ventilation de la TVA au niveau de la facture</entry>
|
||||||
|
<entry key="uebersichtNachlass" id="BG-20">Remise au niveau de la facture</entry>
|
||||||
|
<entry key="uebersichtZuschlaege" id="BG-21">Frais au niveau de la facture</entry>
|
||||||
|
<entry key="uebersichtRechnungAbrechnungszeitraum">Période de facturation</entry>
|
||||||
|
<entry key="uebersichtRechnungVorausgegangeneRechnungen" id="BG-3">Factures précédentes</entry>
|
||||||
|
<entry key="uebersichtZahlungInfo" id="BG-4">Informations de paiement</entry>
|
||||||
|
<entry key="uebersichtZahlungKarte" id="BG-18">Informations carte</entry>
|
||||||
|
<entry key="uebersichtZahlungLastschrift" id="BG-19">Prélèvement</entry>
|
||||||
|
<entry key="uebersichtZahlungUeberweisung" id="BG-17">Virement</entry>
|
||||||
|
<entry key="uebersichtBemerkungen" id="BG-1">Remarques sur la facture</entry>
|
||||||
|
<entry key="details">Lignes de facture</entry>
|
||||||
|
<entry key="detailsPositionAbrechnungszeitraum" id="BG-26">Période de facturation</entry>
|
||||||
|
<entry key="detailsPositionPreiseinzelheiten" id="BG-29">Détails du prix</entry>
|
||||||
|
<entry key="detailsPositionNachlaesse" id="BG-27">Remises au niveau de la ligne de facture</entry>
|
||||||
|
<entry key="detailsPositionZuschlaege" id="BG-28">Frais au niveau de la ligne de facture</entry>
|
||||||
|
<entry key="detailsPositionArtikelinformationen" id="BG-31">Informations sur l'article</entry>
|
||||||
|
<entry key="detailsPositionArtikeleigenschaften" id="BG-32">Caractéristiques de l'article</entry>
|
||||||
|
<entry key="zusaetze">Compléments</entry>
|
||||||
|
<entry key="zusaetzeVerkaeufer" id="BG-4">Informations sur le vendeur</entry>
|
||||||
|
<entry key="zusaetzeSteuervertreter" id="BG-11">Représentant fiscal du vendeur</entry>
|
||||||
|
<entry key="zusaetzeKaeufer" id="BG-7">Informations sur l'acheteur</entry>
|
||||||
|
<entry key="zusaetzeLieferung" id="BG-13">Informations de livraison</entry>
|
||||||
|
<entry key="zusaetzeVertrag">Informations contractuelles</entry>
|
||||||
|
<entry key="zusaetzeZahlungsempfaenger" id="BG-10">Bénéficiaire différent du vendeur</entry>
|
||||||
|
<entry key="laufzettel">Fiche de suivi</entry>
|
||||||
|
<entry key="laufzettelHistorie">Historique de traitement</entry>
|
||||||
|
<entry key="anlagen">Pièces jointes</entry>
|
||||||
|
<entry key="anlagenListe">Documents justificatifs</entry>
|
||||||
|
<entry key="date-format">[D]/[M]/[Y]</entry>
|
||||||
|
<entry key="datetime-format">[D]/[M]/[Y] [H]:[m]:[s]</entry>
|
||||||
|
<entry key="amount-format">###.##0,00</entry>
|
||||||
|
<entry key="percentage-format">##0,00</entry>
|
||||||
|
<entry key="at-least-two-format">###.##0,#################</entry>
|
||||||
|
|
||||||
|
<entry key="sum-of-third-party-payment-amounts" id="BT-DEX-002_sum">Total des créances tierces</entry>
|
||||||
|
|
||||||
|
<entry key="_disclaimer">Nous déclinons toute responsabilité quant à l'exactitude des données.</entry>
|
||||||
|
<entry key="_invoice-note-group">Remarques sur la facture</entry>
|
||||||
|
<entry key="_open">Ouvrir</entry>
|
||||||
|
<entry key="no-data">Aucune donnée disponible</entry>
|
||||||
|
<entry key="_net">HT</entry>
|
||||||
|
<entry key="_gross">TTC</entry>
|
||||||
|
<entry key="_no-content">Les sections sans contenu ne sont pas affichées !</entry>
|
||||||
|
<entry key="_description">Description</entry>
|
||||||
|
<entry key="_price">Prix</entry>
|
||||||
|
<entry key="_price-unit">Unité de prix</entry>
|
||||||
|
<entry key="_vat">Taux TVA</entry>
|
||||||
|
<entry key="_tax-code">Code taxe</entry>
|
||||||
|
<entry key="_total">Total</entry>
|
||||||
|
<entry key="_page">Page</entry>
|
||||||
|
</properties>
|
||||||
@@ -85,6 +85,37 @@
|
|||||||
font-weight="bold">
|
font-weight="bold">
|
||||||
<xsl:text>Prüfbericht</xsl:text>
|
<xsl:text>Prüfbericht</xsl:text>
|
||||||
</fo:block>
|
</fo:block>
|
||||||
|
<xsl:call-template name="SubHeader">
|
||||||
|
<xsl:with-param name="text"
|
||||||
|
select="'Angaben zur Prüfanwendung'"/>
|
||||||
|
<xsl:with-param name="color"
|
||||||
|
select="'black'"/>
|
||||||
|
</xsl:call-template>
|
||||||
|
<fo:table>
|
||||||
|
<fo:table-column border-style="none"/>
|
||||||
|
<fo:table-column column-width="70%"
|
||||||
|
border-style="none"/>
|
||||||
|
<fo:table-body>
|
||||||
|
<fo:table-row>
|
||||||
|
<fo:table-cell>
|
||||||
|
<fo:block text-align="left">Name:</fo:block>
|
||||||
|
</fo:table-cell>
|
||||||
|
<fo:table-cell>
|
||||||
|
<fo:block text-align="right">mustangproject.org</fo:block>
|
||||||
|
</fo:table-cell>
|
||||||
|
</fo:table-row>
|
||||||
|
<fo:table-row>
|
||||||
|
<fo:table-cell>
|
||||||
|
<fo:block text-align="left">Version:</fo:block>
|
||||||
|
</fo:table-cell>
|
||||||
|
<fo:table-cell>
|
||||||
|
<fo:block text-align="right">
|
||||||
|
<xsl:value-of select="./xml/info/validator/@version"/>
|
||||||
|
</fo:block>
|
||||||
|
</fo:table-cell>
|
||||||
|
</fo:table-row>
|
||||||
|
</fo:table-body>
|
||||||
|
</fo:table>
|
||||||
<xsl:call-template name="SubHeader">
|
<xsl:call-template name="SubHeader">
|
||||||
<xsl:with-param name="text"
|
<xsl:with-param name="text"
|
||||||
select="'Angaben zum geprüften Dokument'"/>
|
select="'Angaben zum geprüften Dokument'"/>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
xmlns:xrf="https://projekte.kosit.org/xrechnung/xrechnung-visualization/functions">
|
xmlns:xrf="https://projekte.kosit.org/xrechnung/xrechnung-visualization/functions">
|
||||||
|
|
||||||
<xsl:decimal-format name="de" decimal-separator="," grouping-separator="." NaN="" />
|
<xsl:decimal-format name="de" decimal-separator="," grouping-separator="." NaN="" />
|
||||||
|
<xsl:decimal-format name="fr" decimal-separator="," grouping-separator="." NaN="" />
|
||||||
<xsl:decimal-format name="en" decimal-separator="." grouping-separator="," NaN="" />
|
<xsl:decimal-format name="en" decimal-separator="." grouping-separator="," NaN="" />
|
||||||
|
|
||||||
<xsl:template name="uebersicht">
|
<xsl:template name="uebersicht">
|
||||||
|
|||||||
@@ -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("<rsm:CrossIndustryInvoice"));
|
||||||
|
|
||||||
|
// Assert: BT-18 block contains ID, TypeCode=130 and ReferenceTypeCode=AJW
|
||||||
|
assertThat(xml)
|
||||||
|
.valueByXPath("string(//*[local-name()='AdditionalReferencedDocument']/*[local-name()='TypeCode'])")
|
||||||
|
.isEqualTo("130");
|
||||||
|
|
||||||
|
assertThat(xml)
|
||||||
|
.valueByXPath("string(//*[local-name()='AdditionalReferencedDocument']/*[local-name()='IssuerAssignedID'])")
|
||||||
|
.isEqualTo("ID unique");
|
||||||
|
|
||||||
|
assertThat(xml)
|
||||||
|
.valueByXPath("string(//*[local-name()='AdditionalReferencedDocument']/*[local-name()='ReferenceTypeCode'])")
|
||||||
|
.isEqualTo("AJW");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
package org.mustangproject.ZUGFeRD;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mustangproject.ZUGFeRD.Profiles;
|
||||||
|
import org.mustangproject.ZUGFeRD.ZUGFeRD2PullProvider;
|
||||||
|
|
||||||
|
import org.mustangproject.Invoice;
|
||||||
|
import org.mustangproject.Item;
|
||||||
|
import org.mustangproject.Product;
|
||||||
|
import org.mustangproject.TradeParty;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for BT-23 (BusinessProcessSpecifiedDocumentContextParameter/ID) emission in CII output.
|
||||||
|
*
|
||||||
|
* Assumptions / prerequisites for the proposed fix:
|
||||||
|
* - IExportableTransaction#getBusinessProcessId() exists (as a default method returning null)
|
||||||
|
* - Invoice#setBusinessProcessId(String) exists and Invoice overrides getBusinessProcessId()
|
||||||
|
* - ZUGFeRD2PullProvider writes BT-23 if a custom businessProcessId is provided,
|
||||||
|
* otherwise keeps the current XRechnung default behavior.
|
||||||
|
*/
|
||||||
|
public class BusinessProcessIdCiiTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void xrechnung_nonRegression_defaultBusinessProcessId_isStillEmitted() throws Exception {
|
||||||
|
// ===== GIVEN: A minimal invoice (as provided) WITHOUT setting 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") // BT-1: Invoice number -> /.../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("<ram:BusinessProcessSpecifiedDocumentContextParameter>"),
|
||||||
|
"XRechnung should still emit BusinessProcessSpecifiedDocumentContextParameter (BT-23)."
|
||||||
|
);
|
||||||
|
|
||||||
|
assertTrue(
|
||||||
|
xml.contains("<ram:ID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</ram:ID>"),
|
||||||
|
"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("<ram:BusinessProcessSpecifiedDocumentContextParameter>"),
|
||||||
|
"When businessProcessId is set, BT-23 must be emitted even for EXTENDED."
|
||||||
|
);
|
||||||
|
|
||||||
|
assertTrue(
|
||||||
|
xml.contains("<ram:ID>B1</ram:ID>"),
|
||||||
|
"BT-23 must contain the user-provided businessProcessId."
|
||||||
|
);
|
||||||
|
|
||||||
|
// Extra sanity check: the guideline/specification identifier (BT-24) should still be present
|
||||||
|
assertTrue(
|
||||||
|
xml.contains("<ram:GuidelineSpecifiedDocumentContextParameter>"),
|
||||||
|
"BT-24 (GuidelineSpecifiedDocumentContextParameter) must be present."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
|
||||||
|
" <rsm:CrossIndustryInvoice xmlns:rsm=\"urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:ram=\"urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100\" xmlns:udt=\"urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100\" xmlns:qdt=\"urn:un:unece:uncefact:data:standard:QualifiedDataType:100\">\n" +
|
||||||
|
" <!-- generated by: mustangproject.org v2.15.1-SNAPSHOT-->\n" +
|
||||||
|
" <rsm:ExchangedDocumentContext>\n" +
|
||||||
|
" <ram:GuidelineSpecifiedDocumentContextParameter>\n" +
|
||||||
|
" <ram:ID>urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended</ram:ID>\n" +
|
||||||
|
" </ram:GuidelineSpecifiedDocumentContextParameter>\n" +
|
||||||
|
" </rsm:ExchangedDocumentContext>\n" +
|
||||||
|
" <rsm:ExchangedDocument>\n" +
|
||||||
|
" <ram:ID>471102</ram:ID>\n" +
|
||||||
|
" <ram:TypeCode>380</ram:TypeCode>\n" +
|
||||||
|
" <ram:IssueDateTime>\n" +
|
||||||
|
" <udt:DateTimeString format=\"102\">20260201</udt:DateTimeString>\n" +
|
||||||
|
" </ram:IssueDateTime>\n" +
|
||||||
|
" </rsm:ExchangedDocument>\n" +
|
||||||
|
" <rsm:SupplyChainTradeTransaction>\n" +
|
||||||
|
" <ram:IncludedSupplyChainTradeLineItem>\n" +
|
||||||
|
" <ram:AssociatedDocumentLineDocument>\n" +
|
||||||
|
" <ram:LineID>1</ram:LineID>\n" +
|
||||||
|
" </ram:AssociatedDocumentLineDocument>\n" +
|
||||||
|
" <ram:SpecifiedTradeProduct>\n" +
|
||||||
|
" <ram:Name>Trennblätter A4</ram:Name>\n" +
|
||||||
|
" </ram:SpecifiedTradeProduct>\n" +
|
||||||
|
" <ram:SpecifiedLineTradeAgreement>\n" +
|
||||||
|
" <ram:NetPriceProductTradePrice>\n" +
|
||||||
|
" <ram:ChargeAmount>9.9000</ram:ChargeAmount>\n" +
|
||||||
|
" <ram:BasisQuantity unitCode=\"H87\">1.0000</ram:BasisQuantity>\n" +
|
||||||
|
" </ram:NetPriceProductTradePrice>\n" +
|
||||||
|
" </ram:SpecifiedLineTradeAgreement>\n" +
|
||||||
|
" <ram:SpecifiedLineTradeDelivery>\n" +
|
||||||
|
" <ram:BilledQuantity unitCode=\"H87\">20.0000</ram:BilledQuantity>\n" +
|
||||||
|
" </ram:SpecifiedLineTradeDelivery>\n" +
|
||||||
|
" <ram:SpecifiedLineTradeSettlement>\n" +
|
||||||
|
" <ram:ApplicableTradeTax>\n" +
|
||||||
|
" <ram:TypeCode>VAT</ram:TypeCode>\n" +
|
||||||
|
" <ram:CategoryCode>S</ram:CategoryCode>\n" +
|
||||||
|
" <ram:RateApplicablePercent>19.00</ram:RateApplicablePercent>\n" +
|
||||||
|
" </ram:ApplicableTradeTax>\n" +
|
||||||
|
" <ram:SpecifiedTradeSettlementLineMonetarySummation>\n" +
|
||||||
|
" <ram:LineTotalAmount>198.00</ram:LineTotalAmount>\n" +
|
||||||
|
" </ram:SpecifiedTradeSettlementLineMonetarySummation>\n" +
|
||||||
|
" </ram:SpecifiedLineTradeSettlement>\n" +
|
||||||
|
" </ram:IncludedSupplyChainTradeLineItem>\n" +
|
||||||
|
" <ram:IncludedSupplyChainTradeLineItem>\n" +
|
||||||
|
" <ram:AssociatedDocumentLineDocument>\n" +
|
||||||
|
" <ram:LineID>2</ram:LineID>\n" +
|
||||||
|
" </ram:AssociatedDocumentLineDocument>\n" +
|
||||||
|
" <ram:SpecifiedTradeProduct>\n" +
|
||||||
|
" <ram:Name>Joghurt Banane</ram:Name>\n" +
|
||||||
|
" </ram:SpecifiedTradeProduct>\n" +
|
||||||
|
" <ram:SpecifiedLineTradeAgreement>\n" +
|
||||||
|
" <ram:NetPriceProductTradePrice>\n" +
|
||||||
|
" <ram:ChargeAmount>5.5000</ram:ChargeAmount>\n" +
|
||||||
|
" <ram:BasisQuantity unitCode=\"H87\">1.0000</ram:BasisQuantity>\n" +
|
||||||
|
" </ram:NetPriceProductTradePrice>\n" +
|
||||||
|
" </ram:SpecifiedLineTradeAgreement>\n" +
|
||||||
|
" <ram:SpecifiedLineTradeDelivery>\n" +
|
||||||
|
" <ram:BilledQuantity unitCode=\"H87\">50.0000</ram:BilledQuantity>\n" +
|
||||||
|
" </ram:SpecifiedLineTradeDelivery>\n" +
|
||||||
|
" <ram:SpecifiedLineTradeSettlement>\n" +
|
||||||
|
" <ram:ApplicableTradeTax>\n" +
|
||||||
|
" <ram:TypeCode>VAT</ram:TypeCode>\n" +
|
||||||
|
" <ram:CategoryCode>S</ram:CategoryCode>\n" +
|
||||||
|
" <ram:RateApplicablePercent>7.00</ram:RateApplicablePercent>\n" +
|
||||||
|
" </ram:ApplicableTradeTax>\n" +
|
||||||
|
" <ram:SpecifiedTradeSettlementLineMonetarySummation>\n" +
|
||||||
|
" <ram:LineTotalAmount>275.00</ram:LineTotalAmount>\n" +
|
||||||
|
" </ram:SpecifiedTradeSettlementLineMonetarySummation>\n" +
|
||||||
|
" </ram:SpecifiedLineTradeSettlement>\n" +
|
||||||
|
" </ram:IncludedSupplyChainTradeLineItem>\n" +
|
||||||
|
" <ram:ApplicableHeaderTradeAgreement>\n" +
|
||||||
|
" <ram:SellerTradeParty>\n" +
|
||||||
|
" <ram:Name>Lieferant GmbH</ram:Name>\n" +
|
||||||
|
" <ram:PostalTradeAddress>\n" +
|
||||||
|
" <ram:PostcodeCode>80333</ram:PostcodeCode>\n" +
|
||||||
|
" <ram:LineOne>Lieferantenstraße 20</ram:LineOne>\n" +
|
||||||
|
" <ram:CityName>München</ram:CityName>\n" +
|
||||||
|
" <ram:CountryID>DE</ram:CountryID>\n" +
|
||||||
|
" </ram:PostalTradeAddress>\n" +
|
||||||
|
" <ram:SpecifiedTaxRegistration>\n" +
|
||||||
|
" <ram:ID schemeID=\"VA\">DE123456789</ram:ID>\n" +
|
||||||
|
" </ram:SpecifiedTaxRegistration>\n" +
|
||||||
|
" <ram:SpecifiedTaxRegistration>\n" +
|
||||||
|
" <ram:ID schemeID=\"FC\">201/113/40209</ram:ID>\n" +
|
||||||
|
" </ram:SpecifiedTaxRegistration>\n" +
|
||||||
|
" </ram:SellerTradeParty>\n" +
|
||||||
|
" <ram:BuyerTradeParty>\n" +
|
||||||
|
" <ram:Name>Kunden AG Mitte</ram:Name>\n" +
|
||||||
|
" <ram:PostalTradeAddress>\n" +
|
||||||
|
" <ram:PostcodeCode>69876</ram:PostcodeCode>\n" +
|
||||||
|
" <ram:LineOne>Kundenstraße 15</ram:LineOne>\n" +
|
||||||
|
" <ram:CityName>Frankfurt</ram:CityName>\n" +
|
||||||
|
" <ram:CountryID>DE</ram:CountryID>\n" +
|
||||||
|
" </ram:PostalTradeAddress>\n" +
|
||||||
|
" </ram:BuyerTradeParty>\n" +
|
||||||
|
" </ram:ApplicableHeaderTradeAgreement>\n" +
|
||||||
|
" <ram:ApplicableHeaderTradeDelivery>\n" +
|
||||||
|
" <ram:ActualDeliverySupplyChainEvent>\n" +
|
||||||
|
" <ram:OccurrenceDateTime>\n" +
|
||||||
|
" <udt:DateTimeString format=\"102\">20180304</udt:DateTimeString>\n" +
|
||||||
|
" </ram:OccurrenceDateTime>\n" +
|
||||||
|
" </ram:ActualDeliverySupplyChainEvent>\n" +
|
||||||
|
" </ram:ApplicableHeaderTradeDelivery>\n" +
|
||||||
|
" <ram:ApplicableHeaderTradeSettlement>\n" +
|
||||||
|
" <ram:PaymentReference>471102</ram:PaymentReference>\n" +
|
||||||
|
" <ram:InvoiceCurrencyCode>EUR</ram:InvoiceCurrencyCode>\n" +
|
||||||
|
" <ram:ApplicableTradeTax>\n" +
|
||||||
|
" <ram:CalculatedAmount>19.25</ram:CalculatedAmount>\n" +
|
||||||
|
" <ram:TypeCode>VAT</ram:TypeCode>\n" +
|
||||||
|
" <ram:BasisAmount>275.00</ram:BasisAmount>\n" +
|
||||||
|
" <ram:CategoryCode>S</ram:CategoryCode>\n" +
|
||||||
|
" <ram:RateApplicablePercent>7.00</ram:RateApplicablePercent>\n" +
|
||||||
|
" </ram:ApplicableTradeTax>\n" +
|
||||||
|
" <ram:ApplicableTradeTax>\n" +
|
||||||
|
" <ram:CalculatedAmount>37.62</ram:CalculatedAmount>\n" +
|
||||||
|
" <ram:TypeCode>VAT</ram:TypeCode>\n" +
|
||||||
|
" <ram:BasisAmount>198.00</ram:BasisAmount>\n" +
|
||||||
|
" <ram:CategoryCode>S</ram:CategoryCode>\n" +
|
||||||
|
" <ram:RateApplicablePercent>19.00</ram:RateApplicablePercent>\n" +
|
||||||
|
" </ram:ApplicableTradeTax>\n" +
|
||||||
|
" <ram:SpecifiedTradePaymentTerms>\n" +
|
||||||
|
" <ram:Description>Please remit until 04.03.2018</ram:Description>\n" +
|
||||||
|
" <ram:DueDateDateTime>\n" +
|
||||||
|
" <udt:DateTimeString format=\"102\">20180304</udt:DateTimeString>\n" +
|
||||||
|
" </ram:DueDateDateTime>\n" +
|
||||||
|
" </ram:SpecifiedTradePaymentTerms>\n" +
|
||||||
|
" <ram:SpecifiedTradePaymentTerms>\n" +
|
||||||
|
" <ram:Description>Bis zum 15.02.2026 erhalten Sie 4,000 % Skonto, 21.19 EUR</ram:Description>\n" +
|
||||||
|
" <ram:DueDateDateTime>\n" +
|
||||||
|
" <udt:DateTimeString format=\"102\">20260215</udt:DateTimeString>\n" +
|
||||||
|
" </ram:DueDateDateTime>\n" +
|
||||||
|
" <ram:ApplicableTradePaymentDiscountTerms>\n" +
|
||||||
|
" <ram:BasisAmount>529.8700</ram:BasisAmount>\n" +
|
||||||
|
" <ram:CalculationPercent>4.00</ram:CalculationPercent>\n" +
|
||||||
|
" <ram:ActualDiscountAmount>21.19</ram:ActualDiscountAmount>\n" +
|
||||||
|
" </ram:ApplicableTradePaymentDiscountTerms>\n" +
|
||||||
|
" </ram:SpecifiedTradePaymentTerms>\n" +
|
||||||
|
" <ram:SpecifiedTradePaymentTerms>\n" +
|
||||||
|
" <ram:Description>Bis zum 16.02.2026 ohne Abzug</ram:Description>\n" +
|
||||||
|
" <ram:DueDateDateTime>\n" +
|
||||||
|
" <udt:DateTimeString format=\"102\">20260216</udt:DateTimeString>\n" +
|
||||||
|
" </ram:DueDateDateTime>\n" +
|
||||||
|
" </ram:SpecifiedTradePaymentTerms>\n" +
|
||||||
|
" <ram:SpecifiedTradeSettlementHeaderMonetarySummation>\n" +
|
||||||
|
" <ram:LineTotalAmount>473.00</ram:LineTotalAmount>\n" +
|
||||||
|
" <ram:ChargeTotalAmount>0.00</ram:ChargeTotalAmount>\n" +
|
||||||
|
" <ram:AllowanceTotalAmount>0.00</ram:AllowanceTotalAmount>\n" +
|
||||||
|
" <ram:TaxBasisTotalAmount>473.00</ram:TaxBasisTotalAmount>\n" +
|
||||||
|
" <ram:TaxTotalAmount currencyID=\"EUR\">56.87</ram:TaxTotalAmount>\n" +
|
||||||
|
" <ram:GrandTotalAmount>529.87</ram:GrandTotalAmount>\n" +
|
||||||
|
" <ram:TotalPrepaidAmount>0.00</ram:TotalPrepaidAmount>\n" +
|
||||||
|
" <ram:DuePayableAmount>529.87</ram:DuePayableAmount>\n" +
|
||||||
|
" </ram:SpecifiedTradeSettlementHeaderMonetarySummation>\n" +
|
||||||
|
" </ram:ApplicableHeaderTradeSettlement>\n" +
|
||||||
|
" </rsm:SupplyChainTradeTransaction>\n" +
|
||||||
|
" </rsm:CrossIndustryInvoice>";
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,7 +23,11 @@ import junit.framework.TestSuite;
|
|||||||
|
|
||||||
import org.apache.pdfbox.Loader;
|
import org.apache.pdfbox.Loader;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
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.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.pdfbox.pdmodel.encryption.InvalidPasswordException;
|
||||||
import org.apache.xmpbox.XMPMetadata;
|
import org.apache.xmpbox.XMPMetadata;
|
||||||
import org.apache.xmpbox.schema.PDFAIdentificationSchema;
|
import org.apache.xmpbox.schema.PDFAIdentificationSchema;
|
||||||
@@ -218,8 +222,6 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
|
|||||||
zi.doRecalculateItemPricesFromLineTotals();
|
zi.doRecalculateItemPricesFromLineTotals();
|
||||||
zi.doIgnoreCalculationErrors();
|
zi.doIgnoreCalculationErrors();
|
||||||
zi.setInputStream(inputStream);
|
zi.setInputStream(inputStream);
|
||||||
// Reading ZUGFeRD
|
|
||||||
String amount = zi.getAmount();
|
|
||||||
|
|
||||||
assertEquals("<?xpacket begin=\"\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n" +
|
assertEquals("<?xpacket begin=\"\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n" +
|
||||||
" <x:xmpmeta xmlns:x=\"adobe:ns:meta/\">\n" +
|
" <x:xmpmeta xmlns:x=\"adobe:ns:meta/\">\n" +
|
||||||
@@ -297,8 +299,8 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
|
|||||||
" </x:xmpmeta>\n" +
|
" </x:xmpmeta>\n" +
|
||||||
"<?xpacket end=\"r\"?>", zi.getXMP());
|
"<?xpacket end=\"r\"?>", zi.getXMP());
|
||||||
// this resembles the data written in MustangReaderWriterCustomXMLTest
|
// this resembles the data written in MustangReaderWriterCustomXMLTest
|
||||||
assertEquals(amount, "1005.55");
|
assertEquals("845.00", zi.getTaxBasisTotalAmount());
|
||||||
|
assertEquals("1005.55", zi.getAmount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -415,10 +417,11 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
|
|||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
ze.export(baos);
|
ze.export(baos);
|
||||||
ze.close();
|
ze.close();
|
||||||
String pdfContent = baos.toString(StandardCharsets.UTF_8);
|
byte[] targetBytes = baos.toByteArray();
|
||||||
assertFalse(pdfContent.indexOf(DocumentContextParameterTypeConstants.BASIC) >= 0);
|
String xmlContent = readEmbeddedXmlFromPdf(targetBytes, "ZUGFeRD-invoice.xml");
|
||||||
assertFalse(pdfContent.indexOf(DocumentContextParameterTypeConstants.EXTENDED) >= 0);
|
assertFalse(xmlContent.contains(DocumentContextParameterTypeConstants.BASIC));
|
||||||
assertTrue(pdfContent.indexOf(DocumentContextParameterTypeConstants.COMFORT) >= 0);
|
assertFalse(xmlContent.contains(DocumentContextParameterTypeConstants.EXTENDED));
|
||||||
|
assertTrue(xmlContent.contains(DocumentContextParameterTypeConstants.COMFORT));
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
fail("IOException should not happen in testZExport");
|
fail("IOException should not happen in testZExport");
|
||||||
@@ -442,7 +445,8 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
|
|||||||
result.write(buffer, 0, length);
|
result.write(buffer, 0, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
ze.addAdditionalFile("test.pdf", result.toByteArray());
|
var bytes = result.toByteArray();
|
||||||
|
ze.addAdditionalFile("test.pdf", bytes);
|
||||||
|
|
||||||
ze.setTransaction(this);
|
ze.setTransaction(this);
|
||||||
ze.disableAutoClose(true);
|
ze.disableAutoClose(true);
|
||||||
@@ -451,12 +455,14 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
|
|||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
ze.export(baos);
|
ze.export(baos);
|
||||||
ze.close();
|
ze.close();
|
||||||
String pdfContent = baos.toString(StandardCharsets.UTF_8);
|
byte[] targetBytes = baos.toByteArray();
|
||||||
assertFalse(pdfContent.indexOf("(via mustangproject.org") == -1);
|
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
|
// check for pdf-a schema extension
|
||||||
assertFalse(pdfContent.indexOf("<fx:ConformanceLevel>EN 16931</fx:ConformanceLevel>") == -1);
|
assertTrue(pdfContent.contains("<fx:ConformanceLevel>EN 16931</fx:ConformanceLevel>"));
|
||||||
assertFalse(pdfContent.indexOf("<pdfaSchema:prefix>fx</pdfaSchema:prefix>") == -1);
|
assertTrue(pdfContent.contains("<pdfaSchema:prefix>fx</pdfaSchema:prefix>"));
|
||||||
assertFalse(pdfContent.indexOf("urn:cen.eu:en16931:2017") == -1);
|
assertTrue(xmlContent.contains("urn:cen.eu:en16931:2017"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// now check the contents (like MustangReaderTest)
|
// now check the contents (like MustangReaderTest)
|
||||||
@@ -470,6 +476,26 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
|
|||||||
assertEquals(getNumber(), zi.getForeignReference());
|
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<String, PDComplexFileSpecification> 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() {
|
public void testExceptionOnPDF14() {
|
||||||
|
|
||||||
final String TARGET_PDF = "./target/testout-MustangGnuaccountingBeispielRE-20170509_505new.pdf";
|
final String TARGET_PDF = "./target/testout-MustangGnuaccountingBeispielRE-20170509_505new.pdf";
|
||||||
|
|||||||
@@ -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("<ram:ParentLineID>01</ram:ParentLineID>"),
|
||||||
|
"XML must contain ParentLineID element");
|
||||||
|
assertTrue(xml.contains("<ram:LineStatusReasonCode>DETAIL</ram:LineStatusReasonCode>"),
|
||||||
|
"XML must contain DETAIL LineStatusReasonCode");
|
||||||
|
assertTrue(xml.contains("<ram:LineStatusReasonCode>GROUP</ram:LineStatusReasonCode>"),
|
||||||
|
"XML must contain GROUP LineStatusReasonCode");
|
||||||
|
assertTrue(xml.contains("<ram:LineStatusReasonCode>INFORMATION</ram:LineStatusReasonCode>"),
|
||||||
|
"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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,8 +32,6 @@ import java.io.IOException;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
public class VisualizationTest extends ResourceCase {
|
public class VisualizationTest extends ResourceCase {
|
||||||
@@ -98,13 +96,8 @@ public class VisualizationTest extends ResourceCase {
|
|||||||
|
|
||||||
|
|
||||||
public void testPDFVisualizationCII() {
|
public void testPDFVisualizationCII() {
|
||||||
|
|
||||||
File CIIinputFile = getResourceAsFile("cii/01.01a-INVOICE.cii.xml");
|
File CIIinputFile = getResourceAsFile("cii/01.01a-INVOICE.cii.xml");
|
||||||
|
|
||||||
// the writing part
|
|
||||||
|
|
||||||
String expected = null;
|
|
||||||
String result = null;
|
|
||||||
try {
|
try {
|
||||||
ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer();
|
ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer();
|
||||||
zvi.toPDF(CIIinputFile.getAbsolutePath(), TARGET_PDF_CII);
|
zvi.toPDF(CIIinputFile.getAbsolutePath(), TARGET_PDF_CII);
|
||||||
@@ -116,6 +109,47 @@ public class VisualizationTest extends ResourceCase {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
assertTrue(ByteArraySearcher.startsWith(Files.readAllBytes(Paths.get(TARGET_PDF_CII)), new byte[]{'%', 'P', 'D', 'F'}));
|
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)), "<rdf:li>de</rdf:li>".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)), "<rdf:li>en</rdf:li>".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)), "<rdf:li>fr</rdf:li>".getBytes()));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
fail("IOException should not occur");
|
fail("IOException should not occur");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -546,7 +546,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)))
|
||||||
.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"))
|
.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);
|
String theXML = new String(ze.getProvider().getXML(), StandardCharsets.UTF_8);
|
||||||
assertTrue(theXML.contains("<rsm:CrossIndustryInvoice"));
|
assertTrue(theXML.contains("<rsm:CrossIndustryInvoice"));
|
||||||
@@ -600,7 +600,7 @@ public class ZF2PushTest extends TestCase {
|
|||||||
ReferencedDocument dr1=new ReferencedDocument("90-kl-98798-C", sdf.parse("2025-10-12"));
|
ReferencedDocument dr1=new ReferencedDocument("90-kl-98798-C", sdf.parse("2025-10-12"));
|
||||||
ReferencedDocument dr2=new ReferencedDocument("90-kl-98798-C1", sdf.parse("2025-10-13"));
|
ReferencedDocument dr2=new ReferencedDocument("90-kl-98798-C1", sdf.parse("2025-10-13"));
|
||||||
dr2.setReferenceTypeCode("AAG");
|
dr2.setReferenceTypeCode("AAG");
|
||||||
ze.setTransaction(new Invoice().setCurrency("CHF").addNote("document level 1/2").addNote("document level 2/2").setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setPaymentReference("Verwendungszweck").setDocumentName("Rechnung")
|
ze.setTransaction(new Invoice().setTestIndicator().setCurrency("CHF").addNote("document level 1/2").addNote("document level 2/2").setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setPaymentReference("Verwendungszweck").setDocumentName("Rechnung")
|
||||||
.setSellerOrderReferencedDocumentID("9384").setBuyerOrderReferencedDocumentID("28934")
|
.setSellerOrderReferencedDocumentID("9384").setBuyerOrderReferencedDocumentID("28934")
|
||||||
.setDetailedDeliveryPeriod(new SimpleDateFormat("yyyyMMdd").parse(occurrenceFrom), new SimpleDateFormat("yyyyMMdd").parse(occurrenceTo))
|
.setDetailedDeliveryPeriod(new SimpleDateFormat("yyyyMMdd").parse(occurrenceFrom), new SimpleDateFormat("yyyyMMdd").parse(occurrenceTo))
|
||||||
.setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID(taxID).setEmail("sender@test.org").setID(orgID).addVATID("DE0815"))
|
.setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID(taxID).setEmail("sender@test.org").setID(orgID).addVATID("DE0815"))
|
||||||
@@ -630,6 +630,7 @@ public class ZF2PushTest extends TestCase {
|
|||||||
|
|
||||||
String theXML = new String(ze.getProvider().getXML(), StandardCharsets.UTF_8);
|
String theXML = new String(ze.getProvider().getXML(), StandardCharsets.UTF_8);
|
||||||
assertTrue(theXML.contains("<rsm:CrossIndustryInvoice"));
|
assertTrue(theXML.contains("<rsm:CrossIndustryInvoice"));
|
||||||
|
assertTrue(theXML.contains("<ram:TestIndicator"));
|
||||||
ze.export(TARGET_PUSHEDGE);
|
ze.export(TARGET_PUSHEDGE);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
fail("IOException should not be raised");
|
fail("IOException should not be raised");
|
||||||
|
|||||||
2
pom.xml
2
pom.xml
@@ -71,7 +71,7 @@
|
|||||||
<version.commons-io>2.20.0</version.commons-io><!-- from 2.16.1 -->
|
<version.commons-io>2.20.0</version.commons-io><!-- from 2.16.1 -->
|
||||||
<version.jakarta.xml.bind>4.0.2</version.jakarta.xml.bind>
|
<version.jakarta.xml.bind>4.0.2</version.jakarta.xml.bind>
|
||||||
<version.net.sf.saxon>12.8</version.net.sf.saxon><!-- from 12.4 -->
|
<version.net.sf.saxon>12.8</version.net.sf.saxon><!-- from 12.4 -->
|
||||||
<version.org.apache.pdfbox>3.0.6</version.org.apache.pdfbox>
|
<version.org.apache.pdfbox>3.0.7</version.org.apache.pdfbox>
|
||||||
<version.org.apache.xmlgraphics>2.11</version.org.apache.xmlgraphics><!-- from 2.10 -->
|
<version.org.apache.xmlgraphics>2.11</version.org.apache.xmlgraphics><!-- from 2.10 -->
|
||||||
<version.org.codehaus.janino>3.1.12</version.org.codehaus.janino><!-- from 3.1.7 -->
|
<version.org.codehaus.janino>3.1.12</version.org.codehaus.janino><!-- from 3.1.7 -->
|
||||||
<version.org.dom4j>2.1.5</version.org.dom4j><!-- from 2.1.4 -->
|
<version.org.dom4j>2.1.5</version.org.dom4j><!-- from 2.1.4 -->
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import java.util.HashMap;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
import javax.xml.xpath.XPath;
|
import javax.xml.xpath.XPath;
|
||||||
import javax.xml.xpath.XPathConstants;
|
import javax.xml.xpath.XPathConstants;
|
||||||
@@ -23,6 +22,7 @@ import javax.xml.xpath.XPathExpressionException;
|
|||||||
import javax.xml.xpath.XPathFactory;
|
import javax.xml.xpath.XPathFactory;
|
||||||
|
|
||||||
import org.mustangproject.util.ByteArraySearcher;
|
import org.mustangproject.util.ByteArraySearcher;
|
||||||
|
import org.mustangproject.XMLTools;
|
||||||
import org.mustangproject.ZUGFeRD.ZUGFeRDImporter;
|
import org.mustangproject.ZUGFeRD.ZUGFeRDImporter;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -127,7 +127,7 @@ public class PDFValidator extends Validator {
|
|||||||
final ZUGFeRDImporter zi = new ZUGFeRDImporter();
|
final ZUGFeRDImporter zi = new ZUGFeRDImporter();
|
||||||
zi.doIgnoreCalculationErrors();//of course the calculation will still be schematron checked
|
zi.doIgnoreCalculationErrors();//of course the calculation will still be schematron checked
|
||||||
zi.setInputStream(inputStream);
|
zi.setInputStream(inputStream);
|
||||||
final String xmp = zi.getXMP();
|
final String xmp = zi.getXMP().substring(zi.getXMP().indexOf('<'));
|
||||||
|
|
||||||
final Document docXMP;
|
final Document docXMP;
|
||||||
|
|
||||||
@@ -142,12 +142,7 @@ public class PDFValidator extends Validator {
|
|||||||
* <zf:Version>1.0</zf:Version>
|
* <zf:Version>1.0</zf:Version>
|
||||||
*/
|
*/
|
||||||
try {
|
try {
|
||||||
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
final DocumentBuilder builder = XMLTools.getDocumentBuilder(false);
|
||||||
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
|
||||||
// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
|
|
||||||
factory.setXIncludeAware(false);
|
|
||||||
|
|
||||||
final DocumentBuilder builder = factory.newDocumentBuilder();
|
|
||||||
final InputSource is = new InputSource(new StringReader(xmp));
|
final InputSource is = new InputSource(new StringReader(xmp));
|
||||||
docXMP = builder.parse(is);
|
docXMP = builder.parse(is);
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,8 @@ import java.net.URL;
|
|||||||
import javax.xml.XMLConstants;
|
import javax.xml.XMLConstants;
|
||||||
import javax.xml.transform.Source;
|
import javax.xml.transform.Source;
|
||||||
import javax.xml.transform.stream.StreamSource;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.xml.sax.SAXException;
|
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 {
|
protected void validateSchema(byte[] xmlRawData, String schemaPath, int section, EPart part) throws IrrecoverableValidationError {
|
||||||
URL schemaFile = Thread.currentThread().getContextClassLoader().getResource("schema/" + schemaPath);
|
URL schemaFile = Thread.currentThread().getContextClassLoader().getResource("schema/" + schemaPath);
|
||||||
Source xmlData = new StreamSource(new ByteArrayInputStream(xmlRawData));
|
Source xmlData = new StreamSource(new ByteArrayInputStream(xmlRawData));
|
||||||
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
|
|
||||||
try {
|
try {
|
||||||
schemaFactory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
|
javax.xml.validation.Validator validator = XMLTools.getValidator(schemaFile);
|
||||||
Schema schema = schemaFactory.newSchema(schemaFile);
|
|
||||||
javax.xml.validation.Validator validator = schema.newValidator();
|
|
||||||
validator.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
|
validator.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
|
||||||
validator.validate(xmlData);
|
validator.validate(xmlData);
|
||||||
} catch (SAXException e) {
|
} catch (SAXException e) {
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ import java.text.ParseException;
|
|||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.xml.XMLConstants;
|
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
|
||||||
import javax.xml.transform.stream.StreamSource;
|
import javax.xml.transform.stream.StreamSource;
|
||||||
import javax.xml.xpath.XPath;
|
import javax.xml.xpath.XPath;
|
||||||
import javax.xml.xpath.XPathConstants;
|
import javax.xml.xpath.XPathConstants;
|
||||||
@@ -156,26 +154,7 @@ public class XMLValidator extends Validator {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
final DocumentBuilder db = XMLTools.getDocumentBuilder(true);
|
||||||
//REDHAT
|
|
||||||
//https://www.blackhat.com/docs/us-15/materials/us-15-Wang-FileCry-The-New-Age-Of-XXE-java-wp.pdf
|
|
||||||
dbf.setAttribute(XMLConstants.FEATURE_SECURE_PROCESSING, true);
|
|
||||||
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
|
|
||||||
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
|
|
||||||
|
|
||||||
//OWASP
|
|
||||||
//https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
|
|
||||||
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
|
||||||
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
|
||||||
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
|
||||||
// Disable external DTDs as well
|
|
||||||
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
|
|
||||||
// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
|
|
||||||
dbf.setXIncludeAware(false);
|
|
||||||
dbf.setExpandEntityReferences(false);
|
|
||||||
dbf.setNamespaceAware(true);
|
|
||||||
|
|
||||||
final DocumentBuilder db = dbf.newDocumentBuilder();
|
|
||||||
final InputSource is = new InputSource(new StringReader(zfXML));
|
final InputSource is = new InputSource(new StringReader(zfXML));
|
||||||
final Document doc = db.parse(is);
|
final Document doc = db.parse(is);
|
||||||
|
|
||||||
@@ -213,8 +192,10 @@ public class XMLValidator extends Validator {
|
|||||||
boolean isExtended = false;
|
boolean isExtended = false;
|
||||||
boolean isXRechnung = false;
|
boolean isXRechnung = false;
|
||||||
String currentZFVersionDir = "ZF_240";
|
String currentZFVersionDir = "ZF_240";
|
||||||
|
String currentXPZ12VersionDir ="XP_Z12_012";
|
||||||
int mainSchematronSectionErrorTypeCode = 4;
|
int mainSchematronSectionErrorTypeCode = 4;
|
||||||
String xsltFilename = null;
|
String xsltFilename = null;
|
||||||
|
boolean runFrenchCiiSchematron = false;
|
||||||
// urn:ferd:CrossIndustryDocument:invoice:1p0:extended,
|
// urn:ferd:CrossIndustryDocument:invoice:1p0:extended,
|
||||||
// urn:ferd:CrossIndustryDocument:invoice:1p0:comfort,
|
// urn:ferd:CrossIndustryDocument:invoice:1p0:comfort,
|
||||||
// urn:ferd:CrossIndustryDocument:invoice:1p0:basic,
|
// 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
|
} else if (root.getLocalName().equalsIgnoreCase("CrossIndustryInvoice")) { // ZUGFeRD 2.0 or Factur-X
|
||||||
context.setGeneration("2");
|
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");
|
isMiniumum = contextProfile.contains("minimum");
|
||||||
isBasic = contextProfile.contains("basic");
|
isBasic = contextProfile.contains("basic");
|
||||||
@@ -391,6 +377,11 @@ public class XMLValidator extends Validator {
|
|||||||
// main schematron validation
|
// main schematron validation
|
||||||
validateSchematron(zfXML, xsltFilename, mainSchematronSectionErrorTypeCode, ESeverity.error);
|
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()))) {
|
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 {
|
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
|
//Guideline ID=urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_1.2 or
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ import java.text.SimpleDateFormat;
|
|||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import javax.xml.XMLConstants;
|
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.dom4j.DocumentException;
|
import org.dom4j.DocumentException;
|
||||||
@@ -143,24 +141,7 @@ public class ZUGFeRDValidator {
|
|||||||
boolean isXML = false;
|
boolean isXML = false;
|
||||||
String xmlAsString = null;
|
String xmlAsString = null;
|
||||||
try {
|
try {
|
||||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
DocumentBuilder db = XMLTools.getDocumentBuilder(true);
|
||||||
//REDHAT
|
|
||||||
//https://www.blackhat.com/docs/us-15/materials/us-15-Wang-FileCry-The-New-Age-Of-XXE-java-wp.pdf
|
|
||||||
dbf.setAttribute(XMLConstants.FEATURE_SECURE_PROCESSING, true);
|
|
||||||
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
|
|
||||||
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
|
|
||||||
|
|
||||||
//OWASP
|
|
||||||
//https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
|
|
||||||
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
|
||||||
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
|
||||||
// Disable external DTDs as well
|
|
||||||
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
|
|
||||||
// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
|
|
||||||
dbf.setXIncludeAware(false);
|
|
||||||
dbf.setExpandEntityReferences(false);
|
|
||||||
dbf.setNamespaceAware(true);
|
|
||||||
DocumentBuilder db = dbf.newDocumentBuilder();
|
|
||||||
|
|
||||||
content = XMLTools.removeBOM(content);
|
content = XMLTools.removeBOM(content);
|
||||||
xmlAsString = new String(content, StandardCharsets.UTF_8);
|
xmlAsString = new String(content, StandardCharsets.UTF_8);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -331,4 +331,29 @@ public class LibraryTest extends ResourceCase {
|
|||||||
.isEqualTo(0);
|
.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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -179,6 +179,15 @@ public class PDFValidatorTest extends ResourceCase {
|
|||||||
|
|
||||||
assertEquals(true, actual
|
assertEquals(true, actual
|
||||||
.contains("<error type=\"28\">XMP Metadata: Could not parse XMP metadata (XML invalid)</error>"));
|
.contains("<error type=\"28\">XMP Metadata: Could not parse XMP metadata (XML invalid)</error>"));
|
||||||
|
|
||||||
|
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("<error"));
|
||||||
} catch (final IrrecoverableValidationError e) {
|
} catch (final IrrecoverableValidationError e) {
|
||||||
// ignore, will be in XML output anyway
|
// ignore, will be in XML output anyway
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -337,6 +337,33 @@ public class XMLValidatorTest extends ResourceCase {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testFrenchSchematronValidation() {
|
||||||
|
final ValidationContext ctx = new ValidationContext(null);
|
||||||
|
final XMLValidator xv = new XMLValidator(ctx);
|
||||||
|
|
||||||
|
File tempFile = getResourceAsFile("validV2FR.xml");
|
||||||
|
try {
|
||||||
|
xv.setFilename(tempFile.getAbsolutePath());
|
||||||
|
xv.validate();
|
||||||
|
|
||||||
|
String s = "<validation>" + xv.getXMLResult() + "</validation>";
|
||||||
|
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() {
|
public void testUBLValidation() {
|
||||||
ValidationContext ctx = new ValidationContext(null);
|
ValidationContext ctx = new ValidationContext(null);
|
||||||
|
|||||||
@@ -261,4 +261,27 @@ public class ZUGFeRDValidatorTest extends ResourceCase {
|
|||||||
.isEqualTo("valid");
|
.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");
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
validator/src/test/resources/103767-Invoice-2026-03-24-1.pdf
Normal file
BIN
validator/src/test/resources/103767-Invoice-2026-03-24-1.pdf
Normal file
Binary file not shown.
186
validator/src/test/resources/validV2FR.xml
Normal file
186
validator/src/test/resources/validV2FR.xml
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
<?xml version='1.0' encoding="UTF-8" standalone="yes"?>
|
||||||
|
<rsm:CrossIndustryInvoice xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100" xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">
|
||||||
|
<rsm:ExchangedDocumentContext>
|
||||||
|
<ram:GuidelineSpecifiedDocumentContextParameter>
|
||||||
|
<ram:ID>urn:cen.eu:en16931:2017</ram:ID>
|
||||||
|
</ram:GuidelineSpecifiedDocumentContextParameter>
|
||||||
|
</rsm:ExchangedDocumentContext>
|
||||||
|
<rsm:ExchangedDocument>
|
||||||
|
<ram:ID>FAC-20171118-506</ram:ID>
|
||||||
|
<ram:TypeCode>380</ram:TypeCode>
|
||||||
|
<ram:IssueDateTime><udt:DateTimeString format="102">20171118</udt:DateTimeString></ram:IssueDateTime>
|
||||||
|
</rsm:ExchangedDocument>
|
||||||
|
<rsm:SupplyChainTradeTransaction>
|
||||||
|
<ram:IncludedSupplyChainTradeLineItem>
|
||||||
|
<ram:AssociatedDocumentLineDocument>
|
||||||
|
<ram:LineID>1</ram:LineID>
|
||||||
|
</ram:AssociatedDocumentLineDocument>
|
||||||
|
<ram:SpecifiedTradeProduct>
|
||||||
|
<ram:Name>Prestation graphique</ram:Name>
|
||||||
|
<ram:Description></ram:Description>
|
||||||
|
</ram:SpecifiedTradeProduct>
|
||||||
|
<ram:SpecifiedLineTradeAgreement>
|
||||||
|
<ram:GrossPriceProductTradePrice>
|
||||||
|
<ram:ChargeAmount>160.0000</ram:ChargeAmount>
|
||||||
|
<ram:BasisQuantity unitCode="HUR">1.0000</ram:BasisQuantity>
|
||||||
|
</ram:GrossPriceProductTradePrice>
|
||||||
|
<ram:NetPriceProductTradePrice>
|
||||||
|
<ram:ChargeAmount>160.0000</ram:ChargeAmount>
|
||||||
|
<ram:BasisQuantity unitCode="HUR">1.0000</ram:BasisQuantity>
|
||||||
|
</ram:NetPriceProductTradePrice>
|
||||||
|
</ram:SpecifiedLineTradeAgreement>
|
||||||
|
<ram:SpecifiedLineTradeDelivery>
|
||||||
|
<ram:BilledQuantity unitCode="HUR">1.0000</ram:BilledQuantity>
|
||||||
|
</ram:SpecifiedLineTradeDelivery>
|
||||||
|
<ram:SpecifiedLineTradeSettlement>
|
||||||
|
<ram:ApplicableTradeTax>
|
||||||
|
<ram:TypeCode>VAT</ram:TypeCode>
|
||||||
|
<ram:CategoryCode>S</ram:CategoryCode>
|
||||||
|
<ram:RateApplicablePercent>7.00</ram:RateApplicablePercent>
|
||||||
|
</ram:ApplicableTradeTax>
|
||||||
|
<ram:SpecifiedTradeSettlementLineMonetarySummation>
|
||||||
|
<ram:LineTotalAmount>160.00</ram:LineTotalAmount>
|
||||||
|
</ram:SpecifiedTradeSettlementLineMonetarySummation>
|
||||||
|
</ram:SpecifiedLineTradeSettlement>
|
||||||
|
</ram:IncludedSupplyChainTradeLineItem>
|
||||||
|
<ram:IncludedSupplyChainTradeLineItem>
|
||||||
|
<ram:AssociatedDocumentLineDocument>
|
||||||
|
<ram:LineID>2</ram:LineID>
|
||||||
|
</ram:AssociatedDocumentLineDocument>
|
||||||
|
<ram:SpecifiedTradeProduct>
|
||||||
|
<ram:Name>Ballon decoratif 500 ml</ram:Name>
|
||||||
|
<ram:Description></ram:Description>
|
||||||
|
</ram:SpecifiedTradeProduct>
|
||||||
|
<ram:SpecifiedLineTradeAgreement>
|
||||||
|
<ram:GrossPriceProductTradePrice>
|
||||||
|
<ram:ChargeAmount>0.7900</ram:ChargeAmount>
|
||||||
|
<ram:BasisQuantity unitCode="C62">1.0000</ram:BasisQuantity>
|
||||||
|
</ram:GrossPriceProductTradePrice>
|
||||||
|
<ram:NetPriceProductTradePrice>
|
||||||
|
<ram:ChargeAmount>0.7900</ram:ChargeAmount>
|
||||||
|
<ram:BasisQuantity unitCode="C62">1.0000</ram:BasisQuantity>
|
||||||
|
</ram:NetPriceProductTradePrice>
|
||||||
|
</ram:SpecifiedLineTradeAgreement>
|
||||||
|
<ram:SpecifiedLineTradeDelivery>
|
||||||
|
<ram:BilledQuantity unitCode="C62">400.0000</ram:BilledQuantity>
|
||||||
|
</ram:SpecifiedLineTradeDelivery>
|
||||||
|
<ram:SpecifiedLineTradeSettlement>
|
||||||
|
<ram:ApplicableTradeTax>
|
||||||
|
<ram:TypeCode>VAT</ram:TypeCode>
|
||||||
|
<ram:CategoryCode>S</ram:CategoryCode>
|
||||||
|
<ram:RateApplicablePercent>19.00</ram:RateApplicablePercent>
|
||||||
|
</ram:ApplicableTradeTax>
|
||||||
|
<ram:SpecifiedTradeSettlementLineMonetarySummation>
|
||||||
|
<ram:LineTotalAmount>316.00</ram:LineTotalAmount>
|
||||||
|
</ram:SpecifiedTradeSettlementLineMonetarySummation>
|
||||||
|
</ram:SpecifiedLineTradeSettlement>
|
||||||
|
</ram:IncludedSupplyChainTradeLineItem>
|
||||||
|
<ram:IncludedSupplyChainTradeLineItem>
|
||||||
|
<ram:AssociatedDocumentLineDocument>
|
||||||
|
<ram:LineID>3</ram:LineID>
|
||||||
|
</ram:AssociatedDocumentLineDocument>
|
||||||
|
<ram:SpecifiedTradeProduct>
|
||||||
|
<ram:Name>Helium par litre</ram:Name>
|
||||||
|
<ram:Description></ram:Description>
|
||||||
|
</ram:SpecifiedTradeProduct>
|
||||||
|
<ram:SpecifiedLineTradeAgreement>
|
||||||
|
<ram:GrossPriceProductTradePrice>
|
||||||
|
<ram:ChargeAmount>0.1000</ram:ChargeAmount>
|
||||||
|
<ram:BasisQuantity unitCode="LTR">1.0000</ram:BasisQuantity>
|
||||||
|
</ram:GrossPriceProductTradePrice>
|
||||||
|
<ram:NetPriceProductTradePrice>
|
||||||
|
<ram:ChargeAmount>0.1000</ram:ChargeAmount>
|
||||||
|
<ram:BasisQuantity unitCode="LTR">1.0000</ram:BasisQuantity>
|
||||||
|
</ram:NetPriceProductTradePrice>
|
||||||
|
</ram:SpecifiedLineTradeAgreement>
|
||||||
|
<ram:SpecifiedLineTradeDelivery>
|
||||||
|
<ram:BilledQuantity unitCode="LTR">200.0000</ram:BilledQuantity>
|
||||||
|
</ram:SpecifiedLineTradeDelivery>
|
||||||
|
<ram:SpecifiedLineTradeSettlement>
|
||||||
|
<ram:ApplicableTradeTax>
|
||||||
|
<ram:TypeCode>VAT</ram:TypeCode>
|
||||||
|
<ram:CategoryCode>S</ram:CategoryCode>
|
||||||
|
<ram:RateApplicablePercent>19.00</ram:RateApplicablePercent>
|
||||||
|
</ram:ApplicableTradeTax>
|
||||||
|
<ram:SpecifiedTradeSettlementLineMonetarySummation>
|
||||||
|
<ram:LineTotalAmount>20.00</ram:LineTotalAmount>
|
||||||
|
</ram:SpecifiedTradeSettlementLineMonetarySummation>
|
||||||
|
</ram:SpecifiedLineTradeSettlement>
|
||||||
|
</ram:IncludedSupplyChainTradeLineItem>
|
||||||
|
<ram:ApplicableHeaderTradeAgreement>
|
||||||
|
<ram:SellerTradeParty>
|
||||||
|
<ram:Name>Atelier du Ballon SARL</ram:Name>
|
||||||
|
<ram:PostalTradeAddress>
|
||||||
|
<ram:PostcodeCode>75001</ram:PostcodeCode>
|
||||||
|
<ram:LineOne>1 rue du Louvre</ram:LineOne>
|
||||||
|
<ram:CityName>Paris</ram:CityName>
|
||||||
|
<ram:CountryID>FR</ram:CountryID>
|
||||||
|
</ram:PostalTradeAddress>
|
||||||
|
<ram:SpecifiedTaxRegistration>
|
||||||
|
<ram:ID schemeID="FC">552081317</ram:ID>
|
||||||
|
</ram:SpecifiedTaxRegistration>
|
||||||
|
<ram:SpecifiedTaxRegistration>
|
||||||
|
<ram:ID schemeID="VA">FR44552081317</ram:ID>
|
||||||
|
</ram:SpecifiedTaxRegistration>
|
||||||
|
</ram:SellerTradeParty>
|
||||||
|
<ram:BuyerTradeParty>
|
||||||
|
<ram:Name>Maison de Evenements SAS</ram:Name>
|
||||||
|
<ram:PostalTradeAddress>
|
||||||
|
<ram:PostcodeCode>69002</ram:PostcodeCode>
|
||||||
|
<ram:LineOne>18 rue de Brest</ram:LineOne>
|
||||||
|
<ram:CityName>Lyon</ram:CityName>
|
||||||
|
<ram:CountryID>FR</ram:CountryID>
|
||||||
|
</ram:PostalTradeAddress>
|
||||||
|
<ram:SpecifiedTaxRegistration>
|
||||||
|
<ram:ID schemeID="VA">FR19787878784</ram:ID>
|
||||||
|
</ram:SpecifiedTaxRegistration>
|
||||||
|
</ram:BuyerTradeParty>
|
||||||
|
</ram:ApplicableHeaderTradeAgreement>
|
||||||
|
<ram:ApplicableHeaderTradeDelivery>
|
||||||
|
<ram:ActualDeliverySupplyChainEvent>
|
||||||
|
<ram:OccurrenceDateTime><udt:DateTimeString format="102">20171117</udt:DateTimeString></ram:OccurrenceDateTime>
|
||||||
|
</ram:ActualDeliverySupplyChainEvent>
|
||||||
|
</ram:ApplicableHeaderTradeDelivery>
|
||||||
|
<ram:ApplicableHeaderTradeSettlement>
|
||||||
|
<ram:PaymentReference>FAC-20171118-506</ram:PaymentReference>
|
||||||
|
<ram:InvoiceCurrencyCode>EUR</ram:InvoiceCurrencyCode>
|
||||||
|
<ram:SpecifiedTradeSettlementPaymentMeans>
|
||||||
|
<ram:TypeCode>42</ram:TypeCode>
|
||||||
|
<ram:Information>Virement bancaire</ram:Information>
|
||||||
|
<ram:PayeePartyCreditorFinancialAccount>
|
||||||
|
<ram:IBANID>FR76 3000 6000 0112 3456 7890 189</ram:IBANID>
|
||||||
|
</ram:PayeePartyCreditorFinancialAccount>
|
||||||
|
<ram:PayeeSpecifiedCreditorFinancialInstitution>
|
||||||
|
<ram:BICID>AGRIFRPPXXX</ram:BICID>
|
||||||
|
</ram:PayeeSpecifiedCreditorFinancialInstitution>
|
||||||
|
</ram:SpecifiedTradeSettlementPaymentMeans>
|
||||||
|
<ram:ApplicableTradeTax>
|
||||||
|
<ram:CalculatedAmount>11.20</ram:CalculatedAmount>
|
||||||
|
<ram:TypeCode>VAT</ram:TypeCode>
|
||||||
|
<ram:BasisAmount>160.00</ram:BasisAmount>
|
||||||
|
<ram:CategoryCode>S</ram:CategoryCode>
|
||||||
|
<ram:RateApplicablePercent>7.00</ram:RateApplicablePercent>
|
||||||
|
</ram:ApplicableTradeTax>
|
||||||
|
<ram:ApplicableTradeTax>
|
||||||
|
<ram:CalculatedAmount>63.84</ram:CalculatedAmount>
|
||||||
|
<ram:TypeCode>VAT</ram:TypeCode>
|
||||||
|
<ram:BasisAmount>336.00</ram:BasisAmount>
|
||||||
|
<ram:CategoryCode>S</ram:CategoryCode>
|
||||||
|
<ram:RateApplicablePercent>19.00</ram:RateApplicablePercent>
|
||||||
|
</ram:ApplicableTradeTax>
|
||||||
|
<ram:SpecifiedTradePaymentTerms>
|
||||||
|
<ram:Description>Paiement sans escompte avant le 09.12.2017</ram:Description>
|
||||||
|
<ram:DueDateDateTime><udt:DateTimeString format="102">20171209</udt:DateTimeString></ram:DueDateDateTime>
|
||||||
|
</ram:SpecifiedTradePaymentTerms>
|
||||||
|
<ram:SpecifiedTradeSettlementHeaderMonetarySummation>
|
||||||
|
<ram:LineTotalAmount>496.00</ram:LineTotalAmount>
|
||||||
|
<ram:ChargeTotalAmount>0.00</ram:ChargeTotalAmount>
|
||||||
|
<ram:AllowanceTotalAmount>0.00</ram:AllowanceTotalAmount>
|
||||||
|
<ram:TaxBasisTotalAmount>496.00</ram:TaxBasisTotalAmount>
|
||||||
|
<ram:TaxTotalAmount currencyID="EUR">75.04</ram:TaxTotalAmount>
|
||||||
|
<ram:GrandTotalAmount>571.04</ram:GrandTotalAmount>
|
||||||
|
<ram:DuePayableAmount>571.04</ram:DuePayableAmount>
|
||||||
|
</ram:SpecifiedTradeSettlementHeaderMonetarySummation>
|
||||||
|
</ram:ApplicableHeaderTradeSettlement>
|
||||||
|
</rsm:SupplyChainTradeTransaction>
|
||||||
|
</rsm:CrossIndustryInvoice>
|
||||||
Reference in New Issue
Block a user