Merge branch 'master' into test-fuer-inputstream-validation
This commit is contained in:
2
.github/workflows/maven.yml
vendored
2
.github/workflows/maven.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- uses: actions/setup-java@v3 # the log states the default ~/.m2/toolchains.xml is being created pointing to the JDK
|
||||
with:
|
||||
distribution: 'adopt' # for latest JDKs use temurin https://blog.adoptopenjdk.net/2021/08/goodbye-adoptopenjdk-hello-adoptium/
|
||||
java-version: '8'
|
||||
java-version: '11'
|
||||
cache: 'maven' #cache/restore any dependencies to improve the workflow execution time
|
||||
- name: Build with Maven
|
||||
run: mvn -B package --file pom.xml
|
||||
106
History.md
106
History.md
@@ -1,3 +1,103 @@
|
||||
2.15.0
|
||||
=======
|
||||
2024-
|
||||
- 435 use invoiceimporter as common technical basis also for zugferdimporter
|
||||
- also import delivery address
|
||||
- 527 metrics raises errors
|
||||
- 517
|
||||
- 518 corrently validate more XRechnung versions
|
||||
- make document charges and allowances serializable
|
||||
|
||||
|
||||
2.14.2
|
||||
=======
|
||||
2024-10-14
|
||||
|
||||
- also parse BICs in InvoiceImporter not only IBANs
|
||||
- #509 CLI currently does not write a logfile
|
||||
- #505 crash after invoking ZUGFeRD2PullProvider
|
||||
- #506 Fix POM missing dependencies
|
||||
|
||||
2.14.1
|
||||
=======
|
||||
2024-10-06
|
||||
|
||||
- #481 also be able to convert XRechnung/UBL to PDF not only CII
|
||||
- #494 Quantity/Price Decimal Places
|
||||
- #391 Runden bei Negativwerten
|
||||
- #491/501 non terminating decimal expansion
|
||||
- upgraded en16931 cen schematron to 1.3.12
|
||||
- #499/500 PDF layout corrections
|
||||
|
||||
2.14.0
|
||||
=======
|
||||
2024-09-22
|
||||
|
||||
- #461 UBL import contacts
|
||||
- #463 add support for BT-33, i.e. Tradeparty description #463
|
||||
- #456 Provide a way to set uriUniversalCommunicationId on the TradeParty using JSON deserialization
|
||||
- #467 Fix test using wrong file
|
||||
- #468 Fix validator dependencies
|
||||
- #469 Enable EN16931 schema validation for XRechnung
|
||||
- #471 Fix LegalOrganisation schemeId
|
||||
- #473 Fix UnsupportedOperationException in buildNotes
|
||||
- #476 Add DesignatedProductClassification for SpecifiedTradeProduct
|
||||
- #482 Fix current validation errors
|
||||
- #423 can no longer add attachments via cli
|
||||
- #465 cli version should also be able to combine PDF/A-3 source
|
||||
- #487 update to zugferd 2.3.0
|
||||
- #472 Fix logging implementation missing in CLI
|
||||
- #479 Re-Formatted and re-organized POMs - Part 2
|
||||
|
||||
|
||||
2.13.0
|
||||
=======
|
||||
2024-08-28
|
||||
|
||||
- Item Attributes and Country of Origin missing on Product. #420
|
||||
- Avoid NullPointerException if dueDate is not set. #441
|
||||
- support reasoncodes #431
|
||||
- Enhance Charges/Allowances with reasonCode. #432
|
||||
- Fix build warnings from editing and building. #415
|
||||
- ZUGFeRDVisualizer.toPDF(): generate PDF/A-3b. #400
|
||||
- allow access to invoice attachments via ZUGFeRDInvoiceImporter zii.getFileAttachmentsPDF()
|
||||
and XML (zii.getFileAttachmentsXML)
|
||||
- No interface for required field CreditorReferenceID #436 and
|
||||
- X-Rechnung direct-debit missing mandatory field BT-90 #370. (langfr)
|
||||
- refactor(ZUGFeRDVisualizer): improve PDF visualization performance #438
|
||||
- product creation without description now possible empty description
|
||||
- filename of embedded file was not xrechnung.xml when using profile xrechnung #452
|
||||
- allow legalorganisation to have a tradingbusinessname #447
|
||||
- JSon deserialization does not work with BankDetails #455
|
||||
- Fix ClassCastException in CLI (Main.java). #451
|
||||
- changed additional references by line from String to List and implemented it on Item #454
|
||||
|
||||
2.12.0
|
||||
=======
|
||||
2024-07-20
|
||||
|
||||
- support pdf export/visualization to PDF, thanks to Heavenfighter #387 allow embedd fonts from jar-file for pdf creation #388
|
||||
- Fix #389: ClassCastException: ZUGFeRDExporterFromA3
|
||||
- jakarta support #372
|
||||
- Upgrade to PDFBox 3 #373
|
||||
- Requires Java 11
|
||||
- #397 Build succeeds but file is unusable on alpine/docker
|
||||
- #392 CLI: action combine: --ignorefileextension to ignore PDF/A input file errors dosen't work
|
||||
- for CLI combine, fx is now default
|
||||
- set profile to XR if XR is imported #395
|
||||
- Powershell compatibility: added --no-additional-attachments command line option for better batch processing:
|
||||
In cmd also --attachments "" worked but in powershell it was hard to figure out that one had to use --attachments '""'
|
||||
- Be able to validate XRechnung/UBL files #337
|
||||
- ph-schematron aktualisiert, logback zugungsten log4j entfernt #402
|
||||
- java.util Logging zugungsten log4j entfernt #407
|
||||
- ZF extended no longer requires deliverydate #411
|
||||
- Return all BankDetails from parsed CII xml. Closes #408.
|
||||
- ubl visualization: do not require ubl namespace prefix #416
|
||||
|
||||
2.11.0
|
||||
=======
|
||||
2024-05-22
|
||||
|
||||
- EN16931 validation 1.3.12 codelists v11 #357
|
||||
- Fonts removed #358
|
||||
- xrechnungimporter to read from filename, inputstream
|
||||
@@ -6,7 +106,11 @@
|
||||
- zugferdimporter to accept xml files
|
||||
- UBL importer to also parse contacts
|
||||
- https://github.com/ZUGFeRD/mustangproject/pull/369
|
||||
- upgrade ph-schematron from 6.3.3 to 8
|
||||
- support inputstreams https://github.com/ZUGFeRD/mustangproject/pull/379
|
||||
- #314 ZUGFeRDInvoiceImporter additional constructur
|
||||
- add XML cash discount write support (new class, previously only possible for XRechnung, not ZF Extended, using a manually encoded setPaymentTermDescription)
|
||||
- surrendered to XRechnung 3 compromises, e.g. no longer put gross amount if it does not deviate from net
|
||||
- be able to programmatically access validation messages https://github.com/ZUGFeRD/mustangproject/pull/382
|
||||
|
||||
2.10.0
|
||||
=======
|
||||
|
||||
@@ -3,25 +3,27 @@
|
||||
<parent>
|
||||
<groupId>org.mustangproject</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>2.10.1-SNAPSHOT</version>
|
||||
<version>2.15.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.mustangproject</groupId>
|
||||
<artifactId>Mustang-CLI</artifactId>
|
||||
<name>e-invoices commandline tool, allowing to create(embed), split and validate Factur-X/ZUGFeRD files. Validation should also work for XRechnung/CII. </name>
|
||||
<name>e-invoices commandline tool, allowing to create(embed), split and validate Factur-X/ZUGFeRD files. Validation
|
||||
should also work for XRechnung/CII.
|
||||
</name>
|
||||
<packaging>jar</packaging>
|
||||
<version>2.10.1-SNAPSHOT</version>
|
||||
<version>2.15.0-SNAPSHOT</version>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.compilerVersion>8</maven.compiler.compilerVersion>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<maven.compiler.compilerVersion>11</maven.compiler.compilerVersion>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.mustangproject</groupId>
|
||||
<artifactId>validator</artifactId>
|
||||
<version>2.10.1-SNAPSHOT</version>
|
||||
<version>2.15.0-SNAPSHOT</version>
|
||||
<!-- prototypes of new mustangproject versions can be installed by referring to them and installed to the local repo from a jar file with
|
||||
mvn install:install-file -Dfile=mustang-1.5.4-SNAPSHOT.jar -DgroupId=org.mustangproject.ZUGFeRD -DartifactId=mustang -Dversion=1.5.4 -Dpackaging=jar -DgeneratePom=true
|
||||
-->
|
||||
@@ -30,24 +32,44 @@
|
||||
<dependency><!-- apache commons cli to parse command line -->
|
||||
<groupId>commons-cli</groupId>
|
||||
<artifactId>commons-cli</artifactId>
|
||||
<version>1.5.0</version>
|
||||
<version>1.8.0</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- logging -->
|
||||
<dependency>
|
||||
<groupId>org.riversun</groupId>
|
||||
<artifactId>bigdoc</artifactId>
|
||||
<version>0.3.0</version>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.2.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-core</artifactId>
|
||||
<version>1.2.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- This library is needed so that logback stderr output is sent to, well, stderr, otherwise it lands in stdout -->
|
||||
<groupId>org.codehaus.janino</groupId>
|
||||
<artifactId>janino</artifactId>
|
||||
<version>3.1.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<version>2.0.12</version>
|
||||
</dependency>
|
||||
<!-- end of logging -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>5.9.1</version>
|
||||
<version>5.10.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
<version>5.9.1</version>
|
||||
<version>5.10.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- for directory validation: -->
|
||||
@@ -59,11 +81,18 @@
|
||||
<dependency>
|
||||
<groupId>org.xmlunit</groupId>
|
||||
<artifactId>xmlunit-assertj</artifactId>
|
||||
<version>2.9.1</version>
|
||||
<version>2.10.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- /directory validation: -->
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.xml.bind</groupId>
|
||||
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||
<version>4.0.2</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
<build>
|
||||
<pluginManagement>
|
||||
@@ -90,33 +119,13 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.4.2</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
|
||||
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
|
||||
</manifest>
|
||||
<manifestSections>
|
||||
<manifestSection>
|
||||
<name>FreeSans.ttf</name>
|
||||
<manifestEntries>
|
||||
<Content-Type>font/ttf</Content-Type>
|
||||
</manifestEntries>
|
||||
</manifestSection>
|
||||
<manifestSection>
|
||||
<name>FreeSerif.ttf</name>
|
||||
<manifestEntries>
|
||||
<Content-Type>application/x-font</Content-Type>
|
||||
</manifestEntries>
|
||||
</manifestSection>
|
||||
<manifestSection>
|
||||
<name>Times-Bold.ttf</name>
|
||||
<manifestEntries>
|
||||
<Content-Type>application/x-font</Content-Type>
|
||||
</manifestEntries>
|
||||
</manifestSection>
|
||||
</manifestSections>
|
||||
|
||||
</archive>
|
||||
</configuration>
|
||||
@@ -138,14 +147,14 @@
|
||||
<!-- http://stackoverflow.com/questions/574594/how-can-i-create-an-executable-jar-with-dependencies-using-maven
|
||||
mvn clean compile assembly:single -->
|
||||
<!-- or whatever version you use -->
|
||||
<source>8</source>
|
||||
<target>8</target>
|
||||
<source>11</source>
|
||||
<target>11</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>2.4.3</version>
|
||||
<version>3.5.3</version>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
@@ -227,7 +236,7 @@
|
||||
<configuration>
|
||||
<toolchains>
|
||||
<jdk>
|
||||
<version>8</version>
|
||||
<version>11</version>
|
||||
<vendor>adopt</vendor>
|
||||
</jdk>
|
||||
</toolchains>
|
||||
|
||||
@@ -36,6 +36,7 @@ import java.util.ArrayList;
|
||||
|
||||
public class Main {
|
||||
private static org.slf4j.Logger LOGGER; // log output
|
||||
|
||||
private static void printUsage() {
|
||||
System.err.println(getUsage());
|
||||
}
|
||||
@@ -70,6 +71,7 @@ public class Main {
|
||||
+ " 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"
|
||||
+ " [--attachments <filenames>]: list of file attachments (passing a single empty file name prevents prompting)\n"
|
||||
+ " [--no-additional-attachments]: prevent prompting for attachments\n"
|
||||
+ " --action ubl convert UN/CEFACT 2016b CII XML to UBL XML\n"
|
||||
+ " [--source <filename>]: set input XML file\n"
|
||||
+ " [--out <filename>]: set output XML file\n"
|
||||
@@ -330,31 +332,35 @@ public class Main {
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
CommandLine cmd;
|
||||
CommandLineParser parser = new BasicParser();
|
||||
CommandLineParser parser = new DefaultParser();
|
||||
|
||||
// create Options object
|
||||
Options options = new Options();
|
||||
|
||||
options.addOption(new Option("h", "help", false, "display usage"));
|
||||
options.addOption(new Option("a", "action", true, "which action to perform"));
|
||||
options.addOption(new Option("f", "format", true, "which format to output"));
|
||||
options.addOption(new Option("", "version", true, "which version of the standard to use"));
|
||||
options.addOption(new Option("", "profile", true, "which profile of the standard to use"));
|
||||
Option attachmentOpt = new Option("", "attachments", true, "File attachments");
|
||||
options.addOption(new Option("version", "version", true, "which version of the standard to use"));
|
||||
options.addOption(new Option("profile", "profile", true, "which profile of the standard to use"));
|
||||
options.addOption(new Option("no-additional-attachments", "no-additional-attachments", false, "prevent prompting for attachments"));
|
||||
Option attachmentOpt = new Option("attachments", "attachments", true, "File attachments");
|
||||
attachmentOpt.setValueSeparator(',');
|
||||
attachmentOpt.setArgs(Option.UNLIMITED_VALUES);
|
||||
|
||||
options.addOption(attachmentOpt);
|
||||
options.addOption(new Option("", "source",true, "which source file to use"));
|
||||
options.addOption(new Option("", "source-xml",true, "which source file to use"));
|
||||
options.addOption(new Option("", "language",true, "output language (en, de or fr)"));
|
||||
options.addOption(new Option("", "out",true, "which output file to write to"));
|
||||
options.addOption(new Option("", "no-notices",false, "suppress non-fatal errors"));
|
||||
options.addOption(new Option("", "logAppend",true, "freeform text to be appended to log messages"));
|
||||
options.addOption(new Option("", "disable-file-logging", false, "suppress logging to file"));
|
||||
options.addOption(new Option("source", "source", true, "which source file to use"));
|
||||
options.addOption(new Option("source-xml", "source-xml", true, "which source file to use"));
|
||||
options.addOption(new Option("language", "language", true, "output language (en, de or fr)"));
|
||||
options.addOption(new Option("out", "out", true, "which output file to write to"));
|
||||
options.addOption(new Option("no-notices", "no-notices", false, "suppress non-fatal errors"));
|
||||
options.addOption(new Option("logAppend", "logAppend", true, "freeform text to be appended to log messages"));
|
||||
options.addOption(new Option("disable-file-logging", "disable-file-logging", false, "suppress logging to file"));
|
||||
options.addOption(new Option("d", "directory", true, "which directory to operate on"));
|
||||
options.addOption(new Option("i", "ignorefileextension", false, "ignore non-matching file extensions"));
|
||||
options.addOption(new Option("l", "listfromstdin", false, "take list of files from commandline"));
|
||||
|
||||
boolean optionsRecognized = false;
|
||||
String action = "";
|
||||
|
||||
Boolean disableFileLogging = false;
|
||||
try {
|
||||
cmd = parser.parse(options, args);
|
||||
@@ -364,6 +370,7 @@ public class Main {
|
||||
String directoryName = cmd.getOptionValue("directory");
|
||||
Boolean filesFromStdIn = cmd.hasOption("listfromstdin");//((Number)cmdLine.getParsedOptionValue("integer-option")).intValue();
|
||||
Boolean ignoreFileExt = cmd.hasOption("ignorefileextension");
|
||||
Boolean noAttachments = cmd.hasOption("no-additional-attachments");
|
||||
Boolean helpRequested = cmd.hasOption("help") || ((action != null) && (action.equals("help")));
|
||||
disableFileLogging = cmd.hasOption("disable-file-logging");
|
||||
|
||||
@@ -380,8 +387,6 @@ public class Main {
|
||||
String[] attachmentFilenames = cmd.hasOption("attachments") ? cmd.getOptionValues("attachments") : null;
|
||||
|
||||
|
||||
|
||||
|
||||
ArrayList<FileAttachment> attachments = new ArrayList<>();
|
||||
/*
|
||||
setting system property to disable FILE appender and
|
||||
@@ -401,7 +406,7 @@ public class Main {
|
||||
performMetrics(directoryName, filesFromStdIn, ignoreFileExt);
|
||||
optionsRecognized = true;
|
||||
} else if ((action != null) && (action.equals("combine"))) {
|
||||
performCombine(sourceName, sourceXMLName, outName, format, zugferdVersion, zugferdProfile, ignoreFileExt, attachmentFilenames, attachments);
|
||||
performCombine(sourceName, sourceXMLName, outName, format, zugferdVersion, zugferdProfile, ignoreFileExt, attachmentFilenames, attachments, noAttachments);
|
||||
optionsRecognized = true;
|
||||
} else if ((action != null) && (action.equals("extract"))) {
|
||||
performExtract(sourceName, outName);
|
||||
@@ -422,7 +427,7 @@ public class Main {
|
||||
performUBL(sourceName, outName);
|
||||
optionsRecognized = true;
|
||||
} else if ((action != null) && (action.equals("validate"))) {
|
||||
optionsRecognized = performValidate(sourceName, noNotices != null && noNotices, cmd.getOptionValue("logAppend"));
|
||||
optionsRecognized = performValidate(sourceName, noNotices, cmd.getOptionValue("logAppend"));
|
||||
} else if ((action != null) && (action.equals("validateExpectValid"))) {
|
||||
optionsRecognized = performValidateExpect(true, directoryName);
|
||||
} else if ((action != null) && (action.equals("validateExpectInvalid"))) {
|
||||
@@ -439,7 +444,11 @@ public class Main {
|
||||
System.exit(2);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (LOGGER != null) {
|
||||
LOGGER.error(e.getMessage(), e);
|
||||
} else {
|
||||
System.err.println(e.getMessage());
|
||||
}
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
@@ -510,11 +519,12 @@ public class Main {
|
||||
ensureFileNotExists(outName);
|
||||
|
||||
// All params are good! continue...
|
||||
ZUGFeRDExporterFromA1 ze = new ZUGFeRDExporterFromA1().convertOnly().load(pdfName);
|
||||
|
||||
try (ZUGFeRDExporterFromA1 ze = new ZUGFeRDExporterFromA1()) {
|
||||
ze.convertOnly().load(pdfName);
|
||||
ze.export(outName);
|
||||
System.out.println("Written to " + outName);
|
||||
}
|
||||
}
|
||||
|
||||
private static void performExtract(String pdfName, String xmlName) throws IOException {
|
||||
// Get params from user if not already defined
|
||||
@@ -546,7 +556,7 @@ public class Main {
|
||||
}
|
||||
|
||||
private static void performCombine(String pdfName, String xmlName, String outName, String format, String zfVersion,
|
||||
String zfProfile, Boolean ignoreInputErrors, String[] attachmentFilenames, ArrayList<FileAttachment> attachments) throws Exception {
|
||||
String zfProfile, Boolean ignoreInputErrors, String[] attachmentFilenames, ArrayList<FileAttachment> attachments, Boolean noAttachments) throws Exception {
|
||||
/*
|
||||
* ZUGFeRDExporter ze= new ZUGFeRDExporterFromA1Factory()
|
||||
* .setProducer("toecount") .setCreator(System.getProperty("user.name"))
|
||||
@@ -576,13 +586,15 @@ public class Main {
|
||||
|
||||
if (attachmentFilenames == null) {
|
||||
byte attachmentContents[] = null;
|
||||
String attachmentFilename, attachmentMime, attachmentDescription;
|
||||
String attachmentFilename, attachmentMime;
|
||||
if (!noAttachments) {
|
||||
attachmentFilename = getFilenameFromUser("Additional file attachments filename (empty for none)", "", "pdf", true, false);
|
||||
if (attachmentFilename.length() != 0) {
|
||||
attachmentContents = Files.readAllBytes(Paths.get(attachmentFilename));
|
||||
attachmentMime = Files.probeContentType(Paths.get(attachmentFilename));
|
||||
attachments.add(new FileAttachment(attachmentFilename, attachmentMime, "Data", attachmentContents));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < attachmentFilenames.length; i++) {
|
||||
String attachmentFilename = attachmentFilenames[i];
|
||||
@@ -597,7 +609,7 @@ public class Main {
|
||||
|
||||
if (format == null) {
|
||||
try {
|
||||
format = getStringFromUser("Format (fx=Factur-X, zf=ZUGFeRD, ox=Order-X, da=Cross Industry Despatch Advice)", "zf", "fx|zf|ox|da");
|
||||
format = getStringFromUser("Format (fx=Factur-X, zf=ZUGFeRD, ox=Order-X, da=Cross Industry Despatch Advice)", "fx", "fx|zf|ox|da");
|
||||
} catch (Exception e) {
|
||||
LOGGER.error(e.getMessage(), e);
|
||||
}
|
||||
@@ -607,7 +619,7 @@ public class Main {
|
||||
|
||||
if (zfVersion == null) {
|
||||
try {
|
||||
zfVersion = getStringFromUser("Version (1 or 2)", Integer.toString(ZUGFeRDExporterFromA3.DefaultZUGFeRDVersion), "1|2");
|
||||
zfVersion = getStringFromUser("Version (1 or 2)", "1", "1|2");//fx default version is 1
|
||||
} catch (Exception e) {
|
||||
LOGGER.error(e.getMessage(), e);
|
||||
}
|
||||
@@ -691,53 +703,49 @@ public class Main {
|
||||
IZUGFeRDExporter ze = null;
|
||||
// All params are good! continue...
|
||||
if (format.equals("ox")) {
|
||||
ze = new OXExporterFromA1().setProducer("Mustang-cli")
|
||||
.setZUGFeRDVersion(zfIntVersion)
|
||||
.setCreator(System.getProperty("user.name")).setProfile(zfConformanceLevelProfile);
|
||||
ze = new OXExporterFromA1();
|
||||
if (ignoreInputErrors) {
|
||||
((OXExporterFromA1) ze).ignorePDFAErrors();
|
||||
}
|
||||
|
||||
} else if (format.equals("da")) {
|
||||
ze = new DXExporterFromA1().setProducer("Mustang-cli")
|
||||
.setZUGFeRDVersion(zfIntVersion)
|
||||
.setCreator(System.getProperty("user.name")).setProfile(zfConformanceLevelProfile);
|
||||
|
||||
ze = new DXExporterFromA1();
|
||||
if (ignoreInputErrors) {
|
||||
((DXExporterFromA1) ze).ignorePDFAErrors();
|
||||
}
|
||||
|
||||
} else {
|
||||
if (format.equals("fx")) {
|
||||
zfIntVersion = 2;// actually we are talking of generation, not version
|
||||
// so even if someone correctly requested factur-x 1 internally we call it zugferd 2 :-(
|
||||
}
|
||||
ze = new ZUGFeRDExporterFromPDFA().load(pdfName);
|
||||
ze = new ZUGFeRDExporterFromPDFA();
|
||||
if (ignoreInputErrors) {
|
||||
((ZUGFeRDExporterFromPDFA) ze).ignorePDFAErrors();
|
||||
}
|
||||
}
|
||||
|
||||
ze.load(pdfName);
|
||||
ze.setProducer("Mustang-cli")
|
||||
.setZUGFeRDVersion(zfIntVersion)
|
||||
.setCreator(System.getProperty("user.name")).setProfile(zfConformanceLevelProfile);
|
||||
if (ignoreInputErrors) {
|
||||
((ZUGFeRDExporterFromA1) ze).ignorePDFAErrors();
|
||||
}
|
||||
}
|
||||
for (FileAttachment attachment : attachments) {
|
||||
((ZUGFeRDExporterFromA3) ze).attachFile(attachment.getFilename(), attachment.getData(), attachment.getMimetype(), attachment.getRelation());
|
||||
}
|
||||
|
||||
// ze = ze.load(pdfName);
|
||||
|
||||
if (format.equals("zf")) {
|
||||
ze.disableFacturX();
|
||||
}
|
||||
|
||||
|
||||
for (FileAttachment attachment : attachments) {
|
||||
ze.attachFile(attachment.getFilename(), attachment.getData(), attachment.getMimetype(), attachment.getRelation());
|
||||
}
|
||||
|
||||
ze.setXML(Files.readAllBytes(Paths.get(xmlName)));
|
||||
|
||||
ze.export(outName);
|
||||
System.out.println("Written to " + outName);
|
||||
|
||||
} catch (Exception e) {
|
||||
if (LOGGER != null) {
|
||||
LOGGER.error(e.getMessage(), e);
|
||||
}
|
||||
System.err.println(e.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -837,14 +845,9 @@ public class Main {
|
||||
} else {
|
||||
zvi.toPDF(sourceName, outName);
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
LOGGER.error(e.getMessage(), e);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
LOGGER.error(e.getMessage(), e);
|
||||
} catch (TransformerException e) {
|
||||
LOGGER.error(e.getMessage(), e);
|
||||
} catch (IOException e) {
|
||||
} catch (Exception e) {
|
||||
LOGGER.error(e.getMessage(), e);
|
||||
System.err.println(e.getMessage());
|
||||
}
|
||||
System.out.println("Written to " + outName);
|
||||
|
||||
@@ -871,11 +874,8 @@ public class Main {
|
||||
* @throws Exception e.g. if the specified resource does not exist at the specified location
|
||||
*/
|
||||
static public String ExportResource(String resourceName) throws Exception {
|
||||
InputStream stream = null;
|
||||
OutputStream resStreamOut = null;
|
||||
String jarFolder;
|
||||
try {
|
||||
stream = Main.class.getResourceAsStream(resourceName);//note that each / is a directory down in the "jar tree" been the jar the root of the tree
|
||||
try (InputStream stream = Main.class.getResourceAsStream(resourceName)) {//note that each / is a directory down in the "jar tree" been the jar the root of the tree
|
||||
if (stream == null) {
|
||||
throw new Exception("Cannot get resource \"" + resourceName + "\" from Jar file.");
|
||||
}
|
||||
@@ -883,15 +883,11 @@ public class Main {
|
||||
int readBytes;
|
||||
byte[] buffer = new byte[4096];
|
||||
jarFolder = System.getProperty("user.dir");
|
||||
resStreamOut = new FileOutputStream(jarFolder + resourceName);
|
||||
try (FileOutputStream resStreamOut = new FileOutputStream(jarFolder + resourceName)) {
|
||||
while ((readBytes = stream.read(buffer)) > 0) {
|
||||
resStreamOut.write(buffer, 0, readBytes);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw ex;
|
||||
} finally {
|
||||
stream.close();
|
||||
resStreamOut.close();
|
||||
}
|
||||
}
|
||||
|
||||
return jarFolder + resourceName;
|
||||
@@ -913,7 +909,8 @@ public class Main {
|
||||
if (fileName == null)
|
||||
return false;
|
||||
File f = new File(fileName);
|
||||
return f.exists();
|
||||
// "exists" also returns true for directories
|
||||
return f.isFile();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,9 +14,6 @@ import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.xmlunit.builder.Input;
|
||||
import org.xmlunit.xpath.JAXPXPathEngine;
|
||||
import org.xmlunit.xpath.XPathEngine;
|
||||
import org.mustangproject.validator.ZUGFeRDValidator;
|
||||
|
||||
import static org.xmlunit.assertj.XmlAssert.assertThat;
|
||||
@@ -54,7 +51,6 @@ public class ValidatorFileWalker
|
||||
}
|
||||
if ((attr!=null)&&(attr.isRegularFile())) {
|
||||
if (matcher.matches(file.getFileName())) {
|
||||
boolean thisResultValid=true;
|
||||
String thisResultString=" valid";
|
||||
try {
|
||||
assertThat(zul.validate(file.toAbsolutePath().toString())).valueByXPath("/validation/summary/@status")
|
||||
@@ -62,7 +58,6 @@ public class ValidatorFileWalker
|
||||
.isEqualTo(expectedString);
|
||||
|
||||
} catch (AssertionError ae) {
|
||||
thisResultValid=false;
|
||||
thisResultString="invalid";
|
||||
allValid=false;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package org.mustangproject.commandline;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -10,10 +8,35 @@ import java.nio.file.Paths;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class CliIT {
|
||||
|
||||
public static File getResourceAsFile(String resourcePath) {
|
||||
try {
|
||||
InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(resourcePath);
|
||||
if (in == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
File tempFile = File.createTempFile(String.valueOf(in.hashCode()), ".tmp");
|
||||
tempFile.deleteOnExit();
|
||||
|
||||
try (FileOutputStream out = new FileOutputStream(tempFile)) {
|
||||
// copy stream
|
||||
byte[] buffer = new byte[1024];
|
||||
int bytesRead;
|
||||
while ((bytesRead = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
return tempFile;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCii2Ubl() throws Exception {
|
||||
Path output = Paths.get("target/ubl.xml");
|
||||
@@ -43,4 +66,17 @@ public class CliIT {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMetric() {
|
||||
StatRun sr = new StatRun();
|
||||
File tempFile = getResourceAsFile("corrupt-factur-x-waytoosmall.pdf");
|
||||
|
||||
FileChecker fc = new FileChecker(tempFile.getAbsolutePath(), sr);
|
||||
|
||||
fc.checkForZUGFeRD();
|
||||
System.out.print(fc.getOutputLine());
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
%PDF-1.4
|
||||
%ェォャュ
|
||||
@@ -34,14 +34,11 @@ If you set up a Maven project, you can reference the mustang artifact like this:
|
||||
<dependency>
|
||||
<groupId>org.mustangproject</groupId>
|
||||
<artifactId>library</artifactId>
|
||||
<version>2.11.0</version>
|
||||
<version>2.14.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Further docs on how to develop **with** mustang:
|
||||
|
||||
- [ZugferdDev.en.pdf](https://github.com/ZUGFeRD/mustangproject/blob/master/doc/ZugferdDev.en.pdf?raw=true): The English mustang user documentation.
|
||||
- [ZugferdDev.de.pdf](https://github.com/ZUGFeRD/mustangproject/blob/master/doc/ZugferdDev.de.pdf?raw=true): The German mustang user documentation.
|
||||
- [Usage examples](https://www.mustangproject.org/use/): Read and write electronic invoices.
|
||||
- [Mustang classes](https://www.mustangproject.org/invoice-class/): Using the mustang classes.
|
||||
|
||||
|
||||
@@ -20,13 +20,13 @@ Additionally, the Mustang validator now also supports XRechnung 2, can now also
|
||||
|
||||
|
||||
### Use on command line
|
||||
`java -jar Mustang-CLI-2.0.0-alpha3.jar --action=combine` embeds a XML into a PDF (A-1) file and exports as PDF/A-3
|
||||
`java -jar Mustang-CLI-2.14.0.jar --action=combine` embeds a XML into a PDF (A-1) file and exports as PDF/A-3
|
||||
|
||||
`java -jar mustang-cli.jar --action=extract` extracts XML from a ZUGFeRD file and
|
||||
`java -jar Mustang-CLI-2.14.0.jar --action=extract` extracts XML from a ZUGFeRD file and
|
||||
|
||||
`java -jar mustang-cli.jar --action=validate` validates XML or PDF files.
|
||||
`java -jar Mustang-CLI-2.14.0.jar --action=validate` validates XML or PDF files.
|
||||
|
||||
`java -jar mustang-cli.jar --help` still outputs the parameters which can be used
|
||||
`java -jar Mustang-CLI-2.14.0.jar --help` still outputs the parameters which can be used
|
||||
to for non-interactive (i.e., batch) processing.
|
||||
|
||||
|
||||
@@ -38,11 +38,12 @@ from `-f` (ZUV) to the usual `--source`.
|
||||
We're now on maven central, please remove the old github repository. Additionally, the following changed
|
||||
|
||||
| What | old value | new value |
|
||||
|---|---|---|
|
||||
|---|---|--------------------|
|
||||
| Group id | org.mustangproject.zugferd | org.mustangproject |
|
||||
| Artifact ID | mustang | library |
|
||||
| Version | 1.7.8 | 2.0.0-alpha3 |
|
||||
| Version | 1.7.8 | 2.14.0 |
|
||||
|
||||
And please use the shaded classifier.
|
||||
If you want you can also embed the validator in your software using validator
|
||||
as artifact ID. "validator" includes the library functionality but is >20 MB
|
||||
bigger due to its dependencies.
|
||||
@@ -200,7 +201,7 @@ read/write ZUGFeRD-invoices like the (smaller) library module.
|
||||
<dependency>
|
||||
<groupId>org.mustangproject</groupId>
|
||||
<artifactId>validator</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<version>2.14.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
@@ -1,373 +0,0 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
@@ -1,26 +0,0 @@
|
||||
= Demo Documentation Component B
|
||||
// URLs
|
||||
:url-project: https://antora.org
|
||||
:url-docs: https://docs.antora.org
|
||||
:url-org: https://gitlab.com/antora
|
||||
:url-group: {url-org}/demo
|
||||
:url-demo-site: https://antora.gitlab.io/demo/docs-site
|
||||
:url-site-readme: {url-group}/docs-site/blob/master/README.adoc
|
||||
:url-opendevise: https://opendevise.com
|
||||
|
||||
Component B is an example of a documentation component.
|
||||
It is used by the {url-demo-site}[Antora Demo site].
|
||||
The playbook and instructions for the Demo site are located in the {url-site-readme}[Demo site playbook project].
|
||||
|
||||
== Resources
|
||||
|
||||
* {url-project}[Antora Project Site]
|
||||
* {url-docs}[Antora Docs]
|
||||
* {url-org}[Antora Repositories]
|
||||
|
||||
== Copyright and License
|
||||
|
||||
Copyright (C) 2017-present by OpenDevise Inc. and the individual contributors to Antora.
|
||||
|
||||
Use of this software is granted under the terms of the https://www.mozilla.org/en-US/MPL/2.0/[Mozilla Public License Version 2.0] (MPL-2.0).
|
||||
See link:LICENSE[] to find the full license text.
|
||||
@@ -1,21 +0,0 @@
|
||||
# This file represents "software".
|
||||
# This file helps show that documentation files can be in the same repository as software.
|
||||
|
||||
rescue => ex
|
||||
begin
|
||||
context = %(asciidoctor: FAILED: #{attrs['docfile'] || '<stdin>'}: Failed to load AsciiDoc document)
|
||||
if ex.respond_to? :exception
|
||||
# The original message must be explicitely preserved when wrapping a Ruby exception
|
||||
wrapped_ex = ex.exception %(#{context} - #{ex.message})
|
||||
# JRuby automatically sets backtrace, but not MRI
|
||||
wrapped_ex.set_backtrace ex.backtrace
|
||||
else
|
||||
# Likely a Java exception class
|
||||
wrapped_ex = ex.class.new context, ex
|
||||
wrapped_ex.stack_trace = ex.stack_trace
|
||||
end
|
||||
rescue
|
||||
wrapped_ex = ex
|
||||
end
|
||||
raise wrapped_ex
|
||||
end
|
||||
@@ -1,7 +0,0 @@
|
||||
name: mustang
|
||||
title: Mustang Library
|
||||
version: 2.6.1
|
||||
prerelease: -beta.1
|
||||
nav:
|
||||
- modules/ROOT/nav.adoc
|
||||
- modules/module-one/nav.adoc
|
||||
@@ -1 +0,0 @@
|
||||
* xref:index.adoc[]
|
||||
@@ -1,33 +0,0 @@
|
||||
= Welcome to Component B!
|
||||
:navtitle: Welcome
|
||||
|
||||
This is the automatic start page for version *{page-component-display-version}* of Component B.
|
||||
|
||||
Component B is one of two documentation components in the Antora Demo.
|
||||
The other docs component, Component A, can be accessed using the component selector menu (aka component drawer) at the bottom of the menu on the left side of the page.
|
||||
|
||||
== Page source
|
||||
|
||||
This page is sourced from the AsciiDoc file named [.path]_index.adoc_ that is located at [.path]_demo-component-b/docs/modules/ROOT/pages_.
|
||||
|
||||
=== Why is this the home page of Component B?
|
||||
|
||||
This page is automatically used as the start page of Component B because it is stored in the ROOT module and named [.path]_index.adoc_.
|
||||
|
||||
=== Why is this the home page of the Antora Demo site?
|
||||
|
||||
This page is used as the home page for the Antora Demo because it is assigned as the site `start_page` in the Demo site's playbook file [.path]_site.yml_.
|
||||
|
||||
== Cross reference syntax to target this page
|
||||
|
||||
To create a cross reference *to this page from another page in the ROOT module of Component B*, the xref syntax would be `\xref:index.adoc[]`.
|
||||
|
||||
To create a cross reference *to this page from a page in Module One of Component B*, the xref syntax would be `\xref:ROOT:index.adoc[]`.
|
||||
|
||||
=== Always target the latest version of this page
|
||||
|
||||
To create a cross reference *to the latest version of this page from a page in Component A*, the xref syntax would be `\xref:component-b::index.adoc[]`.
|
||||
|
||||
=== Target a specific version of this page
|
||||
|
||||
To create a cross reference *to version 1.0 of this page from a page in Component A*, the xref syntax would be `\xref:1.0@component-b::index.adoc[]`.
|
||||
@@ -1 +0,0 @@
|
||||
* xref:overview.adoc[]
|
||||
@@ -1,21 +0,0 @@
|
||||
= Module One Overview
|
||||
|
||||
This is version *{page-component-display-version}* of Module One in Component B.
|
||||
|
||||
== Page source
|
||||
|
||||
This page is sourced from [.path]_demo-component-b/docs/modules/module-one/pages/overview.adoc_.
|
||||
|
||||
== Cross reference syntax to target this page
|
||||
|
||||
To create a cross reference *to this page from another page in Module One*, the xref syntax would be `\xref:overview.adoc[]`.
|
||||
|
||||
To create a cross reference *to this page from a page in the ROOT module of Component B*, the xref syntax would be `\xref:module-one:overview.adoc[]`.
|
||||
|
||||
=== Always target the latest version of this page
|
||||
|
||||
To create a cross reference *to the latest version of this page from a page in Component A*, the xref syntax would be `\xref:component-b:module-one:overview.adoc[]`.
|
||||
|
||||
=== Target a specific version of this page
|
||||
|
||||
To create a cross reference *to version 1.0 of this page from a page in Component A*, the xref syntax would be `\xref:1.0@component-b:module-one:overview.adoc[]`.
|
||||
@@ -1,11 +1,10 @@
|
||||
|
||||
## General approach
|
||||
## Typical process
|
||||
|
||||
1. build
|
||||
|
||||
To check if the necessary tools are there, the build is in a stable state and works on your platform, e.g. download and extract https://github.com/ZUGFeRD/mustangproject/archive/master.zip and run ./mvnw clean package
|
||||
|
||||
Mvnw is a maven wrapper which will download maven.Maven is the dependency management tool which will download all libraries, their dependencies, and build the whole thing.
|
||||
Mvnw is a maven wrapper which will download maven.Maven is the dependency management tool which will download all libraries, their dependencies, and build the whole thing.
|
||||
|
||||
You will need a Java JDK, e.g. https://www.azul.com/downloads/zulu-community/?architecture=x86-64-bit&package=jdk
|
||||
@@ -29,6 +28,18 @@ If you do a pull request, please do a feature branch, e.g. if you are working on
|
||||
Most of mustang is a library, adding (autmated junit) test cases is often not only the most sustainable but also the fastest way to see if new/changed functionality works. If something is changed so that old test cases break on purpose please do not just remove them but take the time to fix the test cases
|
||||
|
||||
|
||||
## Typical workflow
|
||||
|
||||
If e.g. new elements or attributes are added, they are often added
|
||||
* in the object so that a developer can use them
|
||||
* in the interface so that a old fashioned developer could use them as well
|
||||
* in the pullprovider so that it actually finds it's way into the XML
|
||||
* in at least one test (the ...edgeTest are supposed to handle edge cases, ~all bells and whistles, maybe it fits there), after the test has been run this should at least once be
|
||||
* validated. If that works one can start implementing the
|
||||
* reading part (along with tests), then it needs to be
|
||||
* documented e.g. on the homepage and
|
||||
* communicated, at the very least by mentioning it in the history.md
|
||||
|
||||
## Architecture
|
||||
|
||||
Mustang contains a library to read/write e-invoices,
|
||||
@@ -48,7 +59,7 @@ to validate the XML part of the invoices.
|
||||
|
||||
## New build
|
||||
|
||||
Target platform is java 1.8
|
||||
Target platform is java 1.17
|
||||
|
||||
## Build
|
||||
|
||||
@@ -115,14 +126,10 @@ The whole settings.xml then looks e.g. like this
|
||||
<offline/>
|
||||
<pluginGroups/>
|
||||
<servers>
|
||||
<server>
|
||||
<id>github</id>
|
||||
<password>TOKEN</password>
|
||||
</server>
|
||||
<server>
|
||||
<id>ossrh</id>
|
||||
<username>jstaerk</username>
|
||||
<password>JIRA-PASSWORD</password>
|
||||
<username>SONATYPE TOKEN USER</username>
|
||||
<password>SONATYPE TOKEN PASSWORD</password>
|
||||
</server>
|
||||
|
||||
</servers>
|
||||
@@ -150,7 +157,7 @@ Sign in in GitHub and click on the profile picture -> Settings. Now just generat
|
||||

|
||||
The Token-ID is the password.
|
||||
|
||||
In .m2 also need a toolchains.xml which defines a Sun JDK 1.8 target like the following:
|
||||
In .m2 also need a toolchains.xml which defines a JDK 1.11 target like the following:
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<toolchains>
|
||||
@@ -158,11 +165,11 @@ In .m2 also need a toolchains.xml which defines a Sun JDK 1.8 target like the fo
|
||||
<toolchain>
|
||||
<type>jdk</type>
|
||||
<provides>
|
||||
<version>8</version>
|
||||
<version>11</version>
|
||||
<vendor>adopt</vendor>
|
||||
</provides>
|
||||
<configuration>
|
||||
<jdkHome>C:\Program Files\Eclipse Adoptium\jdk-8.0.352.8-hotspot</jdkHome>
|
||||
<jdkHome>C:\Program Files\Eclipse Adoptium\jdk-11.0.23.9-hotspot</jdkHome>
|
||||
</configuration>
|
||||
</toolchain>
|
||||
|
||||
@@ -177,11 +184,11 @@ maybe not yet even existing new release version:
|
||||
|
||||
```
|
||||
cd validator/target
|
||||
mvn install:install-file -Dfile=validator-2.5.5-SNAPSHOT-shaded.jar -DgroupId=org.mustangproject -DartifactId=validator -Dversion=2.5.5 -Dpackaging=jar -DgeneratePom=true
|
||||
mvn install:install-file -Dfile=validator-2.12.0-SNAPSHOT-shaded.jar -Dclassifier=shaded -DgroupId=org.mustangproject -DartifactId=validator -Dversion=2.12.0 -Dpackaging=jar -DgeneratePom=true
|
||||
```
|
||||
In gradle you can use something like
|
||||
```
|
||||
implementation files('libs/validator-2.5.6-shaded.jar')
|
||||
implementation files('libs/validator-2.12.0-shaded.jar')
|
||||
```
|
||||
|
||||
|
||||
@@ -222,3 +229,47 @@ extract, rename xsl file to xslt, move to new version dir and add a if where the
|
||||
* freshcode.club
|
||||
* Submit on openpr.de/.com, https://www.einpresswire.com/
|
||||
|
||||
## Tips&Inspriration
|
||||
|
||||
|
||||
NodeList from a XPath https://github.com/ZUGFeRD/mustangproject/pull/476/files
|
||||
```
|
||||
xpr = xpath.compile("//*[local-name()=\"PrepaidAmount\"]");
|
||||
NodeList prepaidNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
nodeMap.getNode("GlobalID").ifPresent(idNode -> {
|
||||
if (idNode.hasAttributes()
|
||||
&& idNode.getAttributes().getNamedItem("schemeID") != null) {
|
||||
globalId = new SchemedID()
|
||||
.setScheme(idNode.getAttributes().getNamedItem("schemeID").getNodeValue())
|
||||
.setId(idNode.getTextContent());
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
nodeMap.getAsNodeMap and nodeMap.getAsString
|
||||
```
|
||||
|
||||
nodeMap.getAsString("SellerAssignedID").ifPresent(this::setSellerAssignedID);
|
||||
nodeMap.getAsString("BuyerAssignedID").ifPresent(this::setBuyerAssignedID);
|
||||
nodeMap.getAsString("Name").ifPresent(this::setName);
|
||||
nodeMap.getAsString("Description").ifPresent(this::setDescription);
|
||||
|
||||
nodeMap.getAsNodeMap("ApplicableProductCharacteristic").ifPresent(apcNodes -> {
|
||||
String key = apcNodes.getAsStringOrNull("Description");
|
||||
String value = apcNodes.getAsStringOrNull("Value");
|
||||
if (key != null && value != null) {
|
||||
if (attributes == null) {
|
||||
attributes = new HashMap<>();
|
||||
}
|
||||
attributes.put(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
nodeMap.getAsNodeMap("DesignatedProductClassification").ifPresent(dpcNodes -> {
|
||||
String className = dpcNodes.getAsStringOrNull("ClassName");
|
||||
dpcNodes.getNode("ClassCode").map(ClassCode::fromNode).ifPresent(classCode ->
|
||||
classifications.add(new DesignatedProductClassification(classCode, className)));
|
||||
});
|
||||
|
||||
nodeMap.getAsString("OriginTradeCounty").ifPresent(this::setCountryOfOrigin);
|
||||
```
|
||||
|
||||
183
doc/mustang-validation.xsd
Normal file
183
doc/mustang-validation.xsd
Normal file
@@ -0,0 +1,183 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
|
||||
<xs:element name="validation">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="pdf"/>
|
||||
<xs:element ref="messages"/>
|
||||
<xs:element ref="summary"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="datetime" use="required"/>
|
||||
<xs:attribute name="filename" use="required" type="xs:NCName"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="pdf">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="report"/>
|
||||
<xs:element ref="info"/>
|
||||
<xs:element ref="messages"/>
|
||||
<xs:element ref="summary"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="report">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="buildInformation"/>
|
||||
<xs:element ref="jobs"/>
|
||||
<xs:element ref="batchSummary"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="buildInformation">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element maxOccurs="unbounded" ref="releaseDetails"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="releaseDetails">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="buildDate" use="required" type="xs:dateTime"/>
|
||||
<xs:attribute name="id" use="required" type="xs:NCName"/>
|
||||
<xs:attribute name="version" use="required" type="xs:NMTOKEN"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="jobs">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="job"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="job">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="item"/>
|
||||
<xs:element ref="validationReport"/>
|
||||
<xs:element ref="duration"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="item">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="name"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="size" use="required" type="xs:integer"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="name" type="xs:string"/>
|
||||
<xs:element name="validationReport">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="details"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="isCompliant" use="required" type="xs:boolean"/>
|
||||
<xs:attribute name="profileName" use="required"/>
|
||||
<xs:attribute name="statement" use="required"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="details">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="failedChecks" use="required" type="xs:integer"/>
|
||||
<xs:attribute name="failedRules" use="required" type="xs:integer"/>
|
||||
<xs:attribute name="passedChecks" use="required" type="xs:integer"/>
|
||||
<xs:attribute name="passedRules" use="required" type="xs:integer"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="batchSummary">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="validationReports"/>
|
||||
<xs:element ref="featureReports"/>
|
||||
<xs:element ref="repairReports"/>
|
||||
<xs:element ref="duration"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="encrypted" use="required" type="xs:integer"/>
|
||||
<xs:attribute name="failedToParse" use="required" type="xs:integer"/>
|
||||
<xs:attribute name="outOfMemory" use="required" type="xs:integer"/>
|
||||
<xs:attribute name="totalJobs" use="required" type="xs:integer"/>
|
||||
<xs:attribute name="veraExceptions" use="required" type="xs:integer"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="validationReports">
|
||||
<xs:complexType>
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:integer">
|
||||
<xs:attribute name="compliant" use="required" type="xs:integer"/>
|
||||
<xs:attribute name="failedJobs" use="required" type="xs:integer"/>
|
||||
<xs:attribute name="nonCompliant" use="required" type="xs:integer"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="featureReports">
|
||||
<xs:complexType>
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:integer">
|
||||
<xs:attribute name="failedJobs" use="required" type="xs:integer"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="repairReports">
|
||||
<xs:complexType>
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:integer">
|
||||
<xs:attribute name="failedJobs" use="required" type="xs:integer"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="info">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref="signature"/>
|
||||
<xs:element ref="duration"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="signature" type="xs:NCName"/>
|
||||
<xs:element name="duration">
|
||||
<xs:complexType>
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:NMTOKEN">
|
||||
<xs:attribute name="finish" type="xs:integer"/>
|
||||
<xs:attribute name="start" type="xs:integer"/>
|
||||
<xs:attribute name="unit" type="xs:NCName"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="messages">
|
||||
<xs:complexType>
|
||||
<xs:choice>
|
||||
<xs:element ref="exception"/>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" ref="error"/>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" ref="notice"/>
|
||||
</xs:choice>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="exception">
|
||||
<xs:complexType mixed="true">
|
||||
<xs:attribute name="type" use="required" type="xs:integer"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="error">
|
||||
<xs:complexType mixed="true">
|
||||
<xs:attribute name="type" use="required" type="xs:integer"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="notice">
|
||||
<xs:complexType mixed="true">
|
||||
<xs:attribute name="type" use="required" type="xs:integer"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="summary">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="status" use="required" type="xs:NCName"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
||||
170
library/pom.xml
170
library/pom.xml
@@ -3,16 +3,17 @@
|
||||
<parent>
|
||||
<groupId>org.mustangproject</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>2.10.1-SNAPSHOT</version>
|
||||
<version>2.15.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.mustangproject</groupId>
|
||||
<artifactId>library</artifactId>
|
||||
<version>2.10.1-SNAPSHOT</version>
|
||||
<version>2.15.0-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>Library to write, read and validate e-invoices (Factur-X, ZUGFeRD, Order-X, XRechnung/CII)</name>
|
||||
<description>FOSS Java library to read, write and validate european electronic invoices and orders in the UN/CEFACT Cross Industry Invoice based formats Factur-X/ZUGFeRD, XRechnung and Order-X in your invoice PDFs.
|
||||
<description>FOSS Java library to read, write and validate european electronic invoices and orders in the UN/CEFACT
|
||||
Cross Industry Invoice based formats Factur-X/ZUGFeRD, XRechnung and Order-X in your invoice PDFs.
|
||||
</description>
|
||||
<url>http://www.mustangproject.org/</url>
|
||||
<scm>
|
||||
@@ -45,98 +46,115 @@
|
||||
<github.global.server>github</github.global.server>
|
||||
<additionalparam>-Xdoclint:none</additionalparam>
|
||||
<!-- Skip error check for javadoc -->
|
||||
<maven.compiler.compilerVersion>8</maven.compiler.compilerVersion>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<maven.deploy.skip>true</maven.deploy.skip><!-- do deploy to maven central, parent project does not and inherits -->
|
||||
<maven.compiler.compilerVersion>11</maven.compiler.compilerVersion>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<maven.deploy.skip>true
|
||||
</maven.deploy.skip><!-- do deploy to maven central, parent project does not and inherits -->
|
||||
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>2.0.9</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/net.sf.saxon/Saxon-HE -->
|
||||
<dependency>
|
||||
<groupId>net.sf.saxon</groupId>
|
||||
<artifactId>Saxon-HE</artifactId>
|
||||
<version>9.9.0-1</version>
|
||||
</dependency><!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
|
||||
<version>12.4</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.14.2</version>
|
||||
<version>2.17.1</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.xmlgraphics/fop -->
|
||||
<dependency>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>fop</artifactId>
|
||||
<version>2.9</version>
|
||||
<version>2.10</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>xml-apis</groupId>
|
||||
<artifactId>xml-apis</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- JAXB -->
|
||||
<dependency>
|
||||
<groupId>javax.activation</groupId>
|
||||
<artifactId>activation</artifactId>
|
||||
<version>1.1.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.pdfbox</groupId>
|
||||
<artifactId>preflight</artifactId>
|
||||
<version>2.0.27</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.pdfbox</groupId>
|
||||
<artifactId>pdfbox</artifactId>
|
||||
<version>2.0.27</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.dom4j</groupId>
|
||||
<artifactId>dom4j</artifactId>
|
||||
<version>2.1.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>5.9.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
<version>5.9.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- CII to UBL conversion -->
|
||||
<dependency>
|
||||
<groupId>com.helger</groupId>
|
||||
<artifactId>en16931-cii2ubl</artifactId>
|
||||
<version>1.4.10</version>
|
||||
<groupId>jakarta.xml.bind</groupId>
|
||||
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||
<version>4.0.2</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.glassfish.jaxb/jaxb-runtime -->
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jaxb</groupId>
|
||||
<artifactId>jaxb-runtime</artifactId>
|
||||
<version>2.3.3</version>
|
||||
<version>4.0.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.angus</groupId>
|
||||
<artifactId>angus-activation</artifactId>
|
||||
<version>2.0.2</version>
|
||||
</dependency>
|
||||
<!-- Apache PDFBox -->
|
||||
<dependency>
|
||||
<groupId>org.apache.pdfbox</groupId>
|
||||
<artifactId>preflight</artifactId>
|
||||
<version>3.0.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.pdfbox</groupId>
|
||||
<artifactId>pdfbox</artifactId>
|
||||
<version>3.0.2</version>
|
||||
</dependency>
|
||||
<!-- DOM4j -->
|
||||
<dependency>
|
||||
<groupId>org.dom4j</groupId>
|
||||
<artifactId>dom4j</artifactId>
|
||||
<version>2.1.4</version>
|
||||
</dependency>
|
||||
<!-- CII to UBL conversion -->
|
||||
<dependency>
|
||||
<groupId>com.helger</groupId>
|
||||
<artifactId>en16931-cii2ubl</artifactId>
|
||||
<version>2.2.4</version>
|
||||
</dependency>
|
||||
|
||||
<!-- test dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>5.10.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
<version>5.10.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xmlunit</groupId>
|
||||
<artifactId>xmlunit-core</artifactId>
|
||||
<version>2.10.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.xmlunit</groupId>
|
||||
<artifactId>xmlunit-assertj</artifactId>
|
||||
<version>2.9.1</version>
|
||||
<version>2.10.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- API -->
|
||||
<dependency>
|
||||
<groupId>jakarta.xml.bind</groupId>
|
||||
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||
<version>2.3.3</version>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<version>2.0.9</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
@@ -150,7 +168,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.6.1</version>
|
||||
<version>3.13.0</version>
|
||||
<configuration>
|
||||
<descriptorRefs>
|
||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||
@@ -158,8 +176,8 @@
|
||||
<!-- http://stackoverflow.com/questions/574594/how-can-i-create-an-executable-jar-with-dependencies-using-maven
|
||||
mvn clean compile assembly:single -->
|
||||
<!-- or whatever version you use -->
|
||||
<source>8</source>
|
||||
<target>8</target>
|
||||
<source>11</source>
|
||||
<target>11</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
@@ -200,7 +218,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<version>3.3.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
@@ -213,12 +231,11 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<version>3.5.3</version>
|
||||
<configuration>
|
||||
<shadedArtifactAttached>true</shadedArtifactAttached>
|
||||
<minimizeJar>false</minimizeJar><!-- no longer java 11 compatible if set to true because it removes e.g. javax/xml/bind/annotation/XmlSchema-->
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<filters>
|
||||
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
@@ -227,18 +244,6 @@
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>log4j:log4j</artifact>
|
||||
<includes>
|
||||
<include>**</include>
|
||||
</includes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>commons-logging:commons-logging</artifact>
|
||||
<includes>
|
||||
<include>**</include>
|
||||
</includes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
<executions>
|
||||
@@ -247,15 +252,6 @@
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<artifactSet>
|
||||
<excludes>
|
||||
<!--exclude>classworlds:classworlds</exclude> <exclude>junit:junit</exclude>
|
||||
<exclude>jmock:*</exclude> <exclude>*:xml-apis</exclude> <exclude>org.apache.maven:lib:tests</exclude>
|
||||
<exclude>log4j:log4j:jar:</exclude -->
|
||||
</excludes>
|
||||
</artifactSet>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
@@ -301,7 +297,7 @@
|
||||
<configuration>
|
||||
<toolchains>
|
||||
<jdk>
|
||||
<version>8</version>
|
||||
<version>11</version>
|
||||
<vendor>adopt</vendor>
|
||||
</jdk>
|
||||
</toolchains>
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import org.mustangproject.ZUGFeRD.IExportableTransaction;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/***
|
||||
* (absolute) allowances on item and document level
|
||||
*/
|
||||
public class Allowance extends Charge implements IZUGFeRDAllowanceCharge {
|
||||
public class Allowance extends Charge {
|
||||
|
||||
/***
|
||||
* bean constructor
|
||||
@@ -31,6 +30,7 @@ public class Allowance extends Charge implements IZUGFeRDAllowanceCharge {
|
||||
* @return false since its not supposed to be calculated negatively
|
||||
*/
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public boolean isCharge() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDTradeSettlementPayment;
|
||||
|
||||
/**
|
||||
@@ -18,6 +20,10 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment {
|
||||
* the "name" of the bank account (holder)
|
||||
*/
|
||||
protected String accountName=null;
|
||||
/***
|
||||
* bean constructor
|
||||
*/
|
||||
public BankDetails() { }
|
||||
|
||||
/***
|
||||
* constructor for IBAN only :-)
|
||||
@@ -80,12 +86,14 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment {
|
||||
* */
|
||||
@Override
|
||||
@Deprecated
|
||||
@JsonIgnore
|
||||
public String getOwnBIC() {
|
||||
return getBIC();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
@JsonIgnore
|
||||
public String getOwnIBAN() {
|
||||
return getIBAN();
|
||||
}
|
||||
|
||||
@@ -5,9 +5,7 @@ import java.io.Serializable;
|
||||
|
||||
import com.helger.commons.error.list.ErrorList;
|
||||
import com.helger.en16931.cii2ubl.CIIToUBL23Converter;
|
||||
import com.helger.ubl21.UBL21Writer;
|
||||
import com.helger.ubl22.UBL22Writer;
|
||||
import com.helger.ubl23.UBL23Writer;
|
||||
import com.helger.ubl23.UBL23Marshaller;
|
||||
|
||||
/***
|
||||
* converts a Cross Industry Invoice XML file to a UBL XML file
|
||||
@@ -23,39 +21,15 @@ public class CIIToUBL {
|
||||
final ErrorList occurred=new ErrorList();
|
||||
final CIIToUBL23Converter cc=new CIIToUBL23Converter();
|
||||
final Serializable aUBL = cc.convertCIItoUBL(input, occurred);
|
||||
if (aUBL instanceof oasis.names.specification.ubl.schema.xsd.invoice_21.InvoiceType)
|
||||
if (aUBL instanceof oasis.names.specification.ubl.schema.xsd.invoice_23.InvoiceType)
|
||||
{
|
||||
UBL21Writer.invoice ()
|
||||
.setFormattedOutput (true)
|
||||
.write ((oasis.names.specification.ubl.schema.xsd.invoice_21.InvoiceType) aUBL, output);
|
||||
}
|
||||
else if (aUBL instanceof oasis.names.specification.ubl.schema.xsd.creditnote_21.CreditNoteType)
|
||||
{
|
||||
UBL21Writer.creditNote ()
|
||||
.setFormattedOutput (true)
|
||||
.write ((oasis.names.specification.ubl.schema.xsd.creditnote_21.CreditNoteType) aUBL, output);
|
||||
}
|
||||
else if (aUBL instanceof oasis.names.specification.ubl.schema.xsd.invoice_22.InvoiceType)
|
||||
{
|
||||
UBL22Writer.invoice ()
|
||||
.setFormattedOutput (true)
|
||||
.write ((oasis.names.specification.ubl.schema.xsd.invoice_22.InvoiceType) aUBL, output);
|
||||
}
|
||||
else if (aUBL instanceof oasis.names.specification.ubl.schema.xsd.creditnote_22.CreditNoteType)
|
||||
{
|
||||
UBL22Writer.creditNote ()
|
||||
.setFormattedOutput (true)
|
||||
.write ((oasis.names.specification.ubl.schema.xsd.creditnote_22.CreditNoteType) aUBL, output);
|
||||
}
|
||||
else if (aUBL instanceof oasis.names.specification.ubl.schema.xsd.invoice_23.InvoiceType)
|
||||
{
|
||||
UBL23Writer.invoice ()
|
||||
UBL23Marshaller.invoice ()
|
||||
.setFormattedOutput (true)
|
||||
.write ((oasis.names.specification.ubl.schema.xsd.invoice_23.InvoiceType) aUBL, output);
|
||||
}
|
||||
else if (aUBL instanceof oasis.names.specification.ubl.schema.xsd.creditnote_23.CreditNoteType)
|
||||
{
|
||||
UBL23Writer.creditNote ()
|
||||
UBL23Marshaller.creditNote ()
|
||||
.setFormattedOutput (true)
|
||||
.write ((oasis.names.specification.ubl.schema.xsd.creditnote_23.CreditNoteType) aUBL, output);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) 2023 Jochen Stärk, see LICENSE file
|
||||
package org.mustangproject;
|
||||
|
||||
|
||||
import org.mustangproject.ZUGFeRD.TransactionCalculator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class CalculatedInvoice extends Invoice implements Serializable {
|
||||
|
||||
protected BigDecimal grandTotal=null;
|
||||
|
||||
public void calculate() {
|
||||
TransactionCalculator tc=new TransactionCalculator(this);
|
||||
grandTotal=tc.getGrandTotal();
|
||||
}
|
||||
public BigDecimal getGrandTotal() {
|
||||
if (grandTotal==null) {
|
||||
calculate();
|
||||
}
|
||||
return grandTotal;
|
||||
}
|
||||
public CalculatedInvoice setGrandTotal(BigDecimal grand) {
|
||||
grandTotal=grand;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
59
library/src/main/java/org/mustangproject/CashDiscount.java
Normal file
59
library/src/main/java/org/mustangproject/CashDiscount.java
Normal file
@@ -0,0 +1,59 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDCashDiscount;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/***
|
||||
* A class to represent discounts for early payments ("Skonto")
|
||||
*/
|
||||
public class CashDiscount implements IZUGFeRDCashDiscount {
|
||||
|
||||
/***
|
||||
* the reduction percent allowed in the period
|
||||
*/
|
||||
protected BigDecimal percent;
|
||||
/***
|
||||
* the period (usually days) count how long the percent apply
|
||||
*/
|
||||
protected Integer days=null;
|
||||
|
||||
/***
|
||||
* Create a cash discount (skonto) with the specified height in the specified period.
|
||||
* Should someone add more period types than just "days" there
|
||||
* is be space for a (optional) third parameter
|
||||
*
|
||||
* @param percent max 3 decimals "behind the dot", more precision is currently ignored
|
||||
* @param days the count of the periods (usually days) the percentage applies
|
||||
*/
|
||||
public CashDiscount(BigDecimal percent, int days) {
|
||||
this.percent = percent;
|
||||
this.days = days;
|
||||
}
|
||||
|
||||
/***
|
||||
* @return this particular cash discount as cross industry invoice XML
|
||||
*/
|
||||
public String getAsCII() {
|
||||
return "<ram:SpecifiedTradePaymentTerms>"+
|
||||
"<ram:Description>Cash Discount</ram:Description>"+
|
||||
" <ram:ApplicableTradePaymentDiscountTerms>"+
|
||||
" <ram:BasisPeriodMeasure unitCode=\"DAY\">"+days+"</ram:BasisPeriodMeasure>"+
|
||||
" <ram:CalculationPercent>"+XMLTools.nDigitFormat(percent,3)+"</ram:CalculationPercent>"+
|
||||
" </ram:ApplicableTradePaymentDiscountTerms>"+
|
||||
"</ram:SpecifiedTradePaymentTerms>";
|
||||
}
|
||||
|
||||
/***
|
||||
* since EN16931 voted not to have (or even allow) cash discounts in their core invoice the german
|
||||
* XRechnung CIUS defined it's own proprietary format for a freetext field
|
||||
* @return this particular cash discount in proprietary xrechnung format
|
||||
*/
|
||||
public String getAsXRechnung() {
|
||||
return "#SKONTO#TAGE="+days+"#PROZENT="+XMLTools.nDigitFormat(percent,2)+"#\n";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import org.mustangproject.ZUGFeRD.IAbsoluteValueProvider;
|
||||
import org.mustangproject.ZUGFeRD.IExportableTransaction;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableItem;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@@ -28,6 +27,10 @@ public class Charge implements IZUGFeRDAllowanceCharge {
|
||||
* a simple human readable description
|
||||
*/
|
||||
protected String reason;
|
||||
/**
|
||||
* Code from list UNTDID 5189
|
||||
*/
|
||||
protected String reasonCode;
|
||||
/**
|
||||
* the category ID why this charge has been applied
|
||||
*/
|
||||
@@ -96,14 +99,38 @@ public class Charge implements IZUGFeRDAllowanceCharge {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getReasonCode() {
|
||||
return reasonCode;
|
||||
}
|
||||
|
||||
/***
|
||||
* Reason code for the charge
|
||||
* @param reasonCode from list UNTDID 5189
|
||||
* @return fluid setter
|
||||
*/
|
||||
public Charge setReasonCode(String reasonCode) {
|
||||
this.reasonCode = reasonCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public BigDecimal getTotalAmount(IAbsoluteValueProvider currentItem) {
|
||||
if (percent!=null) {
|
||||
return currentItem.getValue().multiply(getPercent().divide(new BigDecimal(100)));
|
||||
} else if(totalAmount != null) {
|
||||
return totalAmount;
|
||||
} else {
|
||||
throw new RuntimeException("percent must be set");
|
||||
}
|
||||
}
|
||||
|
||||
public BigDecimal getTotalAmount() {
|
||||
if (totalAmount!=null) {
|
||||
return totalAmount;
|
||||
} else if (percent!=null) {
|
||||
return currentItem.getValue().multiply(getPercent().divide(new BigDecimal(100)));
|
||||
} else {
|
||||
throw new RuntimeException("Either totalAmount or percent must be set");
|
||||
throw new RuntimeException("totalAmount must be set");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +152,7 @@ public class Charge implements IZUGFeRDAllowanceCharge {
|
||||
* @return true since it is supposed to be calculated negatively
|
||||
*/
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public boolean isCharge() {
|
||||
return true;
|
||||
}
|
||||
@@ -139,7 +167,7 @@ public class Charge implements IZUGFeRDAllowanceCharge {
|
||||
|
||||
|
||||
/***
|
||||
* machine readable reason for this allowance/charge
|
||||
* the category ID why this has been applied
|
||||
* @param categoryCode usually S
|
||||
* @return fluid setter
|
||||
*/
|
||||
|
||||
104
library/src/main/java/org/mustangproject/ClassCode.java
Normal file
104
library/src/main/java/org/mustangproject/ClassCode.java
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* *********************************************************************
|
||||
* <p>
|
||||
* Copyright (c) 2024 Jan N. Klug
|
||||
* <p>
|
||||
* Use is subject to license terms.
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* <p>
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* <p>
|
||||
* **********************************************************************
|
||||
*/
|
||||
package org.mustangproject;
|
||||
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
/**
|
||||
* A schemed classification for products. The scheme can be anything defined in UNTDID 7143.
|
||||
*/
|
||||
public class ClassCode {
|
||||
private final String listID;
|
||||
private final String code;
|
||||
private String listVersionID;
|
||||
|
||||
/**
|
||||
* A UNTDID 7143 schemed classification code
|
||||
*
|
||||
* @param listID the scheme from UNTDID 7143
|
||||
* @param code the classification code
|
||||
* @param listVersionID an (optional) version of the scheme
|
||||
*/
|
||||
public ClassCode(String listID, String code, String listVersionID) {
|
||||
this.listID = listID;
|
||||
this.code = code;
|
||||
this.listVersionID = listVersionID;
|
||||
}
|
||||
|
||||
/**
|
||||
* A UNTDID 7143 schemed classification code
|
||||
*
|
||||
* @param listID the scheme from UNTDID 7143
|
||||
* @param code the classification code
|
||||
*/
|
||||
public ClassCode(String listID, String code) {
|
||||
this(listID, code, null);
|
||||
}
|
||||
|
||||
/***
|
||||
* Set the version for the scheme returned by {@link #getListID()}
|
||||
* @param listVersionID the scheme version
|
||||
*/
|
||||
public void setListVersionID(String listVersionID) {
|
||||
this.listVersionID = listVersionID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scheme (according to UNTDID 7143) that describes the value returned by {@link #getCode()},
|
||||
* potentially versioned by {@link #getListVersionID()}
|
||||
*
|
||||
* @return the scheme
|
||||
*/
|
||||
public String getListID() {
|
||||
return listID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the code that (following the scheme returned by {@link #getListID()}) describes the product
|
||||
*
|
||||
* @return the classification code itself
|
||||
*/
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the (optional) version for the scheme returned by {@link #getListID()}
|
||||
*
|
||||
* @return the version or {@code null} if not set
|
||||
*/
|
||||
public String getListVersionID() {
|
||||
return listVersionID;
|
||||
}
|
||||
|
||||
public static ClassCode fromNode(Node node) {
|
||||
NamedNodeMap attrs = node.getAttributes();
|
||||
if (attrs != null && attrs.getNamedItem("listID") != null) {
|
||||
ClassCode classCode = new ClassCode(attrs.getNamedItem("listID").getNodeValue(), node.getTextContent());
|
||||
if (attrs.getNamedItem("listVersionID") != null) {
|
||||
classCode.setListVersionID(attrs.getNamedItem("listVersionID").getNodeValue());
|
||||
}
|
||||
return classCode;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* *********************************************************************
|
||||
* <p>
|
||||
* Copyright (c) 2024 Jan N. Klug
|
||||
* <p>
|
||||
* Use is subject to license terms.
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* <p>
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* <p>
|
||||
* **********************************************************************
|
||||
*/
|
||||
package org.mustangproject;
|
||||
|
||||
import org.mustangproject.ZUGFeRD.IDesignatedProductClassification;
|
||||
|
||||
/**
|
||||
* An implementation of {@link IDesignatedProductClassification} for describing a {@link org.mustangproject.Product}
|
||||
*
|
||||
*/
|
||||
public class DesignatedProductClassification implements IDesignatedProductClassification {
|
||||
private final ClassCode classCode;
|
||||
private String className;
|
||||
|
||||
/**
|
||||
* A schemed product descriptor
|
||||
*
|
||||
* @param classCode an UNTDID 7143 schemed class code
|
||||
* @param className a verbal description of the class code
|
||||
*/
|
||||
public DesignatedProductClassification(ClassCode classCode, String className) {
|
||||
this.classCode = classCode;
|
||||
this.className = className;
|
||||
}
|
||||
|
||||
/**
|
||||
* A schemed product descriptor
|
||||
*
|
||||
* @param classCode an UNTDID 7143 schemed class code
|
||||
*/
|
||||
public DesignatedProductClassification(ClassCode classCode) {
|
||||
this(classCode, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassCode getClassCode() {
|
||||
return classCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassName() {
|
||||
return className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the human-readable name of the class code
|
||||
*
|
||||
* @param className the name of the class code (can be {@code null})
|
||||
*/
|
||||
public void setClassName(String className) {
|
||||
this.className = className;
|
||||
}
|
||||
}
|
||||
42
library/src/main/java/org/mustangproject/DirectDebit.java
Normal file
42
library/src/main/java/org/mustangproject/DirectDebit.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDTradeSettlementDebit;
|
||||
|
||||
/**
|
||||
* provides e.g. the IBAN to transfer money to :-)
|
||||
*/
|
||||
public class DirectDebit implements IZUGFeRDTradeSettlementDebit {
|
||||
/**
|
||||
* Debited account identifier (BT-91)
|
||||
*/
|
||||
protected final String IBAN;
|
||||
|
||||
/**
|
||||
* Mandate reference identifier (BT-89)
|
||||
*/
|
||||
protected final String mandate;
|
||||
|
||||
/***
|
||||
* constructor for normal use :-)
|
||||
* @param IBAN the IBAN as string
|
||||
* @param mandate the mandate as string
|
||||
*/
|
||||
public DirectDebit(String IBAN, String mandate) {
|
||||
this.IBAN = IBAN;
|
||||
this.mandate = mandate;
|
||||
}
|
||||
|
||||
/***
|
||||
* getter for the IBAN
|
||||
* @return IBAN
|
||||
*/
|
||||
@Override
|
||||
public String getIBAN() {
|
||||
return this.IBAN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMandate() {
|
||||
return this.mandate;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.mustangproject;
|
||||
|
||||
public enum EStandard {
|
||||
facturx, orderx, despatchadvice, ubldespatchadvice, zugferd, cii, ubl
|
||||
facturx, orderx, despatchadvice, ubldespatchadvice, zugferd, cii, ubl, ubl_creditnote
|
||||
|
||||
}
|
||||
|
||||
@@ -26,12 +26,8 @@ import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.mustangproject.ZUGFeRD.IExportableTransaction;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableItem;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableTradeParty;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDPaymentTerms;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDTradeSettlement;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import org.mustangproject.ZUGFeRD.*;
|
||||
import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
@@ -42,11 +38,13 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
* @see IExportableTransaction if you want to implement an interface instead
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class Invoice implements IExportableTransaction {
|
||||
|
||||
protected String documentName = null, documentCode = null, number = null, ownOrganisationFullPlaintextInfo = null, referenceNumber = null, shipToOrganisationID = null, shipToOrganisationName = null, shipToStreet = null, shipToZIP = null, shipToLocation = null, shipToCountry = null, buyerOrderReferencedDocumentID = null, invoiceReferencedDocumentID = null, buyerOrderReferencedDocumentIssueDateTime = null, ownForeignOrganisationID = null, ownOrganisationName = null, currency = null, paymentTermDescription = null;
|
||||
protected Date issueDate = null, dueDate = null, deliveryDate = null;
|
||||
protected TradeParty sender = null, recipient = null, deliveryAddress = null;
|
||||
protected TradeParty sender = null, recipient = null, deliveryAddress = null, payee = null;
|
||||
protected ArrayList<CashDiscount> cashDiscounts = null;
|
||||
@JsonDeserialize(contentAs = Item.class)
|
||||
protected ArrayList<IZUGFeRDExportableItem> ZFItems = null;
|
||||
protected ArrayList<String> notes = null;
|
||||
@@ -67,9 +65,11 @@ public class Invoice implements IExportableTransaction {
|
||||
protected String specifiedProcuringProjectName = null;
|
||||
protected String despatchAdviceReferencedDocumentID = null;
|
||||
protected String vatDueDateTypeCode = null;
|
||||
protected String creditorReferenceID; // required when direct debit is used.
|
||||
|
||||
public Invoice() {
|
||||
ZFItems = new ArrayList<>();
|
||||
cashDiscounts = new ArrayList<>();
|
||||
setCurrency("EUR");
|
||||
}
|
||||
|
||||
@@ -115,6 +115,10 @@ public class Invoice implements IExportableTransaction {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public IZUGFeRDCashDiscount[] getCashDiscounts() {
|
||||
return cashDiscounts.toArray(new IZUGFeRDCashDiscount[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNumber() {
|
||||
@@ -139,6 +143,7 @@ public class Invoice implements IExportableTransaction {
|
||||
documentCode = DocumentCodeTypeConstants.CORRECTEDINVOICE;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Invoice setCreditNote() {
|
||||
documentCode = DocumentCodeTypeConstants.CREDITNOTE;
|
||||
return this;
|
||||
@@ -229,6 +234,7 @@ public class Invoice implements IExportableTransaction {
|
||||
public String getBuyerOrderReferencedDocumentID() {
|
||||
return buyerOrderReferencedDocumentID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSellerOrderReferencedDocumentID() {
|
||||
return sellerOrderReferencedDocumentID;
|
||||
@@ -239,6 +245,7 @@ public class Invoice implements IExportableTransaction {
|
||||
this.sellerOrderReferencedDocumentID = sellerOrderReferencedDocumentID;
|
||||
return this;
|
||||
}
|
||||
|
||||
/***
|
||||
* usually the order number
|
||||
* @param buyerOrderReferencedDocumentID string with number
|
||||
@@ -258,6 +265,7 @@ public class Invoice implements IExportableTransaction {
|
||||
this.invoiceReferencedDocumentID = invoiceReferencedDocumentID;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInvoiceReferencedDocumentID() {
|
||||
return invoiceReferencedDocumentID;
|
||||
@@ -480,6 +488,7 @@ public class Invoice implements IExportableTransaction {
|
||||
/**
|
||||
* required.
|
||||
* sets the invoice receiving institution = invoicee
|
||||
*
|
||||
* @param recipient the invoicee organisation
|
||||
* @return fluent setter
|
||||
*/
|
||||
@@ -491,6 +500,7 @@ public class Invoice implements IExportableTransaction {
|
||||
/**
|
||||
* required.
|
||||
* sets the invoicing institution = invoicer
|
||||
*
|
||||
* @param sender the invoicer
|
||||
* @return fluent setter
|
||||
*/
|
||||
@@ -512,6 +522,20 @@ public class Invoice implements IExportableTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* this is wrong and only used from jackson
|
||||
* @param iza
|
||||
* @return
|
||||
*/
|
||||
public Invoice setZFAllowances(Allowance[] iza) {
|
||||
Allowances=new ArrayList<>();
|
||||
|
||||
for (IZUGFeRDAllowanceCharge cz:iza) {
|
||||
Allowances.add(cz);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public IZUGFeRDAllowanceCharge[] getZFCharges() {
|
||||
@@ -522,6 +546,18 @@ public class Invoice implements IExportableTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* this is wrong and only used from jackson
|
||||
* @param iza
|
||||
* @return
|
||||
*/
|
||||
public Invoice setZFCharges(Charge[] iza) {
|
||||
Charges=new ArrayList<>();
|
||||
for (IZUGFeRDAllowanceCharge cz:iza) {
|
||||
Charges.add(cz);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IZUGFeRDAllowanceCharge[] getZFLogisticsServiceCharges() {
|
||||
@@ -540,7 +576,7 @@ public class Invoice implements IExportableTransaction {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ((TradeParty) getSender()).getAsTradeSettlement();
|
||||
return getSender().getAsTradeSettlement();
|
||||
|
||||
}
|
||||
|
||||
@@ -570,6 +606,32 @@ public class Invoice implements IExportableTransaction {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TradeParty getPayee() {
|
||||
return this.payee;
|
||||
}
|
||||
|
||||
/***
|
||||
* if the payee is not the seller, it can be specified here
|
||||
* @param payee the payment receiving organisation
|
||||
* @return fluent setter
|
||||
*/
|
||||
public Invoice setPayee(TradeParty payee) {
|
||||
this.payee = payee;
|
||||
return this;
|
||||
}
|
||||
|
||||
/***
|
||||
* Adds a cash discount (skonto)
|
||||
* @param c the CashDiscount percent/period combination
|
||||
* @return fluent setter
|
||||
*/
|
||||
public Invoice addCashDiscount(CashDiscount c) {
|
||||
this.cashDiscounts.add(c);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public IZUGFeRDExportableItem[] getZFItems() {
|
||||
return ZFItems.toArray(new IZUGFeRDExportableItem[0]);
|
||||
@@ -582,9 +644,10 @@ public class Invoice implements IExportableTransaction {
|
||||
/**
|
||||
* required
|
||||
* adds invoice "lines" :-)
|
||||
* @see Item
|
||||
*
|
||||
* @param item the invoice line
|
||||
* @return fluent setter
|
||||
* @see Item
|
||||
*/
|
||||
public Invoice addItem(IZUGFeRDExportableItem item) {
|
||||
ZFItems.add(item);
|
||||
@@ -669,6 +732,7 @@ public class Invoice implements IExportableTransaction {
|
||||
|
||||
/**
|
||||
* adds a free text paragraph, which will become an includedNote element
|
||||
*
|
||||
* @param text freeform UTF8 plain text
|
||||
* @return fluent setter
|
||||
*/
|
||||
@@ -694,6 +758,7 @@ public class Invoice implements IExportableTransaction {
|
||||
/**
|
||||
* adds a free text paragraph, which will become an includedNote element with explicit
|
||||
* subjectCode {@link SubjectCode#AAI}
|
||||
*
|
||||
* @param content freeform UTF8 plain text
|
||||
* @return fluent setter
|
||||
*/
|
||||
@@ -708,6 +773,7 @@ public class Invoice implements IExportableTransaction {
|
||||
/**
|
||||
* adds a free text paragraph, which will become an includedNote element with explicit
|
||||
* subjectCode {@link SubjectCode#REG}
|
||||
*
|
||||
* @param content freeform UTF8 plain text
|
||||
* @return fluent setter
|
||||
*/
|
||||
@@ -722,6 +788,7 @@ public class Invoice implements IExportableTransaction {
|
||||
/**
|
||||
* adds a free text paragraph, which will become an includedNote element with explicit
|
||||
* subjectCode {@link SubjectCode#ABL}
|
||||
*
|
||||
* @param content freeform UTF8 plain text
|
||||
* @return fluent setter
|
||||
*/
|
||||
@@ -736,6 +803,7 @@ public class Invoice implements IExportableTransaction {
|
||||
/**
|
||||
* adds a free text paragraph, which will become an includedNote element with explicit
|
||||
* subjectCode {@link SubjectCode#CUS}
|
||||
*
|
||||
* @param content freeform UTF8 plain text
|
||||
* @return fluent setter
|
||||
*/
|
||||
@@ -750,6 +818,7 @@ public class Invoice implements IExportableTransaction {
|
||||
/**
|
||||
* adds a free text paragraph, which will become an includedNote element with explicit
|
||||
* subjectCode {@link SubjectCode#SUR}
|
||||
*
|
||||
* @param content freeform UTF8 plain text
|
||||
* @return fluent setter
|
||||
*/
|
||||
@@ -764,6 +833,7 @@ public class Invoice implements IExportableTransaction {
|
||||
/**
|
||||
* adds a free text paragraph, which will become an includedNote element with explicit
|
||||
* subjectCode {@link SubjectCode#TXD}
|
||||
*
|
||||
* @param content freeform UTF8 plain text
|
||||
* @return fluent setter
|
||||
*/
|
||||
@@ -778,6 +848,7 @@ public class Invoice implements IExportableTransaction {
|
||||
/**
|
||||
* adds a free text paragraph, which will become an includedNote element with explicit
|
||||
* subjectCode {@link SubjectCode#ACY}
|
||||
*
|
||||
* @param content freeform UTF8 plain text
|
||||
* @return fluent setter
|
||||
*/
|
||||
@@ -788,9 +859,11 @@ public class Invoice implements IExportableTransaction {
|
||||
includedNotes.add(IncludedNote.introductionNote(content));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* adds a free text paragraph, which will become an includedNote element with explicit
|
||||
* subjectCode {@link SubjectCode#AAK}
|
||||
*
|
||||
* @param content freeform UTF8 plain text
|
||||
* @return fluent setter
|
||||
*/
|
||||
@@ -841,6 +914,7 @@ public class Invoice implements IExportableTransaction {
|
||||
|
||||
/**
|
||||
* Decide when the VAT should be collected.
|
||||
*
|
||||
* @param vatDueDateTypeCode use EventTimeCodeTypeConstants
|
||||
* @return fluent setter
|
||||
*/
|
||||
@@ -849,4 +923,13 @@ public class Invoice implements IExportableTransaction {
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getCreditorReferenceID() {
|
||||
return creditorReferenceID;
|
||||
}
|
||||
|
||||
public Invoice setCreditorReferenceID(String creditorReferenceID) {
|
||||
this.creditorReferenceID = creditorReferenceID;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.apache.fop.util.XMLUtil;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import org.mustangproject.ZUGFeRD.IReferencedDocument;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableItem;
|
||||
import org.mustangproject.util.NodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
@@ -12,24 +13,30 @@ import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/***
|
||||
* describes any invoice line
|
||||
*/
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class Item implements IZUGFeRDExportableItem {
|
||||
protected BigDecimal price, quantity, tax, grossPrice, lineTotalAmount;
|
||||
protected BigDecimal price = BigDecimal.ZERO;
|
||||
protected BigDecimal quantity;
|
||||
protected BigDecimal tax;
|
||||
protected BigDecimal grossPrice;
|
||||
protected BigDecimal lineTotalAmount;
|
||||
protected BigDecimal basisQuantity = BigDecimal.ONE;
|
||||
protected Date detailedDeliveryPeriodFrom = null, detailedDeliveryPeriodTo = null;
|
||||
protected Date detailedDeliveryPeriodFrom = null;
|
||||
protected Date detailedDeliveryPeriodTo = null;
|
||||
protected String id;
|
||||
protected String referencedLineID = null;
|
||||
protected Product product;
|
||||
protected ArrayList<String> notes = null;
|
||||
protected ArrayList<ReferencedDocument> referencedDocuments = null;
|
||||
protected ArrayList<IZUGFeRDAllowanceCharge> Allowances = new ArrayList<IZUGFeRDAllowanceCharge>(),
|
||||
Charges = new ArrayList<IZUGFeRDAllowanceCharge>();
|
||||
protected ArrayList<ReferencedDocument> additionalReference = null;
|
||||
protected ArrayList<IZUGFeRDAllowanceCharge> Allowances = new ArrayList<>();
|
||||
protected ArrayList<IZUGFeRDAllowanceCharge> Charges = new ArrayList<>();
|
||||
|
||||
/***
|
||||
* default constructor
|
||||
@@ -43,7 +50,6 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
this.product = product;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* empty constructor
|
||||
* do not use, but might be used e.g. by jackson
|
||||
@@ -52,221 +58,74 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
}
|
||||
|
||||
public Item(NodeList itemChilds, boolean recalcPrice) {
|
||||
String price = "0";
|
||||
String basisQuantity = "1";
|
||||
String name = "";
|
||||
String sellerAssignedID = null;
|
||||
String description = "";
|
||||
SchemedID gid = null;
|
||||
String quantity = "0";
|
||||
String vatPercent = null;
|
||||
String lineTotal = "0";
|
||||
String unitCode = "0";
|
||||
NodeMap itemMap = new NodeMap(itemChilds);
|
||||
|
||||
ArrayList<ReferencedDocument> rdocs = null;
|
||||
|
||||
// nodes.item(i).getTextContent())) {
|
||||
|
||||
for (int itemChildIndex = 0; itemChildIndex < itemChilds.getLength(); itemChildIndex++) {
|
||||
String lineTrade = itemChilds.item(itemChildIndex).getLocalName();
|
||||
if ((lineTrade != null) && (lineTrade.equals("Item"))) {
|
||||
itemMap.getAsNodeMap("Item").ifPresent(icnm -> {
|
||||
// ubl
|
||||
//we need: name description unitcode
|
||||
//and we additionally have vat%
|
||||
NodeList UBLitemChilds = itemChilds.item(itemChildIndex).getChildNodes();
|
||||
for (Node currentUBLItemChildNode : XMLTools.asList(UBLitemChilds)) {
|
||||
setProduct(new Product());
|
||||
icnm.getAsString("Name").ifPresent(product::setName);
|
||||
icnm.getAsNodeMap("ClassifiedTaxCategory").flatMap(m -> m.getAsBigDecimal("Percent"))
|
||||
.ifPresent(product::setVATPercent);
|
||||
});
|
||||
|
||||
if ((currentUBLItemChildNode.getLocalName() != null) && (currentUBLItemChildNode.getLocalName().equals("Name"))) {
|
||||
name = currentUBLItemChildNode.getTextContent();
|
||||
}
|
||||
if ((currentUBLItemChildNode.getLocalName() != null) && (currentUBLItemChildNode.getLocalName().equals("ClassifiedTaxCategory"))) {
|
||||
for (Node currentUBLTaxChildNode : XMLTools.asList(currentUBLItemChildNode.getChildNodes())) {
|
||||
if ((currentUBLTaxChildNode.getLocalName() != null) && (currentUBLTaxChildNode.getLocalName().equals("Percent"))) {
|
||||
vatPercent = currentUBLTaxChildNode.getTextContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((lineTrade != null) && (lineTrade.equals("Price"))) {
|
||||
itemMap.getAsNodeMap("Price").ifPresent(icnm -> {
|
||||
// ubl
|
||||
// PriceAmount with currencyID and BaseQuantity with unitCode
|
||||
NodeList UBLpriceChilds = itemChilds.item(itemChildIndex).getChildNodes();
|
||||
for (Node currentUBLPriceChildNode : XMLTools.asList(UBLpriceChilds)) {
|
||||
icnm.getAsBigDecimal("PriceAmount").ifPresent(this::setPrice);
|
||||
icnm.getAsBigDecimal("BaseQuantity").ifPresent(this::setBasisQuantity);
|
||||
});
|
||||
|
||||
if ((currentUBLPriceChildNode.getLocalName() != null) && (currentUBLPriceChildNode.getLocalName().equals("PriceAmount"))) {
|
||||
price = currentUBLPriceChildNode.getTextContent();
|
||||
}
|
||||
if ((currentUBLPriceChildNode.getLocalName() != null) && (currentUBLPriceChildNode.getLocalName().equals("BaseQuantity"))) {
|
||||
basisQuantity = currentUBLPriceChildNode.getTextContent();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if ((lineTrade != null) && (lineTrade.equals("InvoicedQuantity"))) {
|
||||
itemMap.getNode("InvoicedQuantity").ifPresent(icn -> {
|
||||
// ubl
|
||||
quantity = itemChilds.item(itemChildIndex).getTextContent();
|
||||
unitCode = itemChilds.item(itemChildIndex).getAttributes()
|
||||
.getNamedItem("unitCode").getNodeValue();
|
||||
}
|
||||
if ((lineTrade != null) && (lineTrade.equals("SpecifiedLineTradeAgreement")
|
||||
|| lineTrade.equals("SpecifiedSupplyChainTradeAgreement"))) {
|
||||
NodeList tradeLineChilds = itemChilds.item(itemChildIndex).getChildNodes();
|
||||
for (int tradeLineChildIndex = 0; tradeLineChildIndex < tradeLineChilds
|
||||
.getLength(); tradeLineChildIndex++) {
|
||||
setQuantity(new BigDecimal(icn.getTextContent().trim()));
|
||||
product.setUnit(icn.getAttributes().getNamedItem("unitCode").getNodeValue());
|
||||
});
|
||||
|
||||
if ((tradeLineChilds.item(tradeLineChildIndex).getLocalName() != null) && tradeLineChilds
|
||||
.item(tradeLineChildIndex).getLocalName().equals("AdditionalReferencedDocument")) {
|
||||
String IssuerAssignedID = "";
|
||||
String TypeCode = "";
|
||||
String ReferenceTypeCode = "";
|
||||
itemMap.getAsNodeMap("SpecifiedLineTradeAgreement", "SpecifiedSupplyChainTradeAgreement").ifPresent(icnm -> {
|
||||
icnm.getAsNodeMap("BuyerOrderReferencedDocument")
|
||||
.flatMap(bordNodes -> bordNodes.getAsString("LineID"))
|
||||
.ifPresent(this::addReferencedLineID);
|
||||
|
||||
NodeList refDocChilds = tradeLineChilds.item(tradeLineChildIndex).getChildNodes();
|
||||
for (int refDocIndex = 0; refDocIndex < refDocChilds.getLength(); refDocIndex++) {
|
||||
String localName = refDocChilds.item(refDocIndex).getLocalName();
|
||||
if ((localName != null) && (localName.equals("IssuerAssignedID"))) {
|
||||
IssuerAssignedID = refDocChilds.item(refDocIndex).getTextContent();
|
||||
}
|
||||
if ((localName != null) && (localName.equals("TypeCode"))) {
|
||||
TypeCode = refDocChilds.item(refDocIndex).getTextContent();
|
||||
}
|
||||
if ((localName != null) && (localName.equals("ReferenceTypeCode"))) {
|
||||
ReferenceTypeCode = refDocChilds.item(refDocIndex).getTextContent();
|
||||
}
|
||||
}
|
||||
icnm.getAsNodeMap("NetPriceProductTradePrice").ifPresent(npptpNodes -> {
|
||||
npptpNodes.getAsBigDecimal("ChargeAmount").ifPresent(this::setPrice);
|
||||
npptpNodes.getAsBigDecimal("BasisQuantity").ifPresent(this::setBasisQuantity);
|
||||
});
|
||||
|
||||
ReferencedDocument rd = new ReferencedDocument(IssuerAssignedID, TypeCode,
|
||||
ReferenceTypeCode);
|
||||
if (rdocs == null) {
|
||||
rdocs = new ArrayList<ReferencedDocument>();
|
||||
}
|
||||
rdocs.add(rd);
|
||||
icnm.getAllNodes("AdditionalReferencedDocument").map(ReferencedDocument::fromNode)
|
||||
.forEach(this::addReferencedDocument);
|
||||
});
|
||||
|
||||
}
|
||||
itemMap.getNode("SpecifiedTradeProduct").map(Product::new).ifPresent(this::setProduct);
|
||||
|
||||
if ((tradeLineChilds.item(tradeLineChildIndex).getLocalName() != null) && tradeLineChilds
|
||||
.item(tradeLineChildIndex).getLocalName().equals("NetPriceProductTradePrice")) {
|
||||
NodeList netChilds = tradeLineChilds.item(tradeLineChildIndex).getChildNodes();
|
||||
for (int netIndex = 0; netIndex < netChilds.getLength(); netIndex++) {
|
||||
if ((netChilds.item(netIndex).getLocalName() != null)
|
||||
&& (netChilds.item(netIndex).getLocalName().equals("ChargeAmount"))) {
|
||||
price = netChilds.item(netIndex).getTextContent();// ChargeAmount
|
||||
|
||||
}
|
||||
if ((netChilds.item(netIndex).getLocalName() != null)
|
||||
&& ((netChilds.item(netIndex).getLocalName().equals("BasisQuantity")) || (netChilds.item(netIndex).getLocalName().equals("InvoicedQuantity")))) {
|
||||
basisQuantity = netChilds.item(netIndex).getTextContent();// ChargeAmount
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((lineTrade != null) && (lineTrade.equals("SpecifiedLineTradeDelivery")
|
||||
|| lineTrade.equals("SpecifiedSupplyChainTradeDelivery"))) {
|
||||
NodeList tradeLineChilds = itemChilds.item(itemChildIndex).getChildNodes();
|
||||
for (int tradeLineChildIndex = 0; tradeLineChildIndex < tradeLineChilds
|
||||
.getLength(); tradeLineChildIndex++) {
|
||||
String tradeName = tradeLineChilds.item(tradeLineChildIndex).getLocalName();
|
||||
if ((tradeName != null)
|
||||
&& (tradeName.equals("BilledQuantity") || tradeName.equals("RequestedQuantity")
|
||||
|| tradeName.equals("DespatchedQuantity"))) {
|
||||
// RequestedQuantity is for Order-X, BilledQuantity for FX and ZF
|
||||
quantity = tradeLineChilds.item(tradeLineChildIndex).getTextContent();
|
||||
unitCode = tradeLineChilds.item(tradeLineChildIndex).getAttributes()
|
||||
.getNamedItem("unitCode").getNodeValue();
|
||||
itemMap.getAsNodeMap("SpecifiedLineTradeDelivery", "SpecifiedSupplyChainTradeDelivery")
|
||||
.flatMap(icnm -> icnm.getNode("BilledQuantity", "RequestedQuantity", "DespatchedQuantity"))
|
||||
.ifPresent(bq -> {
|
||||
setQuantity(new BigDecimal(bq.getTextContent().trim()));
|
||||
if (bq.hasAttributes()) {
|
||||
Node unitAttr = bq.getAttributes().getNamedItem("unitCode");
|
||||
if (unitAttr != null) {
|
||||
product.setUnit(unitAttr.getNodeValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((lineTrade != null) && (lineTrade.equals("SpecifiedTradeProduct"))) {
|
||||
NodeList tradeProductChilds = itemChilds.item(itemChildIndex).getChildNodes();
|
||||
for (int tradeProductChildIndex = 0; tradeProductChildIndex < tradeProductChilds
|
||||
.getLength(); tradeProductChildIndex++) {
|
||||
if ((tradeProductChilds.item(tradeProductChildIndex).getLocalName() != null)
|
||||
&& (tradeProductChilds.item(tradeProductChildIndex).getLocalName()
|
||||
.equals("Name"))) {
|
||||
name = tradeProductChilds.item(tradeProductChildIndex).getTextContent();
|
||||
}
|
||||
if ((tradeProductChilds.item(tradeProductChildIndex).getLocalName() != null)
|
||||
&& (tradeProductChilds.item(tradeProductChildIndex).getLocalName()
|
||||
.equals("SellerAssignedID"))) {
|
||||
sellerAssignedID = tradeProductChilds.item(tradeProductChildIndex).getTextContent();
|
||||
}
|
||||
if ((tradeProductChilds.item(tradeProductChildIndex).getLocalName() != null)
|
||||
&& (tradeProductChilds.item(tradeProductChildIndex).getLocalName()
|
||||
.equals("GlobalID"))) {
|
||||
if (tradeProductChilds.item(tradeProductChildIndex).getAttributes()
|
||||
.getNamedItem("schemeID") != null) {
|
||||
gid = new SchemedID()
|
||||
.setScheme(tradeProductChilds.item(tradeProductChildIndex).getAttributes()
|
||||
.getNamedItem("schemeID").getNodeValue())
|
||||
.setId(tradeProductChilds.item(tradeProductChildIndex).getTextContent());
|
||||
});
|
||||
|
||||
itemMap.getAsNodeMap("SpecifiedLineTradeSettlement", "SpecifiedSupplyChainTradeSettlement").ifPresent(icnm -> {
|
||||
icnm.getAsNodeMap("ApplicableTradeTax")
|
||||
.flatMap(cnm -> cnm.getAsBigDecimal("RateApplicablePercent", "ApplicablePercent"))
|
||||
.ifPresent(product::setVATPercent);
|
||||
|
||||
if (recalcPrice && !BigDecimal.ZERO.equals(quantity)) {
|
||||
icnm.getAsNodeMap("SpecifiedTradeSettlementLineMonetarySummation")
|
||||
.flatMap(cnm -> cnm.getAsBigDecimal("LineTotalAmount"))
|
||||
.ifPresent(lineTotal -> setPrice(lineTotal.divide(quantity, 4, RoundingMode.HALF_UP)));
|
||||
}
|
||||
|
||||
icnm.getAllNodes("AdditionalReferencedDocument").map(ReferencedDocument::fromNode).forEach(this::addAdditionalReference);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((lineTrade != null) && (lineTrade.equals("SpecifiedLineTradeSettlement")
|
||||
|| lineTrade.equals("SpecifiedSupplyChainTradeSettlement"))) {
|
||||
NodeList tradeSettlementChilds = itemChilds.item(itemChildIndex).getChildNodes();
|
||||
for (int tradeSettlementChildIndex = 0; tradeSettlementChildIndex < tradeSettlementChilds
|
||||
.getLength(); tradeSettlementChildIndex++) {
|
||||
|
||||
String tradeSettlementName = tradeSettlementChilds.item(tradeSettlementChildIndex)
|
||||
.getLocalName();
|
||||
if (tradeSettlementName != null) {
|
||||
if (tradeSettlementName.equals("ApplicableTradeTax")) {
|
||||
NodeList taxChilds = tradeSettlementChilds.item(tradeSettlementChildIndex)
|
||||
.getChildNodes();
|
||||
for (int taxChildIndex = 0; taxChildIndex < taxChilds
|
||||
.getLength(); taxChildIndex++) {
|
||||
String taxChildName = taxChilds.item(taxChildIndex).getLocalName();
|
||||
if ((taxChildName != null) && (taxChildName.equals("RateApplicablePercent")
|
||||
|| taxChildName.equals("ApplicablePercent"))) {
|
||||
vatPercent = taxChilds.item(taxChildIndex).getTextContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tradeSettlementName.equals("SpecifiedTradeSettlementLineMonetarySummation")) {
|
||||
NodeList totalChilds = tradeSettlementChilds.item(tradeSettlementChildIndex)
|
||||
.getChildNodes();
|
||||
for (int totalChildIndex = 0; totalChildIndex < totalChilds
|
||||
.getLength(); totalChildIndex++) {
|
||||
if ((totalChilds.item(totalChildIndex).getLocalName() != null) && (totalChilds
|
||||
.item(totalChildIndex).getLocalName().equals("LineTotalAmount"))) {
|
||||
lineTotal = totalChilds.item(totalChildIndex).getTextContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BigDecimal prc = new BigDecimal(price.trim());
|
||||
BigDecimal qty = new BigDecimal(quantity.trim());
|
||||
if ((recalcPrice) && (!qty.equals(BigDecimal.ZERO))) {
|
||||
prc = new BigDecimal(lineTotal.trim()).divide(qty, 4, RoundingMode.HALF_UP);
|
||||
}
|
||||
Product p = new Product(name, description, unitCode,
|
||||
vatPercent == null ? null : new BigDecimal(vatPercent.trim()));
|
||||
if (gid != null) {
|
||||
p.addGlobalID(gid);
|
||||
}
|
||||
if (sellerAssignedID != null) {
|
||||
p.setSellerAssignedID(sellerAssignedID);
|
||||
}
|
||||
setProduct(p);
|
||||
setPrice(prc);
|
||||
setQuantity(qty);
|
||||
setBasisQuantity(new BigDecimal(basisQuantity));
|
||||
if (rdocs != null) {
|
||||
for (ReferencedDocument rdoc : rdocs) {
|
||||
addReferencedDocument(rdoc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Item addReferencedLineID(String s) {
|
||||
referencedLineID = s;
|
||||
@@ -428,7 +287,7 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
*/
|
||||
public Item addNote(String text) {
|
||||
if (notes == null) {
|
||||
notes = new ArrayList<String>();
|
||||
notes = new ArrayList<>();
|
||||
}
|
||||
notes.add(text);
|
||||
return this;
|
||||
@@ -441,7 +300,7 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
*/
|
||||
public Item addReferencedDocument(ReferencedDocument doc) {
|
||||
if (referencedDocuments == null) {
|
||||
referencedDocuments = new ArrayList<ReferencedDocument>();
|
||||
referencedDocuments = new ArrayList<>();
|
||||
}
|
||||
referencedDocuments.add(doc);
|
||||
return this;
|
||||
@@ -455,6 +314,29 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
return referencedDocuments.toArray(new IReferencedDocument[0]);
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* adds item level references along with their typecodes and issuerassignedIDs (contract ID, cost centre, ...)
|
||||
* @param doc the ReferencedDocument to add
|
||||
* @return fluent setter
|
||||
*/
|
||||
public Item addAdditionalReference(ReferencedDocument doc) {
|
||||
if (additionalReference == null) {
|
||||
additionalReference = new ArrayList<>();
|
||||
}
|
||||
additionalReference.add(doc);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IReferencedDocument[] getAdditionalReferences() {
|
||||
if (additionalReference == null) {
|
||||
return null;
|
||||
}
|
||||
return additionalReference.toArray(new IReferencedDocument[0]);
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* specify a item level delivery period
|
||||
* (apart from the document level delivery period, and the document level
|
||||
@@ -475,6 +357,7 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
* this will be included in a BillingSpecifiedPeriod element
|
||||
* @return the beginning of the delivery period
|
||||
*/
|
||||
@Override
|
||||
public Date getDetailedDeliveryPeriodFrom() {
|
||||
return detailedDeliveryPeriodFrom;
|
||||
}
|
||||
@@ -484,6 +367,7 @@ public class Item implements IZUGFeRDExportableItem {
|
||||
* this will be included in a BillingSpecifiedPeriod element
|
||||
* @return the end of the delivery period
|
||||
*/
|
||||
@Override
|
||||
public Date getDetailedDeliveryPeriodTo() {
|
||||
return detailedDeliveryPeriodTo;
|
||||
}
|
||||
|
||||
@@ -5,46 +5,74 @@ import org.mustangproject.ZUGFeRD.*;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/***
|
||||
* A organisation, i.e. usually a company
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class LegalOrganisation implements IZUGFeRDLegalOrganisation {
|
||||
|
||||
protected String ID = null;
|
||||
protected String SchemeID = null;
|
||||
protected SchemedID schemedID = null;
|
||||
protected String tradingBusinessName = null;
|
||||
|
||||
public LegalOrganisation() {
|
||||
}
|
||||
|
||||
public LegalOrganisation(String ID, String scheme) {
|
||||
this.ID=ID;
|
||||
this.SchemeID=scheme;
|
||||
this.schemedID = new SchemedID(scheme, ID);
|
||||
}
|
||||
|
||||
public LegalOrganisation(SchemedID schemedID, String tradingBusinessName) {
|
||||
this.schemedID = schemedID;
|
||||
this.tradingBusinessName=tradingBusinessName;
|
||||
}
|
||||
|
||||
/***
|
||||
* XML parsing constructor
|
||||
* @param nodes the nodelist returned e.g. from xpath
|
||||
*/
|
||||
public LegalOrganisation(NodeList nodes) {
|
||||
if (nodes.getLength() > 0) {
|
||||
/*
|
||||
will parse sth like
|
||||
<ram:SpecifiedLegalOrganization>
|
||||
<ram:ID schemeID="0002">4711</ram:ID>
|
||||
<ram:TradingBusinessName>Test GmbH & Co.KG</ram:TradingBusinessName>
|
||||
</ram:SpecifiedLegalOrganization>
|
||||
*/
|
||||
for (int nodeIndex = 0; nodeIndex < nodes.getLength(); nodeIndex++) {
|
||||
Node currentItemNode = nodes.item(nodeIndex);
|
||||
if (currentItemNode.getLocalName() != null) {
|
||||
if (currentItemNode.getLocalName().equals("GlobalID")) {
|
||||
if (currentItemNode.getAttributes().getNamedItem("schemeID") != null) {
|
||||
SchemedID gid = new SchemedID().setScheme(currentItemNode.getAttributes().getNamedItem("schemeID").getNodeValue()).setId(currentItemNode.getTextContent());
|
||||
this.setSchemedID(gid);
|
||||
}
|
||||
}
|
||||
if (currentItemNode.getLocalName().equals("TradingBusinessName")) {
|
||||
setTradingBusinessName(currentItemNode.getFirstChild().getNodeValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getID() {
|
||||
return ID;
|
||||
public SchemedID getSchemedID() {
|
||||
return this.schemedID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSchemeID() {
|
||||
return SchemeID;
|
||||
public String getTradingBusinessName() {
|
||||
return this.tradingBusinessName;
|
||||
}
|
||||
|
||||
public LegalOrganisation setID(String id) {
|
||||
this.ID=id;
|
||||
public LegalOrganisation setSchemedID(SchemedID schemedID) {
|
||||
this.schemedID = schemedID;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LegalOrganisation setSchemeID(String scheme) {
|
||||
SchemeID=scheme;
|
||||
public LegalOrganisation setTradingBusinessName(String tradingBusinessName) {
|
||||
this.tradingBusinessName = tradingBusinessName;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,20 +1,37 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import org.mustangproject.ZUGFeRD.IDesignatedProductClassification;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableProduct;
|
||||
import org.mustangproject.util.NodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/***
|
||||
* describes a product, good or service used in an invoice item line
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
|
||||
public class Product implements IZUGFeRDExportableProduct {
|
||||
protected String unit, name, description, sellerAssignedID, buyerAssignedID;
|
||||
protected String unit, name, sellerAssignedID, buyerAssignedID;
|
||||
protected String description="";
|
||||
protected String taxExemptionReason=null;
|
||||
protected String taxCategoryCode=null;
|
||||
protected BigDecimal VATPercent;
|
||||
protected boolean isReverseCharge = false;
|
||||
protected boolean isIntraCommunitySupply = false;
|
||||
protected SchemedID globalId = null;
|
||||
protected String countryOfOrigin = null;
|
||||
protected HashMap<String, String> attributes = new HashMap<>();
|
||||
protected List<IDesignatedProductClassification> classifications = new ArrayList<>();
|
||||
|
||||
/***
|
||||
* default constructor
|
||||
@@ -30,6 +47,42 @@ public class Product implements IZUGFeRDExportableProduct {
|
||||
this.VATPercent = VATPercent;
|
||||
}
|
||||
|
||||
public Product(Node node) {
|
||||
NodeMap nodeMap = new NodeMap(node);
|
||||
|
||||
nodeMap.getNode("GlobalID").ifPresent(idNode -> {
|
||||
if (idNode.hasAttributes()
|
||||
&& idNode.getAttributes().getNamedItem("schemeID") != null) {
|
||||
globalId = new SchemedID()
|
||||
.setScheme(idNode.getAttributes().getNamedItem("schemeID").getNodeValue())
|
||||
.setId(idNode.getTextContent());
|
||||
}
|
||||
});
|
||||
|
||||
nodeMap.getAsString("SellerAssignedID").ifPresent(this::setSellerAssignedID);
|
||||
nodeMap.getAsString("BuyerAssignedID").ifPresent(this::setBuyerAssignedID);
|
||||
nodeMap.getAsString("Name").ifPresent(this::setName);
|
||||
nodeMap.getAsString("Description").ifPresent(this::setDescription);
|
||||
|
||||
nodeMap.getAsNodeMap("ApplicableProductCharacteristic").ifPresent(apcNodes -> {
|
||||
String key = apcNodes.getAsStringOrNull("Description");
|
||||
String value = apcNodes.getAsStringOrNull("Value");
|
||||
if (key != null && value != null) {
|
||||
if (attributes == null) {
|
||||
attributes = new HashMap<>();
|
||||
}
|
||||
attributes.put(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
nodeMap.getAsNodeMap("DesignatedProductClassification").ifPresent(dpcNodes -> {
|
||||
String className = dpcNodes.getAsStringOrNull("ClassName");
|
||||
dpcNodes.getNode("ClassCode").map(ClassCode::fromNode).ifPresent(classCode ->
|
||||
classifications.add(new DesignatedProductClassification(classCode, className)));
|
||||
});
|
||||
|
||||
nodeMap.getAsString("OriginTradeCounty").ifPresent(this::setCountryOfOrigin);
|
||||
}
|
||||
|
||||
/***
|
||||
* empty constructor
|
||||
@@ -62,7 +115,49 @@ public class Product implements IZUGFeRDExportableProduct {
|
||||
return this;
|
||||
}
|
||||
|
||||
/***
|
||||
*
|
||||
* @return e.g. intra-commnunity supply or small business
|
||||
*/
|
||||
@Override
|
||||
public String getTaxExemptionReason() {
|
||||
return taxExemptionReason;
|
||||
}
|
||||
|
||||
/***
|
||||
*
|
||||
* @param taxExemptionReasonText String e.g. Kleinunternehmer gemäß §19 UStG https://github.com/ZUGFeRD/mustangproject/issues/463
|
||||
* @return fluent setter
|
||||
*/
|
||||
public Product setTaxExemptionReason(String taxExemptionReasonText) {
|
||||
taxExemptionReason = taxExemptionReasonText;
|
||||
return this;
|
||||
}
|
||||
|
||||
/***
|
||||
*
|
||||
* @return e.g. S (normal tax), Z=zero rated, E (e.g. small business) or K (intrra community supply)
|
||||
*/
|
||||
@Override
|
||||
public String getTaxCategoryCode() {
|
||||
if (taxCategoryCode == null) {
|
||||
return IZUGFeRDExportableProduct.super.getTaxCategoryCode();
|
||||
}
|
||||
return taxCategoryCode;
|
||||
}
|
||||
|
||||
/***
|
||||
*
|
||||
* @param code e.g. S (normal tax), Z=zero rated, E (e.g. small business) or K (intrra community supply) see also https://github.com/ZUGFeRD/mustangproject/issues/463
|
||||
* @return fluent setter
|
||||
*/
|
||||
public Product setTaxCategoryCode(String code) {
|
||||
taxCategoryCode = code;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getSellerAssignedID() {
|
||||
return sellerAssignedID;
|
||||
}
|
||||
@@ -77,6 +172,7 @@ public class Product implements IZUGFeRDExportableProduct {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBuyerAssignedID() {
|
||||
return buyerAssignedID;
|
||||
}
|
||||
@@ -119,6 +215,8 @@ public class Product implements IZUGFeRDExportableProduct {
|
||||
public Product setIntraCommunitySupply() {
|
||||
isIntraCommunitySupply = true;
|
||||
setVATPercent(BigDecimal.ZERO);
|
||||
setTaxExemptionReason("Intra-community supply");
|
||||
setTaxCategoryCode("K");
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -183,4 +281,70 @@ public class Product implements IZUGFeRDExportableProduct {
|
||||
this.VATPercent = VATPercent;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCountryOfOrigin() {
|
||||
return this.countryOfOrigin;
|
||||
}
|
||||
|
||||
public Product setCountryOfOrigin(String countryOfOrigin) {
|
||||
this.countryOfOrigin = countryOfOrigin;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<String, String> getAttributes() {
|
||||
if (attributes.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
return this.attributes;
|
||||
}
|
||||
}
|
||||
|
||||
public Product setAttributes(Map<String, String> attributes) {
|
||||
this.attributes.clear();
|
||||
if (attributes != null) {
|
||||
this.attributes.putAll(attributes);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Product addAttribute(String name, String value ) {
|
||||
this.attributes.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IDesignatedProductClassification[] getClassifications() {
|
||||
if (classifications.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
return classifications.toArray(new IDesignatedProductClassification[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the current set of {@link IDesignatedProductClassification}s with a new set
|
||||
*
|
||||
* @param classifications the new set of classifications
|
||||
* @return the modified object
|
||||
*/
|
||||
public Product setClassifications(IDesignatedProductClassification[] classifications) {
|
||||
this.classifications.clear();
|
||||
if (classifications != null) {
|
||||
this.classifications.addAll(Arrays.asList(classifications));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link IDesignatedProductClassification} classification
|
||||
*
|
||||
* @param classification the classification
|
||||
* @return the modified object
|
||||
*/
|
||||
public Product addClassification(IDesignatedProductClassification classification) {
|
||||
this.classifications.add(classification);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import org.mustangproject.ZUGFeRD.IReferencedDocument;
|
||||
import org.mustangproject.util.NodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
public class ReferencedDocument implements IReferencedDocument {
|
||||
|
||||
@@ -58,4 +60,14 @@ public class ReferencedDocument implements IReferencedDocument {
|
||||
public String getReferenceTypeCode() {
|
||||
return referenceTypeCode;
|
||||
}
|
||||
|
||||
public static ReferencedDocument fromNode(Node node) {
|
||||
if (!node.hasChildNodes()) {
|
||||
return null;
|
||||
}
|
||||
NodeMap nodes = new NodeMap(node);
|
||||
return new ReferencedDocument(nodes.getAsStringOrNull("IssuerAssignedID"),
|
||||
nodes.getAsStringOrNull("TypeCode"),
|
||||
nodes.getAsStringOrNull("ReferenceTypeCode"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,4 +31,9 @@ public class SchemedID {
|
||||
setId(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SchemedID{scheme='" + scheme + "', id='" + id + "'}";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,29 +5,32 @@ import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableContact;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableTradeParty;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDLegalOrganisation;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDTradeSettlement;
|
||||
import org.mustangproject.ZUGFeRD.IZUGFeRDTradeSettlementDebit;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
/***
|
||||
* A organisation, i.e. usually a company
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
|
||||
protected String name, zip, street, location, country;
|
||||
protected String taxID = null, vatID = null;
|
||||
protected String ID = null;
|
||||
protected String description = null;
|
||||
protected String additionalAddress = null;
|
||||
protected String additionalAddressExtension = null;
|
||||
protected List<BankDetails> bankDetails = new ArrayList<>();
|
||||
protected List<IZUGFeRDTradeSettlementDebit> debitDetails = new ArrayList<>();
|
||||
protected List<DirectDebit> debitDetails = new ArrayList<>();
|
||||
protected Contact contact = null;
|
||||
protected LegalOrganisation legalOrg = null;
|
||||
protected SchemedID globalId = null;
|
||||
@@ -66,15 +69,15 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
//nodes.item(i).getTextContent())) {
|
||||
Node currentItemNode = nodes.item(nodeIndex);
|
||||
|
||||
if (nodes.item(nodeIndex).getLocalName() != null) {
|
||||
String debcurrentChild = nodes.item(nodeIndex).getLocalName();
|
||||
if (nodes.item(nodeIndex).getLocalName().equals("Party")) {
|
||||
if (currentItemNode.getLocalName() != null) {
|
||||
String currentUBLChild = currentItemNode.getLocalName();
|
||||
if (currentUBLChild.equals("Party")) {
|
||||
|
||||
NodeList party = nodes.item(nodeIndex).getChildNodes();
|
||||
NodeList party = currentItemNode.getChildNodes();
|
||||
for (int partyIndex = 0; partyIndex < party.getLength(); partyIndex++) {
|
||||
if (party.item(partyIndex).getLocalName() != null) {
|
||||
String debCN = party.item(partyIndex).getLocalName();
|
||||
if (party.item(partyIndex).getLocalName().equals("PartyName")) {
|
||||
String currentTopElementName = party.item(partyIndex).getLocalName();
|
||||
if (currentTopElementName.equals("PartyName")) {
|
||||
|
||||
NodeList partyName = party.item(partyIndex).getChildNodes();
|
||||
for (int partyNameIndex = 0; partyNameIndex < partyName.getLength(); partyNameIndex++) {
|
||||
@@ -87,7 +90,20 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (party.item(partyIndex).getLocalName().equals("PostalAddress")) {
|
||||
|
||||
// UBL only: formally it can have a name as well but BT27 party name *should* be stored in
|
||||
// so overwrite if one exists
|
||||
if (currentTopElementName.equals("PartyLegalEntity")) {
|
||||
NodeList legal = party.item(partyIndex).getChildNodes();
|
||||
for (int legalChildIndex = 0; legalChildIndex < legal.getLength(); legalChildIndex++) {
|
||||
if (legal.item(legalChildIndex).getLocalName() != null) {
|
||||
if (legal.item(legalChildIndex).getLocalName().equals("RegistrationName")) {
|
||||
setName(legal.item(legalChildIndex).getTextContent());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentTopElementName.equals("PostalAddress")) {
|
||||
|
||||
NodeList postal = party.item(partyIndex).getChildNodes();
|
||||
for (int postalChildIndex = 0; postalChildIndex < postal.getLength(); postalChildIndex++) {
|
||||
@@ -145,7 +161,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
}
|
||||
}
|
||||
|
||||
if (party.item(partyIndex).getLocalName().equals("Contact")) {
|
||||
if (currentTopElementName.equals("Contact")) {
|
||||
NodeList contact = party.item(partyIndex).getChildNodes();
|
||||
setContact(new Contact(contact));
|
||||
|
||||
@@ -156,19 +172,19 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
|
||||
}
|
||||
|
||||
if (nodes.item(nodeIndex).getLocalName().equals("GlobalID")) {
|
||||
if (currentUBLChild.equals("GlobalID")) {
|
||||
if (nodes.item(nodeIndex).getAttributes().getNamedItem("schemeID") != null) {
|
||||
SchemedID gid = new SchemedID().setScheme(nodes.item(nodeIndex).getAttributes().getNamedItem("schemeID").getNodeValue()).setId(nodes.item(nodeIndex).getTextContent());
|
||||
addGlobalID(gid);
|
||||
}
|
||||
|
||||
}
|
||||
if (nodes.item(nodeIndex).getLocalName().equals("DefinedTradeContact")) {
|
||||
if (currentUBLChild.equals("DefinedTradeContact")) {
|
||||
NodeList contact = nodes.item(nodeIndex).getChildNodes();
|
||||
setContact(new Contact(contact));
|
||||
}
|
||||
|
||||
if (nodes.item(nodeIndex).getLocalName().equals("PostalTradeAddress")) {
|
||||
if (currentUBLChild.equals("PostalTradeAddress")) {
|
||||
NodeList postal = nodes.item(nodeIndex).getChildNodes();
|
||||
for (int postalChildIndex = 0; postalChildIndex < postal.getLength(); postalChildIndex++) {
|
||||
if (postal.item(postalChildIndex).getLocalName() != null) {
|
||||
@@ -196,7 +212,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
|
||||
}
|
||||
|
||||
if (nodes.item(nodeIndex).getLocalName().equals("SpecifiedTaxRegistration")) {
|
||||
if (currentUBLChild.equals("SpecifiedTaxRegistration")) {
|
||||
NodeList taxChilds = nodes.item(nodeIndex).getChildNodes();
|
||||
for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) {
|
||||
if (taxChilds.item(taxChildIndex).getLocalName() != null) {
|
||||
@@ -204,12 +220,10 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
if (taxChilds.item(taxChildIndex).getAttributes().getNamedItem("schemeID") != null) {
|
||||
Node firstChild = taxChilds.item(taxChildIndex).getFirstChild();
|
||||
if (firstChild != null) {
|
||||
if (taxChilds.item(taxChildIndex).getAttributes()
|
||||
.getNamedItem("schemeID").getNodeValue().equals("VA")) {
|
||||
if (taxChilds.item(taxChildIndex).getAttributes().getNamedItem("schemeID").getNodeValue().equals("VA")) {
|
||||
setVATID(firstChild.getNodeValue());
|
||||
}
|
||||
if (taxChilds.item(taxChildIndex).getAttributes()
|
||||
.getNamedItem("schemeID").getNodeValue().equals("FC")) {
|
||||
if (taxChilds.item(taxChildIndex).getAttributes().getNamedItem("schemeID").getNodeValue().equals("FC")) {
|
||||
setTaxID(firstChild.getNodeValue());
|
||||
}
|
||||
}
|
||||
@@ -289,8 +303,8 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
|
||||
for (int nodeIndex = 0; nodeIndex < nodes.getLength(); nodeIndex++) {
|
||||
//nodes.item(i).getTextContent())) {
|
||||
String debLN = nodes.item(nodeIndex).getLocalName();
|
||||
if (nodes.item(nodeIndex).getLocalName().equals("Party")) {
|
||||
String topElementName = nodes.item(nodeIndex).getLocalName();
|
||||
if (topElementName.equals("Party")) {
|
||||
// take one step back and parse from top
|
||||
parseFromUBL(nodes);
|
||||
return;
|
||||
@@ -299,15 +313,26 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
NodeList itemChilds = currentItemNode.getChildNodes();
|
||||
for (int itemChildIndex = 0; itemChildIndex < itemChilds.getLength(); itemChildIndex++) {
|
||||
if (itemChilds.item(itemChildIndex).getLocalName() != null) {
|
||||
if (itemChilds.item(itemChildIndex).getLocalName().equals("ID")) {
|
||||
setID(itemChilds.item(itemChildIndex).getTextContent());
|
||||
}
|
||||
if (itemChilds.item(itemChildIndex).getLocalName().equals("Name")) {
|
||||
setName(itemChilds.item(itemChildIndex).getTextContent());
|
||||
}
|
||||
|
||||
if (itemChilds.item(itemChildIndex).getLocalName().equals("Description")) {
|
||||
setDescription(itemChilds.item(itemChildIndex).getTextContent());
|
||||
}
|
||||
if (itemChilds.item(itemChildIndex).getLocalName().equals("GlobalID")) {
|
||||
if (itemChilds.item(itemChildIndex).getAttributes().getNamedItem("schemeID") != null) {
|
||||
SchemedID gid = new SchemedID().setScheme(itemChilds.item(itemChildIndex).getAttributes().getNamedItem("schemeID").getNodeValue()).setId(itemChilds.item(itemChildIndex).getTextContent());
|
||||
addGlobalID(gid);
|
||||
}
|
||||
}
|
||||
if (itemChilds.item(itemChildIndex).getLocalName().equals("SpecifiedLegalOrganization")) {
|
||||
NodeList organization = itemChilds.item(itemChildIndex).getChildNodes();
|
||||
setLegalOrganisation(new LegalOrganisation(organization));
|
||||
}
|
||||
if (itemChilds.item(itemChildIndex).getLocalName().equals("DefinedTradeContact")) {
|
||||
NodeList contact = itemChilds.item(itemChildIndex).getChildNodes();
|
||||
setContact(new Contact(contact));
|
||||
@@ -359,12 +384,10 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
if (taxChilds.item(taxChildIndex).getAttributes().getNamedItem("schemeID") != null) {
|
||||
Node firstChild = taxChilds.item(taxChildIndex).getFirstChild();
|
||||
if (firstChild != null) {
|
||||
if (taxChilds.item(taxChildIndex).getAttributes()
|
||||
.getNamedItem("schemeID").getNodeValue().equals("VA")) {
|
||||
if (taxChilds.item(taxChildIndex).getAttributes().getNamedItem("schemeID").getNodeValue().equals("VA")) {
|
||||
setVATID(firstChild.getNodeValue());
|
||||
}
|
||||
if (taxChilds.item(taxChildIndex).getAttributes()
|
||||
.getNamedItem("schemeID").getNodeValue().equals("FC")) {
|
||||
if (taxChilds.item(taxChildIndex).getAttributes().getNamedItem("schemeID").getNodeValue().equals("FC")) {
|
||||
setTaxID(firstChild.getNodeValue());
|
||||
}
|
||||
}
|
||||
@@ -404,6 +427,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public String getUriUniversalCommunicationID() {
|
||||
if (uriUniversalCommunicationId != null) {
|
||||
return uriUniversalCommunicationId.getID();
|
||||
@@ -413,6 +437,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public String getUriUniversalCommunicationIDScheme() {
|
||||
if (uriUniversalCommunicationId != null) {
|
||||
return uriUniversalCommunicationId.getScheme();
|
||||
@@ -425,6 +450,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
/***
|
||||
* sets the email of the organization (not the one of the contact person)
|
||||
* (while setEmail has to be defined here getEmail comes from IZUGFeRDExportableTradeParty)
|
||||
* @param eMail address of institution (not contact)
|
||||
* @return fluent setter
|
||||
*/
|
||||
public TradeParty setEmail(String eMail) {
|
||||
@@ -476,17 +502,26 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* (optional)
|
||||
*
|
||||
* @param debitDetail e.g. containing IBAN and mandate
|
||||
* @return fluent setter
|
||||
*/
|
||||
public TradeParty addDebitDetails(IZUGFeRDTradeSettlementDebit debitDetail) {
|
||||
public TradeParty addDebitDetails(DirectDebit debitDetail) {
|
||||
debitDetails.add(debitDetail);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* primarily for invoiceimporter and JSON
|
||||
* @return the list of sepa mandates
|
||||
*/
|
||||
public List<DirectDebit> getDebitDetails() {
|
||||
return debitDetails;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IZUGFeRDLegalOrganisation getLegalOrganisation() {
|
||||
return legalOrg;
|
||||
@@ -557,6 +592,26 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
*
|
||||
* @return String the description, e.g. if it's a vat exempt company
|
||||
*/
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* required, usually done in the constructor: the complete name of the organisation
|
||||
* @param description human readable description
|
||||
* @return fluent setter
|
||||
*/
|
||||
public TradeParty setDescription(String description) {
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public String getZIP() {
|
||||
return zip;
|
||||
}
|
||||
@@ -626,13 +681,12 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
|
||||
return contact;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public IZUGFeRDTradeSettlement[] getAsTradeSettlement() {
|
||||
if (bankDetails.isEmpty() && debitDetails.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
List<IZUGFeRDTradeSettlement> tradeSettlements = Stream.concat(bankDetails.stream(), debitDetails.stream())
|
||||
.map(IZUGFeRDTradeSettlement.class::cast)
|
||||
.collect(Collectors.toList());
|
||||
List<IZUGFeRDTradeSettlement> tradeSettlements = Stream.concat(bankDetails.stream(), debitDetails.stream()).map(IZUGFeRDTradeSettlement.class::cast).collect(Collectors.toList());
|
||||
|
||||
IZUGFeRDTradeSettlement[] result = new IZUGFeRDTradeSettlement[tradeSettlements.size()];
|
||||
for (int i = 0; i < tradeSettlements.size(); i++) {
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
package org.mustangproject;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.AbstractList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.RandomAccess;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.dom4j.io.XMLWriter;
|
||||
import org.mustangproject.ZUGFeRD.ZUGFeRDDateFormat;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
public class XMLTools extends XMLWriter {
|
||||
@Override
|
||||
@@ -24,34 +21,11 @@ public class XMLTools extends XMLWriter {
|
||||
@Override
|
||||
public String escapeElementEntities(String s) {
|
||||
return super.escapeElementEntities(s);
|
||||
|
||||
}
|
||||
|
||||
public static List<Node> asList(NodeList n) {
|
||||
return n.getLength() == 0 ?
|
||||
Collections.<Node>emptyList() : new NodeListWrapper(n);
|
||||
}
|
||||
|
||||
static final class NodeListWrapper extends AbstractList<Node>
|
||||
implements RandomAccess {
|
||||
private final NodeList list;
|
||||
|
||||
NodeListWrapper(NodeList l) {
|
||||
list = l;
|
||||
}
|
||||
|
||||
public Node get(int index) {
|
||||
return list.item(index);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return list.getLength();
|
||||
}
|
||||
}
|
||||
|
||||
public static String nDigitFormat(BigDecimal value, int scale) {
|
||||
/*
|
||||
* I needed 123,45, locale independent.I tried
|
||||
* I needed 123.45, locale independent.I tried
|
||||
* NumberFormat.getCurrencyInstance().format( 12345.6789 ); but that is locale
|
||||
* specific.I also tried DecimalFormat df = new DecimalFormat( "0,00" );
|
||||
* df.setDecimalSeparatorAlwaysShown(true); df.setGroupingUsed(false);
|
||||
@@ -69,7 +43,105 @@ public class XMLTools extends XMLWriter {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the value of an node
|
||||
*
|
||||
* @param node the Node to get the value from
|
||||
* @return A String or empty String, if no value was found
|
||||
*/
|
||||
public static String getNodeValue(Node node) {
|
||||
if (node != null && node.getFirstChild() != null) {
|
||||
return node.getFirstChild().getNodeValue();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* tries to convert a String to BigDecimal.
|
||||
*
|
||||
* @param nodeValue The value as String
|
||||
* @return a BigDecimal with the value provides as String or a BigDecimal with value 0.00 if an error occurs
|
||||
*/
|
||||
public static BigDecimal tryBigDecimal(String nodeValue) {
|
||||
try {
|
||||
return new BigDecimal(nodeValue);
|
||||
} catch (final Exception e) {
|
||||
try {
|
||||
return BigDecimal.valueOf(Float.valueOf(nodeValue));
|
||||
} catch (final Exception ex) {
|
||||
return new BigDecimal("0.00");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* tries to convert a Node to a BigDecimal.
|
||||
*
|
||||
* @param node The value as String
|
||||
* @return a BigDecimal with the value provides as String or a BigDecimal with value 0.00 if an error occurs
|
||||
*/
|
||||
public static BigDecimal tryBigDecimal(Node node) {
|
||||
final String nodeValue = XMLTools.getNodeValue(node);
|
||||
if (nodeValue.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return XMLTools.tryBigDecimal(nodeValue);
|
||||
}
|
||||
/***
|
||||
* formats a number so that at least minDecimals are displayed but at the maximum maxDecimals are there, i.e.
|
||||
* cuts potential 0s off the end until minDecimals
|
||||
* @param value
|
||||
* @param maxDecimals number of maximal scale
|
||||
* @param minDecimals number of minimal scale
|
||||
* @return value as String with decimals in the specified range
|
||||
*/
|
||||
public static String nDigitFormatDecimalRange(BigDecimal value, int maxDecimals, int minDecimals) {
|
||||
if ((maxDecimals<minDecimals)||(maxDecimals<0)||(minDecimals<0)) {
|
||||
throw new IllegalArgumentException("Invalid scale range provided");
|
||||
}
|
||||
int curDecimals=maxDecimals;
|
||||
while ( (curDecimals>minDecimals) && (value.setScale(curDecimals, RoundingMode.HALF_UP).compareTo(value.setScale(curDecimals-1, RoundingMode.HALF_UP))==0)) {
|
||||
curDecimals--;
|
||||
}
|
||||
return value.setScale(curDecimals, RoundingMode.HALF_UP).toPlainString();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* returns a util.Date from a 102 String yyyymmdd in a node
|
||||
* @param node the node
|
||||
* @return a util.Date, or null, if not parseable
|
||||
*/
|
||||
public static Date tryDate(Node node) {
|
||||
final String nodeValue = XMLTools.getNodeValue(node);
|
||||
if (nodeValue.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return tryDate(nodeValue);
|
||||
}
|
||||
|
||||
/***
|
||||
* returns a util.Date from a 102 String yyyymmdd
|
||||
* @param toParse the string
|
||||
* @return a util.Date, or null, if not parseable
|
||||
*/
|
||||
public static Date tryDate(String toParse) {
|
||||
final SimpleDateFormat formatter = ZUGFeRDDateFormat.DATE.getFormatter();
|
||||
try {
|
||||
return formatter.parse(toParse);
|
||||
} catch (final Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* relplaces some entities like < , > and & with their escaped pendant like <
|
||||
* @param s the string
|
||||
* @return the "safe" string
|
||||
*/
|
||||
public static String encodeXML(CharSequence s) {
|
||||
if (s == null) {
|
||||
return "";
|
||||
@@ -120,36 +192,6 @@ public class XMLTools extends XMLWriter {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the Byte Order Mark size and thus allows to skips over a BOM
|
||||
* at the beginning of the given ByteArrayInputStream, if one exists.
|
||||
*
|
||||
* @param is the ByteArrayInputStream used
|
||||
* @throws IOException if can not be read from is
|
||||
* @see <a href="https://www.w3.org/TR/xml/#sec-guessing">Autodetection of Character Encodings</a>
|
||||
*
|
||||
public static int guessBOMSize(ByteArrayInputStream is) throws IOException {
|
||||
byte[] pad = new byte[4];
|
||||
is.read(pad);
|
||||
is.reset();
|
||||
int test2 = ((pad[0] & 0xFF) << 8) | (pad[1] & 0xFF);
|
||||
int test3 = ((test2 & 0xFFFF) << 8) | (pad[2] & 0xFF);
|
||||
int test4 = ((test3 & 0xFFFFFF) << 8) | (pad[3] & 0xFF);
|
||||
//
|
||||
if (test4 == 0x0000FEFF || test4 == 0xFFFE0000 || test4 == 0x0000FFFE || test4 == 0xFEFF0000) {
|
||||
// UCS-4: BOM takes 4 bytes
|
||||
return 4;
|
||||
} else if (test3 == 0xEFBBFF) {
|
||||
// UTF-8: BOM takes 3 bytes
|
||||
return 3;
|
||||
} else if (test2 == 0xFEFF || test2 == 0xFFFE) {
|
||||
// UTF-16: BOM takes 2 bytes
|
||||
return 2;
|
||||
}
|
||||
return 0;
|
||||
}*/
|
||||
|
||||
/***
|
||||
* removes utf8 byte order marks from byte arrays, in case one is there
|
||||
* @param zugferdRaw the CII XML
|
||||
@@ -157,6 +199,7 @@ public class XMLTools extends XMLWriter {
|
||||
*/
|
||||
public static byte[] removeBOM(byte[] zugferdRaw) {
|
||||
final byte[] zugferdData;
|
||||
// This handles the UTF-8 BOM
|
||||
if ((zugferdRaw[0] == (byte) 0xEF) && (zugferdRaw[1] == (byte) 0xBB) && (zugferdRaw[2] == (byte) 0xBF)) {
|
||||
// I don't like BOMs, lets remove it
|
||||
zugferdData = new byte[zugferdRaw.length - 3];
|
||||
@@ -168,20 +211,19 @@ public class XMLTools extends XMLWriter {
|
||||
}
|
||||
|
||||
public static byte[] getBytesFromStream(InputStream fileinput) throws IOException {
|
||||
|
||||
// we're on java 8 so we cant use inputstream.readallbytes
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
|
||||
int nRead;
|
||||
byte[] data = new byte[16384];
|
||||
BufferedInputStream bufferedInput=new BufferedInputStream(fileinput);
|
||||
|
||||
while ((nRead = bufferedInput.read(data, 0, data.length)) != -1) {
|
||||
buffer.write(data, 0, nRead);
|
||||
}
|
||||
return buffer.toByteArray();
|
||||
|
||||
// end of polyfill
|
||||
return IOUtils.toByteArray (fileinput);
|
||||
}
|
||||
|
||||
|
||||
public static String trimOrNull(Node node) {
|
||||
if (node != null) {
|
||||
String textContent = node.getTextContent();
|
||||
if (textContent != null) {
|
||||
return textContent.trim();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -22,18 +22,18 @@ package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import static org.mustangproject.ZUGFeRD.ZUGFeRDDateFormat.DATE;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.mustangproject.*;
|
||||
import org.mustangproject.EStandard;
|
||||
import org.mustangproject.FileAttachment;
|
||||
import org.mustangproject.Invoice;
|
||||
import org.mustangproject.XMLTools;
|
||||
|
||||
public class DAPullProvider extends ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
public class DAPullProvider extends ZUGFeRD2PullProvider {
|
||||
|
||||
protected IExportableTransaction trans;
|
||||
private String paymentTermsDescription;
|
||||
protected Profile profile = Profiles.getByName(EStandard.despatchadvice,"pilot", 1);
|
||||
|
||||
|
||||
@@ -249,13 +249,9 @@ public class DAPullProvider extends ZUGFeRD2PullProvider implements IXMLProvider
|
||||
+ "</SCRDMCCBDACIDAMessageStructure>";
|
||||
|
||||
final byte[] zugferdRaw;
|
||||
try {
|
||||
zugferdRaw = xml.getBytes("UTF-8");
|
||||
zugferdRaw = xml.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
zugferdData = XMLTools.removeBOM(zugferdRaw);
|
||||
} catch (final UnsupportedEncodingException e) {
|
||||
Logger.getLogger(OXPullProvider.class.getName()).log(Level.SEVERE, null, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -19,30 +19,34 @@
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
|
||||
import org.apache.pdfbox.preflight.PreflightDocument;
|
||||
import org.apache.pdfbox.preflight.exception.ValidationException;
|
||||
import org.apache.pdfbox.preflight.parser.PreflightParser;
|
||||
|
||||
import javax.activation.DataSource;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class DXExporterFromA1 extends DXExporterFromA3 implements IZUGFeRDExporter {
|
||||
import org.apache.pdfbox.preflight.PreflightDocument;
|
||||
import org.apache.pdfbox.preflight.ValidationResult;
|
||||
import org.apache.pdfbox.preflight.exception.ValidationException;
|
||||
import org.apache.pdfbox.preflight.parser.PreflightParser;
|
||||
|
||||
import jakarta.activation.DataSource;
|
||||
|
||||
public class DXExporterFromA1 extends DXExporterFromA3 {
|
||||
protected boolean ignorePDFAErrors = false;
|
||||
|
||||
@Override
|
||||
public DXExporterFromA1 ignorePDFAErrors() {
|
||||
this.ignorePDFAErrors = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
private static boolean isValidA1(DataSource dataSource) throws IOException {
|
||||
return getPDFAParserValidationResult(new PreflightParser(dataSource));
|
||||
return getPDFAParserValidationResult(PreflightParserHelper.createPreflightParser (dataSource));
|
||||
}
|
||||
/***
|
||||
* internal helper function: get namespace for order-x
|
||||
* @param ver the delivery-x version
|
||||
* @return the URN of the namespace
|
||||
*/
|
||||
@Override
|
||||
public String getNamespaceForVersion(int ver) {
|
||||
// As of late 2022 the Delivery-X standard is not yet published. See specification:
|
||||
// Die digitale Ablösung des Papier-Lieferscheins, Version 1.1, April 2022
|
||||
@@ -55,6 +59,7 @@ public class DXExporterFromA1 extends DXExporterFromA3 implements IZUGFeRDExport
|
||||
* @param ver the ox version
|
||||
* @return the namespace prefix as string, without colon
|
||||
*/
|
||||
@Override
|
||||
public String getPrefixForVersion(int ver) {
|
||||
return "fx";
|
||||
}
|
||||
@@ -65,19 +70,19 @@ public class DXExporterFromA1 extends DXExporterFromA3 implements IZUGFeRDExport
|
||||
* NonSequentialParser. Some additional controls are present to check a set of
|
||||
* PDF/A requirements. (Stream length consistency, EOL after some Keyword...)
|
||||
*/
|
||||
parser.parse();// might add a Format.PDF_A1A as parameter and iterate through A1 and A3
|
||||
// might add a Format.PDF_A1A as parameter and iterate through A1 and A3
|
||||
|
||||
try (PreflightDocument document = parser.getPreflightDocument()) {
|
||||
try (PreflightDocument document = (PreflightDocument) parser.parse()) {
|
||||
/*
|
||||
* Once the syntax validation is done, the parser can provide a
|
||||
* PreflightDocument (that inherits from PDDocument) This document process the
|
||||
* end of PDF/A validation.
|
||||
*/
|
||||
|
||||
document.validate();
|
||||
ValidationResult res = document.validate();
|
||||
|
||||
// Get validation result
|
||||
return document.getResult().isValid();
|
||||
return res.isValid();
|
||||
} catch (ValidationException e) {
|
||||
/*
|
||||
* the parse method can throw a SyntaxValidationException if the PDF file can't
|
||||
@@ -89,13 +94,16 @@ public class DXExporterFromA1 extends DXExporterFromA3 implements IZUGFeRDExport
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DXExporterFromA1 setProfile(Profile p) {
|
||||
return (DXExporterFromA1)super.setProfile(p);
|
||||
}
|
||||
@Override
|
||||
public DXExporterFromA1 setProfile(String profileName) {
|
||||
return (DXExporterFromA1)super.setProfile(profileName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean ensurePDFIsValid(final DataSource dataSource) throws IOException {
|
||||
if (!ignorePDFAErrors && !isValidA1(dataSource)) {
|
||||
throw new IOException("File is not a valid PDF/A input file");
|
||||
@@ -108,31 +116,40 @@ public class DXExporterFromA1 extends DXExporterFromA3 implements IZUGFeRDExport
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public DXExporterFromA1 load(String pdfFilename) throws IOException {
|
||||
return (DXExporterFromA1) super.load(pdfFilename);
|
||||
}
|
||||
@Override
|
||||
public DXExporterFromA1 load(byte[] pdfBinary) throws IOException {
|
||||
return (DXExporterFromA1) super.load(pdfBinary);
|
||||
}
|
||||
@Override
|
||||
public DXExporterFromA1 load(InputStream pdfSource) throws IOException{
|
||||
return (DXExporterFromA1) super.load(pdfSource);
|
||||
}
|
||||
@Override
|
||||
public DXExporterFromA1 setCreator(String creator) {
|
||||
return (DXExporterFromA1) super.setCreator(creator);
|
||||
}
|
||||
@Override
|
||||
public DXExporterFromA1 setConformanceLevel(PDFAConformanceLevel newLevel) {
|
||||
return (DXExporterFromA1) super.setConformanceLevel(newLevel);
|
||||
}
|
||||
@Override
|
||||
public DXExporterFromA1 setProducer(String producer){
|
||||
return (DXExporterFromA1) super.setProducer(producer);
|
||||
}
|
||||
@Override
|
||||
public DXExporterFromA1 setZUGFeRDVersion(int version){
|
||||
return (DXExporterFromA1) super.setZUGFeRDVersion(version);
|
||||
}
|
||||
@Override
|
||||
public DXExporterFromA1 setXML(byte[] zugferdData) throws IOException{
|
||||
return (DXExporterFromA1) super.setXML(zugferdData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DXExporterFromA1 disableAutoClose(boolean disableAutoClose){
|
||||
return (DXExporterFromA1) super.disableAutoClose(disableAutoClose);
|
||||
}
|
||||
|
||||
@@ -20,16 +20,38 @@
|
||||
*/
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import org.apache.pdfbox.cos.*;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.xml.transform.TransformerException;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.cos.COSArray;
|
||||
import org.apache.pdfbox.cos.COSBase;
|
||||
import org.apache.pdfbox.cos.COSDictionary;
|
||||
import org.apache.pdfbox.cos.COSName;
|
||||
import org.apache.pdfbox.cos.COSObject;
|
||||
import org.apache.pdfbox.io.IOUtils;
|
||||
import org.apache.pdfbox.pdmodel.*;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
|
||||
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
|
||||
import org.apache.pdfbox.pdmodel.common.PDMetadata;
|
||||
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
|
||||
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
|
||||
import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDMarkInfo;
|
||||
import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot;
|
||||
import org.apache.pdfbox.pdmodel.graphics.color.PDOutputIntent;
|
||||
import org.apache.pdfbox.preflight.utils.ByteArrayDataSource;
|
||||
import org.apache.xmpbox.XMPMetadata;
|
||||
import org.apache.xmpbox.schema.AdobePDFSchema;
|
||||
import org.apache.xmpbox.schema.DublinCoreSchema;
|
||||
@@ -43,16 +65,13 @@ import org.apache.xmpbox.xml.XmpSerializer;
|
||||
import org.mustangproject.EStandard;
|
||||
import org.mustangproject.FileAttachment;
|
||||
|
||||
import javax.activation.DataSource;
|
||||
import javax.activation.FileDataSource;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import jakarta.activation.DataSource;
|
||||
import jakarta.activation.FileDataSource;
|
||||
|
||||
public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
|
||||
protected PDFAConformanceLevel conformanceLevel = PDFAConformanceLevel.UNICODE;
|
||||
protected ArrayList<FileAttachment> fileAttachments = new ArrayList<FileAttachment>();
|
||||
protected ArrayList<FileAttachment> fileAttachments = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* This flag controls whether or not the metadata is overwritten, or kind of merged.
|
||||
@@ -108,9 +127,6 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
protected String despatchAdviceDocumentType = "DESPATCHADVICE";
|
||||
|
||||
|
||||
private HashMap<String, byte[]> additionalXMLs = new HashMap<String, byte[]>();
|
||||
|
||||
|
||||
private boolean attachZUGFeRDHeaders = true;
|
||||
|
||||
/**
|
||||
@@ -119,6 +135,7 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
*
|
||||
* @param pdfFilename filename of an PDF/A1 compliant document
|
||||
*/
|
||||
@Override
|
||||
public DXExporterFromA3 load(String pdfFilename) throws IOException {
|
||||
|
||||
ensurePDFIsValid(new FileDataSource(pdfFilename));
|
||||
@@ -127,10 +144,12 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IXMLProvider getProvider() {
|
||||
return xmlProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DXExporterFromA3 setProfile(Profile p) {
|
||||
this.profile = p;
|
||||
if (xmlProvider != null) {
|
||||
@@ -139,6 +158,7 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DXExporterFromA3 setProfile(String profilename) {
|
||||
this.profile = Profiles.getByName(profilename);
|
||||
|
||||
@@ -148,6 +168,7 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DXExporterFromA3 addAdditionalFile(String name, byte[] content) {
|
||||
fileAttachments.add(new FileAttachment(name, "text/xml", "Supplement", content).setDescription("ZUGFeRD extension/additional data"));
|
||||
return this;
|
||||
@@ -161,9 +182,10 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
*
|
||||
* @param pdfBinary binary of a PDF/A1 compliant document
|
||||
*/
|
||||
@Override
|
||||
public DXExporterFromA3 load(byte[] pdfBinary) throws IOException {
|
||||
ensurePDFIsValid(new ByteArrayDataSource(new ByteArrayInputStream(pdfBinary)));
|
||||
doc = PDDocument.load(pdfBinary);
|
||||
doc = Loader.loadPDF(pdfBinary);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -171,10 +193,12 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachFile(FileAttachment file) {
|
||||
fileAttachments.add(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachFile(String filename, byte[] data, String mimetype, String relation) {
|
||||
FileAttachment fa = new FileAttachment(filename, mimetype, relation, data);
|
||||
fileAttachments.add(fa);
|
||||
@@ -185,6 +209,7 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
* @param ZUGFeRDfilename the pdf file name
|
||||
* @throws IOException if anything is wrong in the target location
|
||||
*/
|
||||
@Override
|
||||
public void export(String ZUGFeRDfilename) throws IOException {
|
||||
if (!documentPrepared) {
|
||||
prepareDocument();
|
||||
@@ -212,6 +237,7 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
* @param output the OutputStream
|
||||
* @throws IOException if anything is wrong in the OutputStream
|
||||
*/
|
||||
@Override
|
||||
public void export(OutputStream output) throws IOException {
|
||||
if (!documentPrepared) {
|
||||
prepareDocument();
|
||||
@@ -238,6 +264,7 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
* @param data the binary data of the file/attachment
|
||||
* @throws IOException if anything is wrong with filename
|
||||
*/
|
||||
@Override
|
||||
public void PDFAttachGenericFile(String filename, String relationship, String description,
|
||||
String subType, byte[] data) throws IOException {
|
||||
PDFAttachGenericFile(this.doc, filename, relationship, description, subType, data);
|
||||
@@ -255,6 +282,7 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
* @param data the binary data of the file/attachment
|
||||
* @throws IOException if anything is wrong with filename
|
||||
*/
|
||||
@Override
|
||||
public void PDFAttachGenericFile(PDDocument doc, String filename, String relationship, String description,
|
||||
String subType, byte[] data) throws IOException {
|
||||
fileAttached = true;
|
||||
@@ -274,7 +302,7 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
ef.setSize(data.length);
|
||||
ef.setCreationDate(new GregorianCalendar());
|
||||
|
||||
ef.setModDate(GregorianCalendar.getInstance());
|
||||
ef.setModDate(Calendar.getInstance());
|
||||
|
||||
fs.setEmbeddedFile(ef);
|
||||
|
||||
@@ -306,7 +334,7 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
doc.getDocumentCatalog().setNames(names);
|
||||
|
||||
// AF entry (Array) in catalog with the FileSpec
|
||||
COSBase AFEntry = (COSBase) doc.getDocumentCatalog().getCOSObject().getItem("AF");
|
||||
COSBase AFEntry = doc.getDocumentCatalog().getCOSObject().getItem("AF");
|
||||
if ((AFEntry == null)) {
|
||||
COSArray cosArray = new COSArray();
|
||||
cosArray.add(fs);
|
||||
@@ -332,6 +360,7 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
* @param zugferdData XML data to be set as a byte array (XML file in raw form).
|
||||
* @throws IOException (should not happen)
|
||||
*/
|
||||
@Override
|
||||
public DXExporterFromA3 setXML(byte[] zugferdData) throws IOException {
|
||||
CustomXMLProvider cus = new CustomXMLProvider();
|
||||
// As of late 2022 the Delivery-X standard is not yet published. See specification:
|
||||
@@ -353,10 +382,12 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
*
|
||||
* @param pdfSource source to read a PDF/A1 compliant document from
|
||||
*/
|
||||
@Override
|
||||
public DXExporterFromA3 load(InputStream pdfSource) throws IOException {
|
||||
return load(readAllBytes(pdfSource));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean ensurePDFIsValid(final DataSource dataSource) throws IOException {
|
||||
return true;
|
||||
}
|
||||
@@ -380,22 +411,26 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
* <p>
|
||||
* Feel free to pass "A" as new level if you know what you are doing :-)
|
||||
*/
|
||||
@Override
|
||||
public DXExporterFromA3 setConformanceLevel(PDFAConformanceLevel newLevel) {
|
||||
conformanceLevel = newLevel;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DXExporterFromA3 setCreator(String creator) {
|
||||
this.creator = creator;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DXExporterFromA3 setCreatorTool(String creatorTool) {
|
||||
this.creatorTool = creatorTool;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DXExporterFromA3 setProducer(String producer) {
|
||||
this.producer = producer;
|
||||
return this;
|
||||
@@ -415,6 +450,7 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DXExporterFromA3 setAttachZUGFeRDHeaders(boolean attachHeaders) {
|
||||
this.attachZUGFeRDHeaders = attachHeaders;
|
||||
return this;
|
||||
@@ -428,6 +464,7 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
*
|
||||
* @param metadata the PDFbox XMPMetadata object
|
||||
*/
|
||||
@Override
|
||||
protected void addXMP(XMPMetadata metadata) {
|
||||
|
||||
if (attachZUGFeRDHeaders) {
|
||||
@@ -459,11 +496,13 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
* <code>setZUGFeRDXMLData(byte[] zugferdData)</code>
|
||||
* @throws IOException if anything is wrong with already loaded PDF
|
||||
*/
|
||||
@Override
|
||||
public IExporter setTransaction(IExportableTransaction trans) throws IOException {
|
||||
this.trans = trans;
|
||||
return prepare();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IExporter prepare() throws IOException {
|
||||
prepareDocument();
|
||||
xmlProvider.generateXML(trans);
|
||||
@@ -483,6 +522,7 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
* Reads the XMPMetadata from the PDDocument, if it exists.
|
||||
* Otherwise creates XMPMetadata.
|
||||
*/
|
||||
@Override
|
||||
protected XMPMetadata getXmpMetadata() throws IOException {
|
||||
PDMetadata meta = doc.getDocumentCatalog().getMetadata();
|
||||
if ((meta != null) && (meta.getLength() > 0)) {
|
||||
@@ -496,6 +536,7 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
return XMPMetadata.createXMPMetadata();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] serializeXmpMetadata(XMPMetadata xmpMetadata) throws TransformerException {
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
new XmpSerializer().serialize(xmpMetadata, buffer, true); // see https://github.com/ZUGFeRD/mustangproject/issues/44
|
||||
@@ -506,6 +547,7 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
* Sets the producer if the overwrite flag is set or the producer is not already set.
|
||||
* Sets the PDFVersion to 1.4 if the field is empty.
|
||||
*/
|
||||
@Override
|
||||
protected void writeAdobePDFSchema(XMPMetadata xmp) {
|
||||
AdobePDFSchema pdf = getAdobePDFSchema(xmp);
|
||||
if (overwrite || isEmpty(pdf.getProducer()))
|
||||
@@ -516,6 +558,7 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
* Returns the AdobePDFSchema from the XMPMetadata if it exists.
|
||||
* If the overwrite flag is set or no AdobePDFSchema exists in the XMPMetadata, it is created, added and returned.
|
||||
*/
|
||||
@Override
|
||||
protected AdobePDFSchema getAdobePDFSchema(XMPMetadata xmp) {
|
||||
AdobePDFSchema pdf = xmp.getAdobePDFSchema();
|
||||
if (pdf != null)
|
||||
@@ -526,6 +569,7 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
return xmp.createAndAddAdobePDFSchema();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writePDFAIdentificationSchema(XMPMetadata xmp) {
|
||||
PDFAIdentificationSchema pdfaid = getPDFAIdentificationSchema(xmp);
|
||||
if (overwrite || isEmpty(pdfaid.getConformance())) {
|
||||
@@ -541,16 +585,18 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
pdfaid.setPart(3);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PDFAIdentificationSchema getPDFAIdentificationSchema(XMPMetadata xmp) {
|
||||
PDFAIdentificationSchema pdfaid = xmp.getPDFIdentificationSchema();
|
||||
PDFAIdentificationSchema pdfaid = xmp.getPDFAIdentificationSchema();
|
||||
if (pdfaid != null)
|
||||
if (overwrite)
|
||||
xmp.removeSchema(pdfaid);
|
||||
else
|
||||
return pdfaid;
|
||||
return xmp.createAndAddPFAIdentificationSchema();
|
||||
return xmp.createAndAddPDFAIdentificationSchema();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeDublinCoreSchema(XMPMetadata xmp) {
|
||||
DublinCoreSchema dc = getDublinCoreSchema(xmp);
|
||||
if (dc.getFormat() == null)
|
||||
@@ -574,6 +620,7 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DublinCoreSchema getDublinCoreSchema(XMPMetadata xmp) {
|
||||
DublinCoreSchema dc = xmp.getDublinCoreSchema();
|
||||
if (dc != null)
|
||||
@@ -584,14 +631,16 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
return xmp.createAndAddDublinCoreSchema();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeXMLBasicSchema(XMPMetadata xmp) {
|
||||
XMPBasicSchema xsb = getXmpBasicSchema(xmp);
|
||||
if (overwrite || isEmpty(xsb.getCreatorTool()) || "UnknownApplication".equals(xsb.getCreatorTool()))
|
||||
xsb.setCreatorTool(creatorTool);
|
||||
if (overwrite || xsb.getCreateDate() == null)
|
||||
xsb.setCreateDate(GregorianCalendar.getInstance());
|
||||
xsb.setCreateDate(Calendar.getInstance());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected XMPBasicSchema getXmpBasicSchema(XMPMetadata xmp) {
|
||||
XMPBasicSchema xsb = xmp.getXMPBasicSchema();
|
||||
if (xsb != null)
|
||||
@@ -602,6 +651,7 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
return xmp.createAndAddXMPBasicSchema();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeDocumentInformation() {
|
||||
String fullProducer = producer + " (via mustangproject.org " + Version.VERSION + ")";
|
||||
PDDocumentInformation info = doc.getDocumentInformation();
|
||||
@@ -624,6 +674,7 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
/**
|
||||
* Adds an OutputIntent and the sRGB color profile if no OutputIntent exist
|
||||
*/
|
||||
@Override
|
||||
protected void addSRGBOutputIntend() throws IOException {
|
||||
if (!doc.getDocumentCatalog().getOutputIntents().isEmpty()) {
|
||||
return;
|
||||
@@ -647,6 +698,7 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
/**
|
||||
* Adds a MarkInfo element to the PDF if it doesn't already exist and sets it as marked.
|
||||
*/
|
||||
@Override
|
||||
protected void setMarked() {
|
||||
PDDocumentCatalog catalog = doc.getDocumentCatalog();
|
||||
if (catalog.getMarkInfo() == null) {
|
||||
@@ -658,6 +710,7 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
/**
|
||||
* Adds a StructureTreeRoot element to the PDF if it doesn't already exist.
|
||||
*/
|
||||
@Override
|
||||
protected void addStructureTreeRoot() {
|
||||
if (doc.getDocumentCatalog().getStructureTreeRoot() == null) {
|
||||
doc.getDocumentCatalog().setStructureTreeRoot(new PDStructureTreeRoot());
|
||||
@@ -668,6 +721,7 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
/**
|
||||
* @return if pdf file will be automatically closed after adding ZF
|
||||
*/
|
||||
@Override
|
||||
public boolean isAutoCloseDisabled() {
|
||||
return disableAutoClose;
|
||||
}
|
||||
@@ -675,11 +729,13 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
/**
|
||||
* @param disableAutoClose prevent PDF file from being closed after adding ZF
|
||||
*/
|
||||
@Override
|
||||
public DXExporterFromA3 disableAutoClose(boolean disableAutoClose) {
|
||||
this.disableAutoClose = disableAutoClose;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setXMLProvider(IXMLProvider p) {
|
||||
this.xmlProvider = p;
|
||||
if (profile != null) {
|
||||
|
||||
@@ -20,31 +20,10 @@
|
||||
*/
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import org.apache.pdfbox.cos.*;
|
||||
import org.apache.pdfbox.io.IOUtils;
|
||||
import org.apache.pdfbox.pdmodel.*;
|
||||
import org.apache.pdfbox.pdmodel.common.PDMetadata;
|
||||
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
|
||||
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
|
||||
import org.apache.pdfbox.preflight.PreflightDocument;
|
||||
import org.apache.pdfbox.preflight.exception.ValidationException;
|
||||
import org.apache.pdfbox.preflight.parser.PreflightParser;
|
||||
import org.apache.pdfbox.preflight.utils.ByteArrayDataSource;
|
||||
import org.apache.xmpbox.XMPMetadata;
|
||||
import org.apache.xmpbox.schema.AdobePDFSchema;
|
||||
import org.apache.xmpbox.schema.DublinCoreSchema;
|
||||
import org.apache.xmpbox.schema.PDFAIdentificationSchema;
|
||||
import org.apache.xmpbox.schema.XMPBasicSchema;
|
||||
import org.apache.xmpbox.type.BadFieldValueException;
|
||||
import org.apache.xmpbox.xml.XmpSerializer;
|
||||
|
||||
import javax.activation.DataSource;
|
||||
import javax.activation.FileDataSource;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import java.io.*;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class EinLieferscheinExporter implements IExporter {
|
||||
IXMLProvider xmlProvider;
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* *********************************************************************
|
||||
* <p>
|
||||
* Copyright (c) 2024 Jan N. Klug
|
||||
* <p>
|
||||
* Use is subject to license terms.
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* <p>
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* <p>
|
||||
* **********************************************************************
|
||||
*/
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import org.mustangproject.ClassCode;
|
||||
|
||||
/**
|
||||
* A product classification that allows to describe a product. Classification codes are schemed by the available systems in UNTDID 7143.
|
||||
*/
|
||||
public interface IDesignatedProductClassification {
|
||||
/**
|
||||
* Classification code
|
||||
*
|
||||
* @return the classification code
|
||||
*/
|
||||
ClassCode getClassCode();
|
||||
|
||||
/**
|
||||
* an optional, human-readable description of the classifcation code
|
||||
*
|
||||
* @return the name or {@code null} if not set
|
||||
*/
|
||||
default String getClassName() { return null; }
|
||||
}
|
||||
@@ -140,6 +140,11 @@ public interface IExportableTransaction {
|
||||
return null;
|
||||
}
|
||||
|
||||
default IZUGFeRDCashDiscount[] getCashDiscounts() { return null; }
|
||||
|
||||
/***
|
||||
* @return the invoice line items with the positions
|
||||
*/
|
||||
IZUGFeRDExportableItem[] getZFItems();
|
||||
|
||||
/**
|
||||
@@ -445,11 +450,19 @@ public interface IExportableTransaction {
|
||||
*
|
||||
* @return the IZUGFeRDExportableTradeParty delivery address
|
||||
*/
|
||||
|
||||
default IZUGFeRDExportableTradeParty getDeliveryAddress() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/***
|
||||
* payee / payment receiver, if different from seller, ram:Payee (only supported for zf2)
|
||||
*
|
||||
* @return the IZUGFeRDExportableTradeParty payment receiver, if different from sellver
|
||||
*/
|
||||
default IZUGFeRDExportableTradeParty getPayee() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/***
|
||||
* specifies the document level delivery period, will be included in a
|
||||
* BillingSpecifiedPeriod element
|
||||
@@ -517,4 +530,7 @@ public interface IExportableTransaction {
|
||||
return null;
|
||||
}
|
||||
|
||||
default String getCreditorReferenceID() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
*********************************************************************** */
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
*/
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import org.mustangproject.ZUGFeRD.model.TaxCategoryCodeTypeConstants;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.mustangproject.ZUGFeRD.model.TaxCategoryCodeTypeConstants;
|
||||
|
||||
/**
|
||||
* The interface for allowances or charges, to be used by the pullprovider
|
||||
* @author AlexanderSchmidt
|
||||
@@ -44,6 +44,12 @@ public interface IZUGFeRDAllowanceCharge {
|
||||
*/
|
||||
String getReason();
|
||||
|
||||
/***
|
||||
* get the code for the allowance/charge
|
||||
* @return the code
|
||||
*/
|
||||
String getReasonCode();
|
||||
|
||||
/***
|
||||
* get the applicable tax percentage for the allowance/charge
|
||||
* @return the percentage
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
public interface IZUGFeRDCashDiscount {
|
||||
|
||||
|
||||
/***
|
||||
* @return this particular cash discount as cross industry invoice XML
|
||||
*/
|
||||
public String getAsCII();
|
||||
|
||||
/***
|
||||
* since EN16931 voted not to have (or even allow) cash discounts in their core invoice the german
|
||||
* XRechnung CIUS defined it's own proprietary format for a freetext field
|
||||
* @return this particular cash discount in proprietary xrechnung format
|
||||
*/
|
||||
public String getAsXRechnung();
|
||||
|
||||
}
|
||||
@@ -26,12 +26,13 @@ package org.mustangproject.ZUGFeRD;
|
||||
* @author jstaerk
|
||||
* */
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import org.mustangproject.Item;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
import org.mustangproject.Item;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
|
||||
@JsonDeserialize(as = Item.class)
|
||||
public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
|
||||
IZUGFeRDExportableProduct getProduct();
|
||||
@@ -91,12 +92,22 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
|
||||
|
||||
/***
|
||||
* the ID of an additionally referenced document for this item
|
||||
* @deprecated use {@link #getAdditionalReferences()} instead.
|
||||
* @return the id as string
|
||||
*/
|
||||
@Deprecated
|
||||
default String getAdditionalReferencedDocumentID() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/***
|
||||
* allows to specify multiple references (billing information)
|
||||
* @return the referenced documents
|
||||
*/
|
||||
default IReferencedDocument[] getAdditionalReferences() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* allows to specify multiple(!) referenced documents along with e.g. their typecodes
|
||||
@@ -140,6 +151,6 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
|
||||
*/
|
||||
default IZUGFeRDAllowanceCharge[] getItemTotalAllowances() {
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.mustangproject.ZUGFeRD.model.TaxCategoryCodeTypeConstants;
|
||||
|
||||
@@ -156,4 +157,19 @@ public interface IZUGFeRDExportableProduct {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
default String getCountryOfOrigin() {
|
||||
return null;
|
||||
}
|
||||
|
||||
default HashMap<String, String> getAttributes() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detailed information about the product
|
||||
*
|
||||
* @return an array containing the product classifications or {@code null} if not set
|
||||
*/
|
||||
default IDesignatedProductClassification[] getClassifications() { return null; }
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@
|
||||
*/
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import org.mustangproject.TradeParty;
|
||||
|
||||
/**
|
||||
* Mustangproject's ZUGFeRD implementation neccessary interface for ZUGFeRD exporter Licensed under the APLv2
|
||||
*
|
||||
@@ -114,12 +112,17 @@ public interface IZUGFeRDExportableTradeParty {
|
||||
}
|
||||
|
||||
/**
|
||||
* First and last name of the recipient
|
||||
* e.g. first and last name of the owner
|
||||
*
|
||||
* @return First and last name of the recipient
|
||||
* @return full name of the party
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* @return description, e.g. if it's a small company
|
||||
*/
|
||||
default String getDescription() { return null; }
|
||||
|
||||
/**
|
||||
* Postal code of the recipient
|
||||
*
|
||||
|
||||
@@ -18,11 +18,13 @@
|
||||
*********************************************************************** */
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import javax.activation.DataSource;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import jakarta.activation.DataSource;
|
||||
import org.mustangproject.FileAttachment;
|
||||
|
||||
public interface IZUGFeRDExporter extends Closeable, IExporter {
|
||||
/**
|
||||
* factory: loads a PDF file and returns an appropriate exporter
|
||||
@@ -63,6 +65,8 @@ public interface IZUGFeRDExporter extends Closeable, IExporter {
|
||||
public String getNamespaceForVersion(int ver);
|
||||
public String getPrefixForVersion(int ver) ;
|
||||
public IZUGFeRDExporter disableAutoClose(boolean disableAutoClose);
|
||||
public void attachFile(FileAttachment file);
|
||||
public void attachFile(String filename, byte[] data, String mimetype, String relation);
|
||||
public IXMLProvider getProvider();
|
||||
|
||||
}
|
||||
|
||||
@@ -18,22 +18,19 @@
|
||||
*********************************************************************** */
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import javax.activation.DataSource;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import org.mustangproject.SchemedID;
|
||||
|
||||
public interface IZUGFeRDLegalOrganisation {
|
||||
|
||||
/***
|
||||
*
|
||||
* @return the ID of the legal organisation
|
||||
*/
|
||||
public String getID();
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the scheme attribute of the legal organization=the type of the identification, e.g. 0002=Siren
|
||||
* @return the scheme attribute of the legal organization=the type of the identification, e.g. 0002=Siren, and its value
|
||||
*/
|
||||
public String getSchemeID();
|
||||
public SchemedID getSchemedID();
|
||||
|
||||
/***
|
||||
*
|
||||
* @return the TradingBusinessName of the legal organisation
|
||||
*/
|
||||
public String getTradingBusinessName();
|
||||
}
|
||||
|
||||
@@ -18,12 +18,15 @@
|
||||
*********************************************************************** */
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
public interface IZUGFeRDTradeSettlement {
|
||||
|
||||
/***
|
||||
* gets the applicableHeaderTradeSettlement
|
||||
* @return zf2 xml
|
||||
*/
|
||||
@JsonIgnore
|
||||
String getSettlementXML();
|
||||
|
||||
|
||||
@@ -31,6 +34,7 @@ public interface IZUGFeRDTradeSettlement {
|
||||
* gets the applicableHeaderTradePayment
|
||||
* @return zf2 xml
|
||||
*/
|
||||
@JsonIgnore
|
||||
default String getPaymentXML() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -22,15 +22,11 @@ import org.mustangproject.XMLTools;
|
||||
|
||||
public interface IZUGFeRDTradeSettlementDebit extends IZUGFeRDTradeSettlement {
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
default String getSettlementXML() {
|
||||
|
||||
|
||||
|
||||
String xml = "<ram:SpecifiedTradeSettlementPaymentMeans>"
|
||||
+ "<ram:TypeCode>59</ram:TypeCode>"
|
||||
+ "<ram:Information>SEPA direct debit</ram:Information>"
|
||||
+ "<ram:PayerPartyDebtorFinancialAccount>"
|
||||
+ "<ram:IBANID>" + XMLTools.encodeXML(getIBAN()) + "</ram:IBANID>"
|
||||
+ "</ram:PayerPartyDebtorFinancialAccount>";
|
||||
@@ -50,7 +46,6 @@ public interface IZUGFeRDTradeSettlementDebit extends IZUGFeRDTradeSettlement {
|
||||
*/
|
||||
String getIBAN();
|
||||
|
||||
|
||||
/***
|
||||
* @return sepa direct debit mandate reference
|
||||
*/
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*********************************************************************** */
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import org.mustangproject.XMLTools;
|
||||
|
||||
public interface IZUGFeRDTradeSettlementPayment extends IZUGFeRDTradeSettlement {
|
||||
@@ -27,6 +28,7 @@ public interface IZUGFeRDTradeSettlementPayment extends IZUGFeRDTradeSettlement
|
||||
*
|
||||
* @return payment information text
|
||||
*/
|
||||
@JsonIgnore
|
||||
default String getOwnPaymentInfoText() {
|
||||
return null;
|
||||
}
|
||||
@@ -59,6 +61,7 @@ public interface IZUGFeRDTradeSettlementPayment extends IZUGFeRDTradeSettlement
|
||||
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
default String getSettlementXML() {
|
||||
String accountNameStr="";
|
||||
if (getAccountName()!=null) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
/***
|
||||
* the linecalculator does the math within an item line, and e.g. calculates quantity*price.
|
||||
@@ -33,14 +34,18 @@ public class LineCalculator {
|
||||
}
|
||||
}
|
||||
|
||||
BigDecimal vatPercent = currentItem.getProduct().getVATPercent();
|
||||
if (vatPercent == null)
|
||||
BigDecimal vatPercent = null;
|
||||
if (currentItem.getProduct()!=null) {
|
||||
vatPercent = currentItem.getProduct().getVATPercent();
|
||||
}
|
||||
if (vatPercent == null) {
|
||||
vatPercent = BigDecimal.ZERO;
|
||||
}
|
||||
BigDecimal multiplicator = vatPercent.divide(BigDecimal.valueOf(100));
|
||||
priceGross = currentItem.getPrice(); // see https://github.com/ZUGFeRD/mustangproject/issues/159
|
||||
price = priceGross.subtract(allowance).add(charge);
|
||||
itemTotalNetAmount = currentItem.getQuantity().multiply(getPrice()).divide(currentItem.getBasisQuantity())
|
||||
.subtract(allowanceItemTotal).setScale(2, BigDecimal.ROUND_HALF_UP);
|
||||
itemTotalNetAmount = currentItem.getQuantity().multiply(getPrice()).divide(currentItem.getBasisQuantity(), 18, RoundingMode.HALF_UP)
|
||||
.subtract(allowanceItemTotal).setScale(2, RoundingMode.HALF_UP);
|
||||
itemTotalVATAmount = itemTotalNetAmount.multiply(multiplicator);
|
||||
|
||||
}
|
||||
|
||||
@@ -19,30 +19,34 @@
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
|
||||
import org.apache.pdfbox.preflight.PreflightDocument;
|
||||
import org.apache.pdfbox.preflight.exception.ValidationException;
|
||||
import org.apache.pdfbox.preflight.parser.PreflightParser;
|
||||
|
||||
import javax.activation.DataSource;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class OXExporterFromA1 extends OXExporterFromA3 implements IZUGFeRDExporter {
|
||||
import org.apache.pdfbox.preflight.PreflightDocument;
|
||||
import org.apache.pdfbox.preflight.ValidationResult;
|
||||
import org.apache.pdfbox.preflight.exception.ValidationException;
|
||||
import org.apache.pdfbox.preflight.parser.PreflightParser;
|
||||
|
||||
import jakarta.activation.DataSource;
|
||||
|
||||
public class OXExporterFromA1 extends OXExporterFromA3 {
|
||||
protected boolean ignorePDFAErrors = false;
|
||||
|
||||
@Override
|
||||
public OXExporterFromA1 ignorePDFAErrors() {
|
||||
this.ignorePDFAErrors = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
private static boolean isValidA1(DataSource dataSource) throws IOException {
|
||||
return getPDFAParserValidationResult(new PreflightParser(dataSource));
|
||||
return getPDFAParserValidationResult(PreflightParserHelper.createPreflightParser(dataSource));
|
||||
}
|
||||
/***
|
||||
* internal helper function: get namespace for order-x
|
||||
* @param ver the order-x version
|
||||
* @return the URN of the namespace
|
||||
*/
|
||||
@Override
|
||||
public String getNamespaceForVersion(int ver) {
|
||||
return "urn:factur-x:pdfa:CrossIndustryDocument:1p0#";
|
||||
}
|
||||
@@ -51,6 +55,7 @@ public class OXExporterFromA1 extends OXExporterFromA3 implements IZUGFeRDExport
|
||||
* @param ver the ox version
|
||||
* @return the namespace prefix as string, without colon
|
||||
*/
|
||||
@Override
|
||||
public String getPrefixForVersion(int ver) {
|
||||
return "fx";
|
||||
}
|
||||
@@ -61,19 +66,19 @@ public class OXExporterFromA1 extends OXExporterFromA3 implements IZUGFeRDExport
|
||||
* NonSequentialParser. Some additional controls are present to check a set of
|
||||
* PDF/A requirements. (Stream length consistency, EOL after some Keyword...)
|
||||
*/
|
||||
parser.parse();// might add a Format.PDF_A1A as parameter and iterate through A1 and A3
|
||||
// might add a Format.PDF_A1A as parameter and iterate through A1 and A3
|
||||
|
||||
try (PreflightDocument document = parser.getPreflightDocument()) {
|
||||
try (PreflightDocument document = (PreflightDocument) parser.parse()) {
|
||||
/*
|
||||
* Once the syntax validation is done, the parser can provide a
|
||||
* PreflightDocument (that inherits from PDDocument) This document process the
|
||||
* end of PDF/A validation.
|
||||
*/
|
||||
|
||||
document.validate();
|
||||
ValidationResult res = document.validate();
|
||||
|
||||
// Get validation result
|
||||
return document.getResult().isValid();
|
||||
return res.isValid();
|
||||
} catch (ValidationException e) {
|
||||
/*
|
||||
* the parse method can throw a SyntaxValidationException if the PDF file can't
|
||||
@@ -85,13 +90,16 @@ public class OXExporterFromA1 extends OXExporterFromA3 implements IZUGFeRDExport
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public OXExporterFromA1 setProfile(Profile p) {
|
||||
return (OXExporterFromA1)super.setProfile(p);
|
||||
}
|
||||
@Override
|
||||
public OXExporterFromA1 setProfile(String profileName) {
|
||||
return (OXExporterFromA1)super.setProfile(profileName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean ensurePDFIsValid(final DataSource dataSource) throws IOException {
|
||||
if (!ignorePDFAErrors && !isValidA1(dataSource)) {
|
||||
throw new IOException("File is not a valid PDF/A input file");
|
||||
@@ -104,31 +112,40 @@ public class OXExporterFromA1 extends OXExporterFromA3 implements IZUGFeRDExport
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public OXExporterFromA1 load(String pdfFilename) throws IOException {
|
||||
return (OXExporterFromA1) super.load(pdfFilename);
|
||||
}
|
||||
@Override
|
||||
public OXExporterFromA1 load(byte[] pdfBinary) throws IOException {
|
||||
return (OXExporterFromA1) super.load(pdfBinary);
|
||||
}
|
||||
@Override
|
||||
public OXExporterFromA1 load(InputStream pdfSource) throws IOException{
|
||||
return (OXExporterFromA1) super.load(pdfSource);
|
||||
}
|
||||
@Override
|
||||
public OXExporterFromA1 setCreator(String creator) {
|
||||
return (OXExporterFromA1) super.setCreator(creator);
|
||||
}
|
||||
@Override
|
||||
public OXExporterFromA1 setConformanceLevel(PDFAConformanceLevel newLevel) {
|
||||
return (OXExporterFromA1) super.setConformanceLevel(newLevel);
|
||||
}
|
||||
@Override
|
||||
public OXExporterFromA1 setProducer(String producer){
|
||||
return (OXExporterFromA1) super.setProducer(producer);
|
||||
}
|
||||
@Override
|
||||
public OXExporterFromA1 setZUGFeRDVersion(int version){
|
||||
return (OXExporterFromA1) super.setZUGFeRDVersion(version);
|
||||
}
|
||||
@Override
|
||||
public OXExporterFromA1 setXML(byte[] zugferdData) throws IOException{
|
||||
return (OXExporterFromA1) super.setXML(zugferdData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OXExporterFromA1 disableAutoClose(boolean disableAutoClose){
|
||||
return (OXExporterFromA1) super.disableAutoClose(disableAutoClose);
|
||||
}
|
||||
|
||||
@@ -20,20 +20,38 @@
|
||||
*/
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import org.apache.pdfbox.cos.*;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.xml.transform.TransformerException;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.cos.COSArray;
|
||||
import org.apache.pdfbox.cos.COSBase;
|
||||
import org.apache.pdfbox.cos.COSDictionary;
|
||||
import org.apache.pdfbox.cos.COSName;
|
||||
import org.apache.pdfbox.cos.COSObject;
|
||||
import org.apache.pdfbox.io.IOUtils;
|
||||
import org.apache.pdfbox.pdmodel.*;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
|
||||
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
|
||||
import org.apache.pdfbox.pdmodel.common.PDMetadata;
|
||||
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
|
||||
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
|
||||
import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDMarkInfo;
|
||||
import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot;
|
||||
import org.apache.pdfbox.pdmodel.font.PDCIDFontType2;
|
||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||
import org.apache.pdfbox.pdmodel.font.PDFontDescriptor;
|
||||
import org.apache.pdfbox.pdmodel.font.PDType0Font;
|
||||
import org.apache.pdfbox.pdmodel.graphics.color.PDOutputIntent;
|
||||
import org.apache.pdfbox.preflight.utils.ByteArrayDataSource;
|
||||
import org.apache.xmpbox.XMPMetadata;
|
||||
import org.apache.xmpbox.schema.AdobePDFSchema;
|
||||
import org.apache.xmpbox.schema.DublinCoreSchema;
|
||||
@@ -47,16 +65,13 @@ import org.apache.xmpbox.xml.XmpSerializer;
|
||||
import org.mustangproject.EStandard;
|
||||
import org.mustangproject.FileAttachment;
|
||||
|
||||
import javax.activation.DataSource;
|
||||
import javax.activation.FileDataSource;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import jakarta.activation.DataSource;
|
||||
import jakarta.activation.FileDataSource;
|
||||
|
||||
public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
|
||||
protected PDFAConformanceLevel conformanceLevel = PDFAConformanceLevel.UNICODE;
|
||||
protected ArrayList<FileAttachment> fileAttachments = new ArrayList<FileAttachment>();
|
||||
protected ArrayList<FileAttachment> fileAttachments = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* This flag controls whether or not the metadata is overwritten, or kind of merged.
|
||||
@@ -112,9 +127,6 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
protected String orderXDocumentType = "ORDER";
|
||||
|
||||
|
||||
private HashMap<String, byte[]> additionalXMLs = new HashMap<String, byte[]>();
|
||||
|
||||
|
||||
private boolean attachZUGFeRDHeaders = true;
|
||||
|
||||
/**
|
||||
@@ -123,6 +135,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
*
|
||||
* @param pdfFilename filename of an PDF/A1 compliant document
|
||||
*/
|
||||
@Override
|
||||
public OXExporterFromA3 load(String pdfFilename) throws IOException {
|
||||
|
||||
ensurePDFIsValid(new FileDataSource(pdfFilename));
|
||||
@@ -131,10 +144,12 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IXMLProvider getProvider() {
|
||||
return xmlProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OXExporterFromA3 setProfile(Profile p) {
|
||||
this.profile = p;
|
||||
if (xmlProvider != null) {
|
||||
@@ -143,6 +158,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OXExporterFromA3 setProfile(String profilename) {
|
||||
this.profile = Profiles.getByName(profilename);
|
||||
|
||||
@@ -152,6 +168,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OXExporterFromA3 addAdditionalFile(String name, byte[] content) {
|
||||
fileAttachments.add(new FileAttachment(name, "text/xml", "Supplement", content).setDescription("ZUGFeRD extension/additional data"));
|
||||
return this;
|
||||
@@ -165,9 +182,10 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
*
|
||||
* @param pdfBinary binary of a PDF/A1 compliant document
|
||||
*/
|
||||
@Override
|
||||
public OXExporterFromA3 load(byte[] pdfBinary) throws IOException {
|
||||
ensurePDFIsValid(new ByteArrayDataSource(new ByteArrayInputStream(pdfBinary)));
|
||||
doc = PDDocument.load(pdfBinary);
|
||||
doc = Loader.loadPDF(pdfBinary);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -175,10 +193,12 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachFile(FileAttachment file) {
|
||||
fileAttachments.add(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachFile(String filename, byte[] data, String mimetype, String relation) {
|
||||
FileAttachment fa = new FileAttachment(filename, mimetype, relation, data);
|
||||
fileAttachments.add(fa);
|
||||
@@ -189,6 +209,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
* @param ZUGFeRDfilename the pdf file name
|
||||
* @throws IOException if anything is wrong in the target location
|
||||
*/
|
||||
@Override
|
||||
public void export(String ZUGFeRDfilename) throws IOException {
|
||||
if (!documentPrepared) {
|
||||
prepareDocument();
|
||||
@@ -216,6 +237,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
* @param output the OutputStream
|
||||
* @throws IOException if anything is wrong in the OutputStream
|
||||
*/
|
||||
@Override
|
||||
public void export(OutputStream output) throws IOException {
|
||||
if (!documentPrepared) {
|
||||
prepareDocument();
|
||||
@@ -242,6 +264,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
* @param data the binary data of the file/attachment
|
||||
* @throws java.io.IOException if anything is wrong with filename
|
||||
*/
|
||||
@Override
|
||||
public void PDFAttachGenericFile(String filename, String relationship, String description,
|
||||
String subType, byte[] data) throws IOException {
|
||||
PDFAttachGenericFile(this.doc, filename, relationship, description, subType, data);
|
||||
@@ -259,6 +282,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
* @param data the binary data of the file/attachment
|
||||
* @throws IOException if anything is wrong with filename
|
||||
*/
|
||||
@Override
|
||||
public void PDFAttachGenericFile(PDDocument doc, String filename, String relationship, String description,
|
||||
String subType, byte[] data) throws IOException {
|
||||
fileAttached = true;
|
||||
@@ -278,7 +302,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
ef.setSize(data.length);
|
||||
ef.setCreationDate(new GregorianCalendar());
|
||||
|
||||
ef.setModDate(GregorianCalendar.getInstance());
|
||||
ef.setModDate(Calendar.getInstance());
|
||||
|
||||
fs.setEmbeddedFile(ef);
|
||||
|
||||
@@ -310,7 +334,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
doc.getDocumentCatalog().setNames(names);
|
||||
|
||||
// AF entry (Array) in catalog with the FileSpec
|
||||
COSBase AFEntry = (COSBase) doc.getDocumentCatalog().getCOSObject().getItem("AF");
|
||||
COSBase AFEntry = doc.getDocumentCatalog().getCOSObject().getItem("AF");
|
||||
if ((AFEntry == null)) {
|
||||
COSArray cosArray = new COSArray();
|
||||
cosArray.add(fs);
|
||||
@@ -336,6 +360,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
* @param zugferdData XML data to be set as a byte array (XML file in raw form).
|
||||
* @throws IOException (should not happen)
|
||||
*/
|
||||
@Override
|
||||
public OXExporterFromA3 setXML(byte[] zugferdData) throws IOException {
|
||||
CustomXMLProvider cus = new CustomXMLProvider();
|
||||
cus.setXML(zugferdData);
|
||||
@@ -351,10 +376,12 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
*
|
||||
* @param pdfSource source to read a PDF/A1 compliant document from
|
||||
*/
|
||||
@Override
|
||||
public OXExporterFromA3 load(InputStream pdfSource) throws IOException {
|
||||
return load(readAllBytes(pdfSource));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean ensurePDFIsValid(final DataSource dataSource) throws IOException {
|
||||
return true;
|
||||
}
|
||||
@@ -378,22 +405,26 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
* <p>
|
||||
* Feel free to pass "A" as new level if you know what you are doing :-)
|
||||
*/
|
||||
@Override
|
||||
public OXExporterFromA3 setConformanceLevel(PDFAConformanceLevel newLevel) {
|
||||
conformanceLevel = newLevel;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public OXExporterFromA3 setCreator(String creator) {
|
||||
this.creator = creator;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OXExporterFromA3 setCreatorTool(String creatorTool) {
|
||||
this.creatorTool = creatorTool;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OXExporterFromA3 setProducer(String producer) {
|
||||
this.producer = producer;
|
||||
return this;
|
||||
@@ -413,6 +444,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OXExporterFromA3 setAttachZUGFeRDHeaders(boolean attachHeaders) {
|
||||
this.attachZUGFeRDHeaders = attachHeaders;
|
||||
return this;
|
||||
@@ -426,6 +458,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
*
|
||||
* @param metadata the PDFbox XMPMetadata object
|
||||
*/
|
||||
@Override
|
||||
protected void addXMP(XMPMetadata metadata) {
|
||||
|
||||
if (attachZUGFeRDHeaders) {
|
||||
@@ -453,11 +486,13 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
* <code>setZUGFeRDXMLData(byte[] zugferdData)</code>
|
||||
* @throws IOException if anything is wrong with already loaded PDF
|
||||
*/
|
||||
@Override
|
||||
public IExporter setTransaction(IExportableTransaction trans) throws IOException {
|
||||
this.trans = trans;
|
||||
return prepare();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IExporter prepare() throws IOException {
|
||||
prepareDocument();
|
||||
xmlProvider.generateXML(trans);
|
||||
@@ -477,6 +512,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
* Reads the XMPMetadata from the PDDocument, if it exists.
|
||||
* Otherwise creates XMPMetadata.
|
||||
*/
|
||||
@Override
|
||||
protected XMPMetadata getXmpMetadata() throws IOException {
|
||||
PDMetadata meta = doc.getDocumentCatalog().getMetadata();
|
||||
if ((meta != null) && (meta.getLength() > 0)) {
|
||||
@@ -490,6 +526,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
return XMPMetadata.createXMPMetadata();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] serializeXmpMetadata(XMPMetadata xmpMetadata) throws TransformerException {
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
new XmpSerializer().serialize(xmpMetadata, buffer, true); // see https://github.com/ZUGFeRD/mustangproject/issues/44
|
||||
@@ -500,6 +537,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
* Sets the producer if the overwrite flag is set or the producer is not already set.
|
||||
* Sets the PDFVersion to 1.4 if the field is empty.
|
||||
*/
|
||||
@Override
|
||||
protected void writeAdobePDFSchema(XMPMetadata xmp) {
|
||||
AdobePDFSchema pdf = getAdobePDFSchema(xmp);
|
||||
if (overwrite || isEmpty(pdf.getProducer()))
|
||||
@@ -510,6 +548,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
* Returns the AdobePDFSchema from the XMPMetadata if it exists.
|
||||
* If the overwrite flag is set or no AdobePDFSchema exists in the XMPMetadata, it is created, added and returned.
|
||||
*/
|
||||
@Override
|
||||
protected AdobePDFSchema getAdobePDFSchema(XMPMetadata xmp) {
|
||||
AdobePDFSchema pdf = xmp.getAdobePDFSchema();
|
||||
if (pdf != null)
|
||||
@@ -520,6 +559,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
return xmp.createAndAddAdobePDFSchema();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writePDFAIdentificationSchema(XMPMetadata xmp) {
|
||||
PDFAIdentificationSchema pdfaid = getPDFAIdentificationSchema(xmp);
|
||||
if (overwrite || isEmpty(pdfaid.getConformance())) {
|
||||
@@ -535,16 +575,18 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
pdfaid.setPart(3);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PDFAIdentificationSchema getPDFAIdentificationSchema(XMPMetadata xmp) {
|
||||
PDFAIdentificationSchema pdfaid = xmp.getPDFIdentificationSchema();
|
||||
PDFAIdentificationSchema pdfaid = xmp.getPDFAIdentificationSchema();
|
||||
if (pdfaid != null)
|
||||
if (overwrite)
|
||||
xmp.removeSchema(pdfaid);
|
||||
else
|
||||
return pdfaid;
|
||||
return xmp.createAndAddPFAIdentificationSchema();
|
||||
return xmp.createAndAddPDFAIdentificationSchema();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeDublinCoreSchema(XMPMetadata xmp) {
|
||||
DublinCoreSchema dc = getDublinCoreSchema(xmp);
|
||||
if (dc.getFormat() == null)
|
||||
@@ -568,6 +610,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DublinCoreSchema getDublinCoreSchema(XMPMetadata xmp) {
|
||||
DublinCoreSchema dc = xmp.getDublinCoreSchema();
|
||||
if (dc != null)
|
||||
@@ -578,14 +621,16 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
return xmp.createAndAddDublinCoreSchema();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeXMLBasicSchema(XMPMetadata xmp) {
|
||||
XMPBasicSchema xsb = getXmpBasicSchema(xmp);
|
||||
if (overwrite || isEmpty(xsb.getCreatorTool()) || "UnknownApplication".equals(xsb.getCreatorTool()))
|
||||
xsb.setCreatorTool(creatorTool);
|
||||
if (overwrite || xsb.getCreateDate() == null)
|
||||
xsb.setCreateDate(GregorianCalendar.getInstance());
|
||||
xsb.setCreateDate(Calendar.getInstance());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected XMPBasicSchema getXmpBasicSchema(XMPMetadata xmp) {
|
||||
XMPBasicSchema xsb = xmp.getXMPBasicSchema();
|
||||
if (xsb != null)
|
||||
@@ -596,6 +641,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
return xmp.createAndAddXMPBasicSchema();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeDocumentInformation() {
|
||||
String fullProducer = producer + " (via mustangproject.org " + Version.VERSION + ")";
|
||||
PDDocumentInformation info = doc.getDocumentInformation();
|
||||
@@ -618,6 +664,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
/**
|
||||
* Adds an OutputIntent and the sRGB color profile if no OutputIntent exist
|
||||
*/
|
||||
@Override
|
||||
protected void addSRGBOutputIntend() throws IOException {
|
||||
if (!doc.getDocumentCatalog().getOutputIntents().isEmpty()) {
|
||||
return;
|
||||
@@ -641,6 +688,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
/**
|
||||
* Adds a MarkInfo element to the PDF if it doesn't already exist and sets it as marked.
|
||||
*/
|
||||
@Override
|
||||
protected void setMarked() {
|
||||
PDDocumentCatalog catalog = doc.getDocumentCatalog();
|
||||
if (catalog.getMarkInfo() == null) {
|
||||
@@ -652,6 +700,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
/**
|
||||
* Adds a StructureTreeRoot element to the PDF if it doesn't already exist.
|
||||
*/
|
||||
@Override
|
||||
protected void addStructureTreeRoot() {
|
||||
if (doc.getDocumentCatalog().getStructureTreeRoot() == null) {
|
||||
doc.getDocumentCatalog().setStructureTreeRoot(new PDStructureTreeRoot());
|
||||
@@ -662,6 +711,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
/**
|
||||
* @return if pdf file will be automatically closed after adding ZF
|
||||
*/
|
||||
@Override
|
||||
public boolean isAutoCloseDisabled() {
|
||||
return disableAutoClose;
|
||||
}
|
||||
@@ -669,11 +719,13 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
|
||||
/**
|
||||
* @param disableAutoClose prevent PDF file from being closed after adding ZF
|
||||
*/
|
||||
@Override
|
||||
public OXExporterFromA3 disableAutoClose(boolean disableAutoClose) {
|
||||
this.disableAutoClose = disableAutoClose;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setXMLProvider(IXMLProvider p) {
|
||||
this.xmlProvider = p;
|
||||
if (profile != null) {
|
||||
|
||||
@@ -22,26 +22,19 @@ package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import static org.mustangproject.ZUGFeRD.ZUGFeRDDateFormat.DATE;
|
||||
import static org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants.CORRECTEDINVOICE;
|
||||
import static org.mustangproject.ZUGFeRD.model.TaxCategoryCodeTypeConstants.CATEGORY_CODES_WITH_EXEMPTION_REASON;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.mustangproject.EStandard;
|
||||
import org.mustangproject.FileAttachment;
|
||||
import org.mustangproject.IncludedNote;
|
||||
import org.mustangproject.XMLTools;
|
||||
|
||||
public class OXPullProvider extends ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
public class OXPullProvider extends ZUGFeRD2PullProvider {
|
||||
|
||||
protected IExportableTransaction trans;
|
||||
protected TransactionCalculator calc;
|
||||
@@ -333,17 +326,19 @@ public class OXPullProvider extends ZUGFeRD2PullProvider implements IXMLProvider
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((trans.getDocumentCode() == CORRECTEDINVOICE)/*||(trans.getDocumentCode() == DocumentCodeTypeConstants.CREDITNOTE)*/) {
|
||||
if (trans.getDocumentCode() != null) {
|
||||
if ((trans.getDocumentCode().equals(CORRECTEDINVOICE))/*||(trans.getDocumentCode().equals (DocumentCodeTypeConstants.CREDITNOTE))*/) {
|
||||
hasDueDate = false;
|
||||
}
|
||||
}
|
||||
|
||||
final Map<BigDecimal, VATAmount> VATPercentAmountMap = calc.getVATPercentAmountMap();
|
||||
for (final BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) {
|
||||
final VATAmount amount = VATPercentAmountMap.get(currentTaxPercent);
|
||||
if (amount != null) {
|
||||
final String amountCategoryCode = amount.getCategoryCode();
|
||||
/*final String amountCategoryCode = amount.getCategoryCode();
|
||||
final boolean displayExemptionReason = CATEGORY_CODES_WITH_EXEMPTION_REASON.contains(amountCategoryCode);
|
||||
/* xml += "<ram:ApplicableTradeTax>\n"
|
||||
xml += "<ram:ApplicableTradeTax>\n"
|
||||
+ "<ram:CalculatedAmount>" + currencyFormat(amount.getCalculated())
|
||||
+ "</ram:CalculatedAmount>\n" //currencyID=\"EUR\"
|
||||
+ "<ram:TypeCode>VAT</ram:TypeCode>\n"
|
||||
@@ -479,13 +474,9 @@ public class OXPullProvider extends ZUGFeRD2PullProvider implements IXMLProvider
|
||||
+ "</rsm:SCRDMCCBDACIOMessageStructure>";
|
||||
|
||||
final byte[] zugferdRaw;
|
||||
try {
|
||||
zugferdRaw = xml.getBytes("UTF-8");
|
||||
zugferdRaw = xml.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
zugferdData = XMLTools.removeBOM(zugferdRaw);
|
||||
} catch (final UnsupportedEncodingException e) {
|
||||
Logger.getLogger(OXPullProvider.class.getName()).log(Level.SEVERE, null, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.apache.pdfbox.io.IOUtils;
|
||||
import org.apache.pdfbox.preflight.parser.PreflightParser;
|
||||
|
||||
import jakarta.activation.DataSource;
|
||||
|
||||
// Copied from PDFBox preflight 2.0.x
|
||||
final class ByteArrayDataSource implements DataSource
|
||||
{
|
||||
private ByteArrayOutputStream data;
|
||||
private String type = null;
|
||||
private String name = null;
|
||||
|
||||
public ByteArrayDataSource (InputStream is) throws IOException
|
||||
{
|
||||
data = new ByteArrayOutputStream ();
|
||||
IOUtils.copy (is, data);
|
||||
IOUtils.closeQuietly (is);
|
||||
}
|
||||
|
||||
public String getContentType ()
|
||||
{
|
||||
return this.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param type
|
||||
* the type to set
|
||||
*/
|
||||
public void setType (String type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name
|
||||
* the name to set
|
||||
*/
|
||||
public void setName (String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public InputStream getInputStream () throws IOException
|
||||
{
|
||||
return new ByteArrayInputStream (data.toByteArray ());
|
||||
}
|
||||
|
||||
public String getName ()
|
||||
{
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public OutputStream getOutputStream () throws IOException
|
||||
{
|
||||
this.data = new ByteArrayOutputStream ();
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to create an API similar to the 2.x one
|
||||
final class PreflightParserHelper
|
||||
{
|
||||
private static File createTmpFile (InputStream input) throws IOException
|
||||
{
|
||||
FileOutputStream fos = null;
|
||||
try
|
||||
{
|
||||
File tmpFile = File.createTempFile ("mustang-pdf", ".pdf");
|
||||
fos = new FileOutputStream (tmpFile);
|
||||
IOUtils.copy (input, fos);
|
||||
return tmpFile;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IOUtils.closeQuietly (input);
|
||||
IOUtils.closeQuietly (fos);
|
||||
}
|
||||
}
|
||||
|
||||
public static PreflightParser createPreflightParser (DataSource dataSource) throws IOException
|
||||
{
|
||||
return new PreflightParser (createTmpFile (dataSource.getInputStream ()));
|
||||
}
|
||||
}
|
||||
|
||||
final class PDFBoxUpdateMitigation
|
||||
{
|
||||
// Dummy for the name only
|
||||
}
|
||||
@@ -20,12 +20,12 @@
|
||||
*/
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import org.mustangproject.EStandard;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.mustangproject.EStandard;
|
||||
|
||||
public class Profiles {
|
||||
static Map<String, Profile> zf2Map = Stream.of(new Object[][]{
|
||||
{"MINIMUM", new Profile("MINIMUM", "urn:factur-x.eu:1p0:minimum")},
|
||||
|
||||
@@ -39,8 +39,8 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
|
||||
}
|
||||
|
||||
/***
|
||||
* the invoice total with VAT, corrected by prepaid amount, allowances and
|
||||
* charges
|
||||
* the invoice total with VAT, allowances and
|
||||
* charges, WITHOUT considering prepaid amount
|
||||
*
|
||||
* @return the invoice total including taxes
|
||||
*/
|
||||
@@ -172,6 +172,10 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
|
||||
LineCalculator lc = new LineCalculator(currentItem);
|
||||
VATAmount itemVATAmount = new VATAmount(lc.getItemTotalNetAmount(), lc.getItemTotalVATAmount(),
|
||||
currentItem.getProduct().getTaxCategoryCode(), vatDueDateTypeCode);
|
||||
String reasonText=currentItem.getProduct().getTaxExemptionReason();
|
||||
if (reasonText!=null) {
|
||||
itemVATAmount.setVatExemptionReasonText(reasonText);
|
||||
}
|
||||
VATAmount current = hm.get(percent.stripTrailingZeros());
|
||||
if (current == null) {
|
||||
hm.put(percent.stripTrailingZeros(), itemVATAmount);
|
||||
|
||||
@@ -20,33 +20,23 @@
|
||||
*/
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
import org.dom4j.Document;
|
||||
import org.dom4j.DocumentException;
|
||||
import org.dom4j.DocumentHelper;
|
||||
import org.dom4j.io.OutputFormat;
|
||||
import org.dom4j.io.XMLWriter;
|
||||
import org.mustangproject.EStandard;
|
||||
import org.mustangproject.FileAttachment;
|
||||
import org.mustangproject.TradeParty;
|
||||
import org.mustangproject.XMLTools;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static org.mustangproject.ZUGFeRD.ZUGFeRDDateFormat.DATE;
|
||||
import static org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants.CORRECTEDINVOICE;
|
||||
import static org.mustangproject.ZUGFeRD.model.TaxCategoryCodeTypeConstants.CATEGORY_CODES_WITH_EXEMPTION_REASON;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class UBLDAPullProvider implements IXMLProvider {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger (UBLDAPullProvider.class);
|
||||
|
||||
protected IExportableTransaction trans;
|
||||
protected TransactionCalculator calc;
|
||||
@@ -96,13 +86,9 @@ public class UBLDAPullProvider implements IXMLProvider {
|
||||
}
|
||||
xml += "</DespatchAdvice>\n";
|
||||
final byte[] ublRaw;
|
||||
try {
|
||||
ublRaw = xml.getBytes("UTF-8");
|
||||
ublRaw = xml.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
ublData = XMLTools.removeBOM(ublRaw);
|
||||
} catch (final UnsupportedEncodingException e) {
|
||||
Logger.getLogger(UBLDAPullProvider.class.getName()).log(Level.SEVERE, null, e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getPartyXML(IZUGFeRDExportableTradeParty tp) {
|
||||
@@ -133,7 +119,7 @@ public class UBLDAPullProvider implements IXMLProvider {
|
||||
try {
|
||||
document = DocumentHelper.parseText(new String(ublData));
|
||||
} catch (final DocumentException e1) {
|
||||
Logger.getLogger(ZUGFeRD2PullProvider.class.getName()).log(Level.SEVERE, null, e1);
|
||||
LOGGER.error ("Failed to parse UBL", e1);
|
||||
}
|
||||
try {
|
||||
final OutputFormat format = OutputFormat.createPrettyPrint();
|
||||
@@ -143,7 +129,7 @@ public class UBLDAPullProvider implements IXMLProvider {
|
||||
res = sw.toString().getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
} catch (final IOException e) {
|
||||
Logger.getLogger(ZUGFeRD2PullProvider.class.getName()).log(Level.SEVERE, null, e);
|
||||
LOGGER.error ("Failed to write XML", e);
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
@@ -32,6 +32,16 @@ import java.math.RoundingMode;
|
||||
*/
|
||||
public class VATAmount {
|
||||
|
||||
|
||||
protected BigDecimal basis, calculated, applicablePercent;
|
||||
|
||||
protected String categoryCode;
|
||||
|
||||
protected String vatExemptionReasonText;
|
||||
|
||||
protected String dueDateTypeCode;
|
||||
|
||||
|
||||
public VATAmount(BigDecimal basis, BigDecimal calculated, String categoryCode) {
|
||||
super();
|
||||
this.basis = basis;
|
||||
@@ -48,35 +58,41 @@ public class VATAmount {
|
||||
this.dueDateTypeCode = dueDateTypeCode;
|
||||
}
|
||||
|
||||
BigDecimal basis, calculated, applicablePercent;
|
||||
|
||||
String categoryCode;
|
||||
|
||||
String dueDateTypeCode;
|
||||
|
||||
public BigDecimal getApplicablePercent() {
|
||||
return applicablePercent;
|
||||
}
|
||||
|
||||
public void setApplicablePercent(BigDecimal applicablePercent) {
|
||||
public VATAmount setApplicablePercent(BigDecimal applicablePercent) {
|
||||
|
||||
this.applicablePercent = applicablePercent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BigDecimal getBasis() {
|
||||
return basis;
|
||||
}
|
||||
|
||||
public void setBasis(BigDecimal basis) {
|
||||
public VATAmount setBasis(BigDecimal basis) {
|
||||
this.basis = basis.setScale(2, RoundingMode.HALF_UP);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public BigDecimal getCalculated() {
|
||||
return calculated;
|
||||
}
|
||||
|
||||
public void setCalculated(BigDecimal calculated) {
|
||||
public VATAmount setCalculated(BigDecimal calculated) {
|
||||
this.calculated = calculated;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getVatExemptionReasonText() {
|
||||
return vatExemptionReasonText;
|
||||
}
|
||||
|
||||
public VATAmount setVatExemptionReasonText(String theText) {
|
||||
this.vatExemptionReasonText = theText;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,34 +108,38 @@ public class VATAmount {
|
||||
/**
|
||||
* @param documentCode as String
|
||||
* @deprecated Use {@link #setCategoryCode(String)} instead
|
||||
* @return fluent setter
|
||||
*/
|
||||
@Deprecated
|
||||
public void setDocumentCode(String documentCode) {
|
||||
public VATAmount setDocumentCode(String documentCode) {
|
||||
this.categoryCode = documentCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getCategoryCode() {
|
||||
return categoryCode;
|
||||
}
|
||||
|
||||
public void setCategoryCode(String categoryCode) {
|
||||
public VATAmount setCategoryCode(String categoryCode) {
|
||||
this.categoryCode = categoryCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getDueDateTypeCode() {
|
||||
return dueDateTypeCode;
|
||||
}
|
||||
|
||||
public void setDueDateTypeCode(String dueDateTypeCode) {
|
||||
public VATAmount setDueDateTypeCode(String dueDateTypeCode) {
|
||||
this.dueDateTypeCode = dueDateTypeCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public VATAmount add(VATAmount v) {
|
||||
return new VATAmount(basis.add(v.getBasis()), calculated.add(v.getCalculated()), this.categoryCode, this.dueDateTypeCode);
|
||||
return new VATAmount(basis.add(v.getBasis()), calculated.add(v.getCalculated()), this.categoryCode, this.dueDateTypeCode).setVatExemptionReasonText(v.getVatExemptionReasonText());
|
||||
}
|
||||
|
||||
public VATAmount subtract(VATAmount v) {
|
||||
return new VATAmount(basis.subtract(v.getBasis()), calculated.subtract(v.getCalculated()), this.categoryCode, this.dueDateTypeCode);
|
||||
return new VATAmount(basis.subtract(v.getBasis()), calculated.subtract(v.getCalculated()), this.categoryCode, this.dueDateTypeCode).setVatExemptionReasonText(v.getVatExemptionReasonText());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import javax.xml.transform.*;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.Templates;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.URIResolver;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
import java.io.*;
|
||||
|
||||
|
||||
/***
|
||||
@@ -31,9 +42,8 @@ public class XMLUpgrader {
|
||||
* @return String the updated XML
|
||||
* @throws FileNotFoundException if the source could not be found
|
||||
* @throws TransformerException if the source could not be transformed
|
||||
* @throws UnsupportedEncodingException if the source was not utf8
|
||||
*/
|
||||
public String migrateFromV1ToV2(String xmlFilename) throws FileNotFoundException, TransformerException, UnsupportedEncodingException {
|
||||
public String migrateFromV1ToV2(String xmlFilename) throws FileNotFoundException, TransformerException {
|
||||
/**
|
||||
* *
|
||||
* http://www.unece.org/fileadmin/DAM/cefact/xml/XML-Naming-And-Design-Rules-V2_1.pdf
|
||||
@@ -45,7 +55,7 @@ public class XMLUpgrader {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
applySchematronXsl(new FileInputStream(xmlFilename), baos);
|
||||
String res = null;
|
||||
res = baos.toString("UTF-8");
|
||||
res = baos.toString(StandardCharsets.UTF_8);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,15 @@ package org.mustangproject.ZUGFeRD;
|
||||
import org.apache.xmpbox.XMPMetadata;
|
||||
import org.apache.xmpbox.XmpConstants;
|
||||
import org.apache.xmpbox.schema.PDFAExtensionSchema;
|
||||
import org.apache.xmpbox.type.*;
|
||||
import org.apache.xmpbox.type.ArrayProperty;
|
||||
import org.apache.xmpbox.type.Attribute;
|
||||
import org.apache.xmpbox.type.Cardinality;
|
||||
import org.apache.xmpbox.type.ChoiceType;
|
||||
import org.apache.xmpbox.type.DefinedStructuredType;
|
||||
import org.apache.xmpbox.type.PDFAPropertyType;
|
||||
import org.apache.xmpbox.type.PDFASchemaType;
|
||||
import org.apache.xmpbox.type.StructuredType;
|
||||
import org.apache.xmpbox.type.TextType;
|
||||
import org.mustangproject.EStandard;
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,31 +20,10 @@
|
||||
*/
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import org.apache.pdfbox.cos.*;
|
||||
import org.apache.pdfbox.io.IOUtils;
|
||||
import org.apache.pdfbox.pdmodel.*;
|
||||
import org.apache.pdfbox.pdmodel.common.PDMetadata;
|
||||
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
|
||||
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
|
||||
import org.apache.pdfbox.preflight.PreflightDocument;
|
||||
import org.apache.pdfbox.preflight.exception.ValidationException;
|
||||
import org.apache.pdfbox.preflight.parser.PreflightParser;
|
||||
import org.apache.pdfbox.preflight.utils.ByteArrayDataSource;
|
||||
import org.apache.xmpbox.XMPMetadata;
|
||||
import org.apache.xmpbox.schema.AdobePDFSchema;
|
||||
import org.apache.xmpbox.schema.DublinCoreSchema;
|
||||
import org.apache.xmpbox.schema.PDFAIdentificationSchema;
|
||||
import org.apache.xmpbox.schema.XMPBasicSchema;
|
||||
import org.apache.xmpbox.type.BadFieldValueException;
|
||||
import org.apache.xmpbox.xml.XmpSerializer;
|
||||
|
||||
import javax.activation.DataSource;
|
||||
import javax.activation.FileDataSource;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import java.io.*;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class XRExporter implements IExporter {
|
||||
IXMLProvider xmlProvider;
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import org.mustangproject.XMLTools;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.mustangproject.XMLTools;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class XRechnungImporter extends ZUGFeRDImporter {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger (XRechnungImporter.class);
|
||||
|
||||
public XRechnungImporter(byte[] rawXml) {
|
||||
super();
|
||||
@@ -19,7 +19,7 @@ public class XRechnungImporter extends ZUGFeRDImporter {
|
||||
setRawXML(rawXml);
|
||||
containsMeta = true;
|
||||
} catch (final IOException e) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
|
||||
LOGGER.error ("Failed to set raw XML", e);
|
||||
throw new ZUGFeRDExportException(e);
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ public class XRechnungImporter extends ZUGFeRDImporter {
|
||||
setRawXML(Files.readAllBytes(Paths.get(filename)));
|
||||
containsMeta = true;
|
||||
} catch (final IOException e) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
|
||||
LOGGER.error ("Failed to set raw XML", e);
|
||||
throw new ZUGFeRDExportException(e);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public class XRechnungImporter extends ZUGFeRDImporter {
|
||||
setRawXML(XMLTools.getBytesFromStream(fileinput));
|
||||
containsMeta = true;
|
||||
} catch (final IOException e) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
|
||||
LOGGER.error ("Failed to set raw XML", e);
|
||||
throw new ZUGFeRDExportException(e);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,6 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.dom4j.Document;
|
||||
import org.dom4j.DocumentException;
|
||||
@@ -39,9 +37,11 @@ import org.dom4j.DocumentHelper;
|
||||
import org.dom4j.io.OutputFormat;
|
||||
import org.dom4j.io.XMLWriter;
|
||||
import org.mustangproject.XMLTools;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
|
||||
public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger (ZUGFeRD1PullProvider.class);
|
||||
|
||||
//// MAIN CLASS
|
||||
|
||||
@@ -93,17 +93,17 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
|
||||
try {
|
||||
document = DocumentHelper.parseText(new String(zugferdData));
|
||||
} catch (final DocumentException e1) {
|
||||
Logger.getLogger(ZUGFeRD1PullProvider.class.getName()).log(Level.SEVERE, null, e1);
|
||||
LOGGER.error ("Failed to parse ZUGFeRD data", e1);
|
||||
}
|
||||
try {
|
||||
final OutputFormat format = OutputFormat.createPrettyPrint();
|
||||
format.setTrimText(false);
|
||||
final XMLWriter writer = new XMLWriter(sw, format);
|
||||
writer.write(document);
|
||||
res = sw.toString().getBytes("UTF-8");
|
||||
res = sw.toString().getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
} catch (final IOException e) {
|
||||
Logger.getLogger(ZUGFeRD1PullProvider.class.getName()).log(Level.SEVERE, null, e);
|
||||
LOGGER.error ("Failed to write ZUGFeRD data", e);
|
||||
}
|
||||
|
||||
return res;
|
||||
@@ -387,9 +387,10 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
|
||||
+ "<ram:LineTotalAmount currencyID=\"" + trans.getCurrency() + "\">" + currencyFormat(lc.getItemTotalNetAmount())
|
||||
+ "</ram:LineTotalAmount>"
|
||||
+ "</ram:SpecifiedTradeSettlementMonetarySummation>";
|
||||
if (currentItem.getAdditionalReferencedDocumentID() != null) {
|
||||
if (currentItem.getAdditionalReferences() != null) {
|
||||
xml += "<ram:AdditionalReferencedDocument><ram:ID>" + currentItem.getAdditionalReferences()[0].getIssuerAssignedID() + "</ram:ID><ram:TypeCode>130</ram:TypeCode></ram:AdditionalReferencedDocument>";
|
||||
} else if (currentItem.getAdditionalReferencedDocumentID() != null) {
|
||||
xml += "<ram:AdditionalReferencedDocument><ram:ID>" + currentItem.getAdditionalReferencedDocumentID() + "</ram:ID><ram:TypeCode>130</ram:TypeCode></ram:AdditionalReferencedDocument>";
|
||||
|
||||
}
|
||||
xml += "</ram:SpecifiedSupplyChainTradeSettlement>"
|
||||
+ "<ram:SpecifiedTradeProduct>";
|
||||
|
||||
@@ -25,7 +25,6 @@ import static org.mustangproject.ZUGFeRD.model.TaxCategoryCodeTypeConstants.CATE
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
@@ -35,9 +34,8 @@ import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.dom4j.Document;
|
||||
@@ -49,8 +47,11 @@ import org.mustangproject.FileAttachment;
|
||||
import org.mustangproject.IncludedNote;
|
||||
import org.mustangproject.XMLTools;
|
||||
import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRD2PullProvider.class);
|
||||
|
||||
protected byte[] zugferdData;
|
||||
protected IExportableTransaction trans;
|
||||
@@ -75,11 +76,13 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
|
||||
protected String priceFormat(BigDecimal value) {
|
||||
return XMLTools.nDigitFormat(value, 4);
|
||||
// 18 decimals are max for price and qty due to xml restrictions,
|
||||
// see Chapter 3.2.3 of https://www.w3.org/TR/xmlschema-2/
|
||||
return XMLTools.nDigitFormatDecimalRange(value, 18, 4);
|
||||
}
|
||||
|
||||
protected String quantityFormat(BigDecimal value) {
|
||||
return XMLTools.nDigitFormat(value, 4);
|
||||
return XMLTools.nDigitFormatDecimalRange(value, 18, 4);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -92,7 +95,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
try {
|
||||
document = DocumentHelper.parseText(new String(zugferdData));
|
||||
} catch (final DocumentException e1) {
|
||||
Logger.getLogger(ZUGFeRD2PullProvider.class.getName()).log(Level.SEVERE, null, e1);
|
||||
LOGGER.error("Failed to parse ZUGFeRD data", e1);
|
||||
}
|
||||
try {
|
||||
final OutputFormat format = OutputFormat.createPrettyPrint();
|
||||
@@ -102,7 +105,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
res = sw.toString().getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
} catch (final IOException e) {
|
||||
Logger.getLogger(ZUGFeRD2PullProvider.class.getName()).log(Level.SEVERE, null, e);
|
||||
LOGGER.error("Failed to write ZUGFeRD data", e);
|
||||
}
|
||||
|
||||
return res;
|
||||
@@ -137,21 +140,32 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
+ XMLTools.encodeXML(party.getGlobalID()) + "</ram:GlobalID>";
|
||||
}
|
||||
xml += "<ram:Name>" + XMLTools.encodeXML(party.getName()) + "</ram:Name>";
|
||||
|
||||
if (party.getDescription() != null) {
|
||||
xml += "<ram:Description>" + XMLTools.encodeXML(party.getDescription()) + "</ram:Description>";
|
||||
}
|
||||
if (party.getLegalOrganisation() != null) {
|
||||
xml += "<ram:SpecifiedLegalOrganization> ";
|
||||
xml += " <ram:ID schemeID=\"" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemeID()) + "\">" + XMLTools.encodeXML(party.getLegalOrganisation().getID()) + "</ram:ID>";
|
||||
if (party.getLegalOrganisation().getSchemedID() != null) {
|
||||
if (profile == Profiles.getByName("Minimum")) {
|
||||
xml += "<ram:ID>" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getID()) + "</ram:ID>";
|
||||
} else {
|
||||
xml += "<ram:ID schemeID=\"" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getScheme()) + "\">" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getID()) + "</ram:ID>";
|
||||
}
|
||||
}
|
||||
if (party.getLegalOrganisation().getTradingBusinessName() != null) {
|
||||
xml += "<ram:TradingBusinessName>" + XMLTools.encodeXML(party.getLegalOrganisation().getTradingBusinessName()) + "</ram:TradingBusinessName>";
|
||||
}
|
||||
xml += "</ram:SpecifiedLegalOrganization>";
|
||||
}
|
||||
|
||||
if ((party.getContact() != null) && (isSender || profile == Profiles.getByName("Extended") || profile == Profiles.getByName("XRechnung"))) {
|
||||
if ((party.getContact() != null) && (isSender || profile == Profiles.getByName("EN16931") || profile == Profiles.getByName("Extended") || profile == Profiles.getByName("XRechnung"))) {
|
||||
xml += "<ram:DefinedTradeContact>";
|
||||
if (party.getContact().getName() != null) {
|
||||
xml += "<ram:PersonName>" + XMLTools.encodeXML(party.getContact().getName())
|
||||
xml += "<ram:PersonName>"
|
||||
+ XMLTools.encodeXML(party.getContact().getName())
|
||||
+ "</ram:PersonName>";
|
||||
}
|
||||
if (party.getContact().getPhone() != null) {
|
||||
|
||||
xml += "<ram:TelephoneUniversalCommunication><ram:CompleteNumber>"
|
||||
+ XMLTools.encodeXML(party.getContact().getPhone()) + "</ram:CompleteNumber>"
|
||||
+ "</ram:TelephoneUniversalCommunication>";
|
||||
@@ -220,6 +234,30 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
|
||||
}
|
||||
|
||||
protected String getTradePartyPayeeAsXML(IZUGFeRDExportableTradeParty party) {
|
||||
String xml = "";
|
||||
// According EN16931 either GlobalID or seller assigned ID might be present for a Payee
|
||||
if (party.getID() != null) {
|
||||
xml += "<ram:ID>" + XMLTools.encodeXML(party.getID()) + "</ram:ID>";
|
||||
}
|
||||
if ((party.getGlobalIDScheme() != null) && (party.getGlobalID() != null)) {
|
||||
xml += "<ram:GlobalID schemeID=\"" + XMLTools.encodeXML(party.getGlobalIDScheme()) + "\">"
|
||||
+ XMLTools.encodeXML(party.getGlobalID())
|
||||
+ "</ram:GlobalID>";
|
||||
}
|
||||
xml += "<ram:Name>" + XMLTools.encodeXML(party.getName()) + "</ram:Name>";
|
||||
|
||||
if (party.getLegalOrganisation() != null) {
|
||||
xml += "<ram:SpecifiedLegalOrganization> ";
|
||||
if (party.getLegalOrganisation().getSchemedID() != null) {
|
||||
xml += "<ram:ID schemeID=\"" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getScheme()) + "\">" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getID()) + "</ram:ID>";
|
||||
}
|
||||
xml += "</ram:SpecifiedLegalOrganization>";
|
||||
}
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* returns the XML for a charge or allowance on item level
|
||||
@@ -239,14 +277,19 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
|
||||
String reason = "";
|
||||
if ((allowance.getReason() != null) && (profile == Profiles.getByName("Extended"))) {
|
||||
// only in extended profile
|
||||
if ((allowance.getReason() != null) && (profile == Profiles.getByName("Extended") || profile == Profiles.getByName("XRechnung"))) {
|
||||
reason = "<ram:Reason>" + XMLTools.encodeXML(allowance.getReason()) + "</ram:Reason>";
|
||||
}
|
||||
String reasonCode = "";
|
||||
if ((allowance.getReasonCode() != null) && (profile == Profiles.getByName("XRechnung"))) {
|
||||
// only in XRechnung profile
|
||||
reasonCode = "<ram:ReasonCode>" + allowance.getReasonCode() + "</ram:ReasonCode>";
|
||||
}
|
||||
final String allowanceChargeStr = "<ram:AppliedTradeAllowanceCharge><ram:ChargeIndicator><udt:Indicator>" +
|
||||
chargeIndicator + "</udt:Indicator></ram:ChargeIndicator>" + percentage +
|
||||
"<ram:ActualAmount>" + priceFormat(allowance.getTotalAmount(item)) + "</ram:ActualAmount>" +
|
||||
reason +
|
||||
reasonCode +
|
||||
"</ram:AppliedTradeAllowanceCharge>";
|
||||
return allowanceChargeStr;
|
||||
}
|
||||
@@ -268,10 +311,20 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
chargeIndicator = "true";
|
||||
}
|
||||
|
||||
String reason = "";
|
||||
if ((allowance.getReason() != null) && (profile == Profiles.getByName("Extended") || profile == Profiles.getByName("XRechnung"))) {
|
||||
reason = "<ram:Reason>" + XMLTools.encodeXML(allowance.getReason()) + "</ram:Reason>";
|
||||
}
|
||||
String reasonCode = "";
|
||||
if ((allowance.getReasonCode() != null) && (profile == Profiles.getByName("XRechnung"))) {
|
||||
// only in XRechnung profile
|
||||
reasonCode = "<ram:ReasonCode>" + allowance.getReasonCode() + "</ram:ReasonCode>";
|
||||
}
|
||||
final String itemTotalAllowanceChargeStr = "<ram:SpecifiedTradeAllowanceCharge><ram:ChargeIndicator><udt:Indicator>" +
|
||||
chargeIndicator + "</udt:Indicator></ram:ChargeIndicator>" + percentage +
|
||||
"<ram:ActualAmount>" + currencyFormat(allowance.getTotalAmount(item)) + "</ram:ActualAmount>" +
|
||||
"<ram:Reason>" + XMLTools.encodeXML(allowance.getReason()) + "</ram:Reason>" +
|
||||
reason +
|
||||
reasonCode +
|
||||
"</ram:SpecifiedTradeAllowanceCharge>";
|
||||
return itemTotalAllowanceChargeStr;
|
||||
}
|
||||
@@ -290,10 +343,21 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
paymentTermsDescription = XMLTools.encodeXML(trans.getPaymentTermDescription());
|
||||
}
|
||||
|
||||
if ((paymentTermsDescription == null) && (trans.getDocumentCode() != DocumentCodeTypeConstants.CORRECTEDINVOICE) && (trans.getDocumentCode() != DocumentCodeTypeConstants.CREDITNOTE)) {
|
||||
paymentTermsDescription = "Zahlbar ohne Abzug bis " + germanDateFormat.format(trans.getDueDate());
|
||||
|
||||
if ((profile == Profiles.getByName("XRechnung")) && (trans.getCashDiscounts() != null) && (trans.getCashDiscounts().length > 0)) {
|
||||
for (IZUGFeRDCashDiscount discount : trans.getCashDiscounts()
|
||||
) {
|
||||
if (paymentTermsDescription == null) {
|
||||
paymentTermsDescription = "";
|
||||
}
|
||||
paymentTermsDescription += discount.getAsXRechnung();
|
||||
}
|
||||
} else if ((paymentTermsDescription == null) && (trans.getDocumentCode() != DocumentCodeTypeConstants.CORRECTEDINVOICE) && (trans.getDocumentCode() != DocumentCodeTypeConstants.CREDITNOTE)) {
|
||||
if (trans.getDueDate() != null) {
|
||||
paymentTermsDescription = "Please remit until " + germanDateFormat.format(trans.getDueDate());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String typecode = "380";
|
||||
if (trans.getDocumentCode() != null) {
|
||||
@@ -387,6 +451,33 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
XMLTools.encodeXML(currentItem.getProduct().getDescription()) +
|
||||
"</ram:Description>";
|
||||
}
|
||||
if (currentItem.getProduct().getClassifications() != null && currentItem.getProduct().getClassifications().length > 0) {
|
||||
for (IDesignatedProductClassification classification : currentItem.getProduct().getClassifications()) {
|
||||
xml += "<ram:DesignatedProductClassification>"
|
||||
+ "<ram:ClassCode listId=\"" + XMLTools.encodeXML(classification.getClassCode().getListID()) + "\"";
|
||||
if (classification.getClassCode().getListVersionID() != null) {
|
||||
xml += " listVersionID=\"" + XMLTools.encodeXML(classification.getClassCode().getListVersionID()) + "\"";
|
||||
}
|
||||
xml += ">" + classification.getClassCode().getCode() + "</ram:ClassCode>";
|
||||
if (classification.getClassName() != null) {
|
||||
xml += "<ram:ClassName>" + XMLTools.encodeXML(classification.getClassName()) + "</ram:ClassName>";
|
||||
}
|
||||
xml += "</ram:DesignatedProductClassification>";
|
||||
}
|
||||
}
|
||||
if (currentItem.getProduct().getAttributes() != null) {
|
||||
for (Entry<String, String> entry : currentItem.getProduct().getAttributes().entrySet()) {
|
||||
xml += "<ram:ApplicableProductCharacteristic>" +
|
||||
"<ram:Description>" + XMLTools.encodeXML(entry.getKey()) + "</ram:Description>" +
|
||||
"<ram:Value>" + XMLTools.encodeXML(entry.getValue()) + "</ram:Value>" +
|
||||
"</ram:ApplicableProductCharacteristic>";
|
||||
}
|
||||
}
|
||||
if (currentItem.getProduct().getCountryOfOrigin() != null) {
|
||||
xml += "<ram:OriginTradeCountry><ram:ID>" +
|
||||
XMLTools.encodeXML(currentItem.getProduct().getCountryOfOrigin()) +
|
||||
"</ram:ID></ram:OriginTradeCountry>";
|
||||
}
|
||||
xml += "</ram:SpecifiedTradeProduct>"
|
||||
|
||||
+ "<ram:SpecifiedLineTradeAgreement>";
|
||||
@@ -456,9 +547,16 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
+ "<ram:LineTotalAmount>" + currencyFormat(lc.getItemTotalNetAmount())
|
||||
+ "</ram:LineTotalAmount>" // currencyID=\"EUR\"
|
||||
+ "</ram:SpecifiedTradeSettlementLineMonetarySummation>";
|
||||
if (currentItem.getAdditionalReferencedDocumentID() != null) {
|
||||
if (currentItem.getAdditionalReferences() != null) {
|
||||
for (final IReferencedDocument currentReference : currentItem.getAdditionalReferences()) {
|
||||
xml += "<ram:AdditionalReferencedDocument>" +
|
||||
"<ram:IssuerAssignedID>" + XMLTools.encodeXML(currentReference.getIssuerAssignedID()) + "</ram:IssuerAssignedID>" +
|
||||
"<ram:TypeCode>130</ram:TypeCode>" +
|
||||
"<ram:ReferenceTypeCode>" + XMLTools.encodeXML(currentReference.getReferenceTypeCode()) + "</ram:ReferenceTypeCode>" +
|
||||
"</ram:AdditionalReferencedDocument>";
|
||||
}
|
||||
} else if (currentItem.getAdditionalReferencedDocumentID() != null) {
|
||||
xml += "<ram:AdditionalReferencedDocument><ram:IssuerAssignedID>" + currentItem.getAdditionalReferencedDocumentID() + "</ram:IssuerAssignedID><ram:TypeCode>130</ram:TypeCode></ram:AdditionalReferencedDocument>";
|
||||
|
||||
}
|
||||
xml += "</ram:SpecifiedLineTradeSettlement>"
|
||||
+ "</ram:IncludedSupplyChainTradeLineItem>";
|
||||
@@ -540,10 +638,6 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
xml += "</ram:OccurrenceDateTime>";
|
||||
xml += "</ram:ActualDeliverySupplyChainEvent>";
|
||||
|
||||
} else {
|
||||
if ((trans.getDocumentCode() != DocumentCodeTypeConstants.CREDITNOTE) && (getProfile() != Profiles.getByName("Minimum"))) {
|
||||
throw new IllegalStateException("No delivery date provided");
|
||||
}
|
||||
}
|
||||
/*
|
||||
* + "<DeliveryNoteReferencedDocument>" +
|
||||
@@ -560,10 +654,19 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
|
||||
xml += "</ram:ApplicableHeaderTradeDelivery>";
|
||||
xml += "<ram:ApplicableHeaderTradeSettlement>";
|
||||
|
||||
if ((trans.getCreditorReferenceID() != null) && (getProfile() != Profiles.getByName("Minimum"))) {
|
||||
xml += "<ram:CreditorReferenceID>" + XMLTools.encodeXML(trans.getCreditorReferenceID()) + "</ram:CreditorReferenceID>";
|
||||
}
|
||||
if ((trans.getNumber() != null) && (getProfile() != Profiles.getByName("Minimum"))) {
|
||||
xml += "<ram:PaymentReference>" + XMLTools.encodeXML(trans.getNumber()) + "</ram:PaymentReference>";
|
||||
}
|
||||
xml += "<ram:InvoiceCurrencyCode>" + trans.getCurrency() + "</ram:InvoiceCurrencyCode>";
|
||||
if (this.trans.getPayee() != null) {
|
||||
xml += "<ram:PayeeTradeParty>" +
|
||||
getTradePartyPayeeAsXML(this.trans.getPayee()) +
|
||||
"</ram:PayeeTradeParty>";
|
||||
}
|
||||
|
||||
if (trans.getTradeSettlementPayment() != null) {
|
||||
for (final IZUGFeRDTradeSettlementPayment payment : trans.getTradeSettlementPayment()) {
|
||||
@@ -599,12 +702,17 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
final String amountDueDateTypeCode = amount.getDueDateTypeCode();
|
||||
final boolean displayExemptionReason = CATEGORY_CODES_WITH_EXEMPTION_REASON.contains(amountCategoryCode);
|
||||
if (getProfile() != Profiles.getByName("Minimum")) {
|
||||
String exemptionReasonTextXML = "";
|
||||
if ((displayExemptionReason) && (amount.getVatExemptionReasonText() != null)) {
|
||||
exemptionReasonTextXML = "<ram:ExemptionReason>" + XMLTools.encodeXML(amount.getVatExemptionReasonText()) + "</ram:ExemptionReason>";
|
||||
|
||||
}
|
||||
|
||||
xml += "<ram:ApplicableTradeTax>"
|
||||
+ "<ram:CalculatedAmount>" + currencyFormat(amount.getCalculated())
|
||||
+ "</ram:CalculatedAmount>" //currencyID=\"EUR\"
|
||||
+ "<ram:TypeCode>VAT</ram:TypeCode>"
|
||||
+ (displayExemptionReason ? exemptionReason : "")
|
||||
+ exemptionReasonTextXML
|
||||
+ "<ram:BasisAmount>" + currencyFormat(amount.getBasis()) + "</ram:BasisAmount>" // currencyID=\"EUR\"
|
||||
+ "<ram:CategoryCode>" + amountCategoryCode + "</ram:CategoryCode>"
|
||||
+ (amountDueDateTypeCode != null ? "<ram:DueDateTypeCode>" + amountDueDateTypeCode + "</ram:DueDateTypeCode>" : "")
|
||||
@@ -625,7 +733,29 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
|
||||
if ((trans.getZFCharges() != null) && (trans.getZFCharges().length > 0)) {
|
||||
|
||||
if (profile == Profiles.getByName("XRechnung")) {
|
||||
for (IZUGFeRDAllowanceCharge charge : trans.getZFCharges()) {
|
||||
xml += "<ram:SpecifiedTradeAllowanceCharge>" +
|
||||
"<ram:ChargeIndicator>" +
|
||||
"<udt:Indicator>true</udt:Indicator>" +
|
||||
"</ram:ChargeIndicator>" +
|
||||
"<ram:ActualAmount>" + currencyFormat(charge.getTotalAmount(calc)) + "</ram:ActualAmount>";
|
||||
if (charge.getReason() != null) {
|
||||
xml += "<ram:Reason>" + XMLTools.encodeXML(charge.getReason()) + "</ram:Reason>";
|
||||
}
|
||||
if (charge.getReasonCode() != null) {
|
||||
xml += "<ram:ReasonCode>" + charge.getReasonCode() + "</ram:ReasonCode>";
|
||||
}
|
||||
xml += "<ram:CategoryTradeTax>" +
|
||||
"<ram:TypeCode>VAT</ram:TypeCode>" +
|
||||
"<ram:CategoryCode>" + charge.getCategoryCode() + "</ram:CategoryCode>";
|
||||
if (charge.getTaxPercent() != null) {
|
||||
xml += "<ram:RateApplicablePercent>" + vatFormat(charge.getTaxPercent()) + "</ram:RateApplicablePercent>";
|
||||
}
|
||||
xml += "</ram:CategoryTradeTax>" +
|
||||
"</ram:SpecifiedTradeAllowanceCharge>";
|
||||
}
|
||||
} else {
|
||||
for (final BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) {
|
||||
if (calc.getChargesForPercent(currentTaxPercent).compareTo(BigDecimal.ZERO) != 0) {
|
||||
xml += "<ram:SpecifiedTradeAllowanceCharge>" +
|
||||
@@ -643,8 +773,32 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((trans.getZFAllowances() != null) && (trans.getZFAllowances().length > 0)) {
|
||||
if (profile == Profiles.getByName("XRechnung")) {
|
||||
for (IZUGFeRDAllowanceCharge allowance : trans.getZFAllowances()) {
|
||||
xml += "<ram:SpecifiedTradeAllowanceCharge>" +
|
||||
"<ram:ChargeIndicator>" +
|
||||
"<udt:Indicator>false</udt:Indicator>" +
|
||||
"</ram:ChargeIndicator>" +
|
||||
"<ram:ActualAmount>" + currencyFormat(allowance.getTotalAmount(calc)) + "</ram:ActualAmount>";
|
||||
if (allowance.getReason() != null) {
|
||||
xml += "<ram:Reason>" + XMLTools.encodeXML(allowance.getReason()) + "</ram:Reason>";
|
||||
}
|
||||
if (allowance.getReasonCode() != null) {
|
||||
xml += "<ram:ReasonCode>" + allowance.getReasonCode() + "</ram:ReasonCode>";
|
||||
}
|
||||
xml += "<ram:CategoryTradeTax>" +
|
||||
"<ram:TypeCode>VAT</ram:TypeCode>" +
|
||||
"<ram:CategoryCode>" + allowance.getCategoryCode() + "</ram:CategoryCode>";
|
||||
if (allowance.getTaxPercent() != null) {
|
||||
xml += "<ram:RateApplicablePercent>" + vatFormat(allowance.getTaxPercent()) + "</ram:RateApplicablePercent>";
|
||||
}
|
||||
xml += "</ram:CategoryTradeTax>" +
|
||||
"</ram:SpecifiedTradeAllowanceCharge>";
|
||||
}
|
||||
} else {
|
||||
for (final BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) {
|
||||
if (calc.getAllowancesForPercent(currentTaxPercent).compareTo(BigDecimal.ZERO) != 0) {
|
||||
xml += "<ram:SpecifiedTradeAllowanceCharge>" +
|
||||
@@ -662,6 +816,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ((trans.getPaymentTerms() == null) && (getProfile() != Profiles.getByName("Minimum")) && ((paymentTermsDescription != null) || (trans.getTradeSettlement() != null) || (hasDueDate))) {
|
||||
@@ -689,6 +844,12 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
} else {
|
||||
xml += buildPaymentTermsXml();
|
||||
}
|
||||
if ((profile == Profiles.getByName("Extended")) && (trans.getCashDiscounts() != null) && (trans.getCashDiscounts().length > 0)) {
|
||||
for (IZUGFeRDCashDiscount discount : trans.getCashDiscounts()
|
||||
) {
|
||||
xml += discount.getAsCII();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final String allowanceTotalLine = "<ram:AllowanceTotalAmount>" + currencyFormat(calc.getAllowancesForPercent(null)) + "</ram:AllowanceTotalAmount>";
|
||||
@@ -740,13 +901,9 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
+ "</rsm:CrossIndustryInvoice>";
|
||||
|
||||
final byte[] zugferdRaw;
|
||||
try {
|
||||
zugferdRaw = xml.getBytes("UTF-8");
|
||||
zugferdRaw = xml.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
zugferdData = XMLTools.removeBOM(zugferdRaw);
|
||||
} catch (final UnsupportedEncodingException e) {
|
||||
Logger.getLogger(ZUGFeRD2PullProvider.class.getName()).log(Level.SEVERE, null, e);
|
||||
}
|
||||
}
|
||||
|
||||
protected String buildItemNotes(IZUGFeRDExportableItem currentItem) {
|
||||
@@ -760,8 +917,9 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
|
||||
protected String buildNotes(IExportableTransaction exportableTransaction) {
|
||||
final List<IncludedNote> includedNotes = Optional.ofNullable(exportableTransaction.getNotesWithSubjectCode())
|
||||
.orElse(new ArrayList<>());
|
||||
final List<IncludedNote> includedNotes = new ArrayList<>();
|
||||
Optional.ofNullable(exportableTransaction.getNotesWithSubjectCode()).ifPresent(includedNotes::addAll);
|
||||
|
||||
if (exportableTransaction.getNotes() != null) {
|
||||
for (final String currentNote : exportableTransaction.getNotes()) {
|
||||
includedNotes.add(IncludedNote.unspecifiedNote(currentNote));
|
||||
|
||||
@@ -19,25 +19,21 @@
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.apache.pdfbox.preflight.PreflightDocument;
|
||||
import org.apache.pdfbox.preflight.ValidationResult;
|
||||
import org.apache.pdfbox.preflight.exception.ValidationException;
|
||||
import org.apache.pdfbox.preflight.parser.PreflightParser;
|
||||
import org.mustangproject.EStandard;
|
||||
|
||||
import javax.activation.DataSource;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import jakarta.activation.DataSource;
|
||||
|
||||
public class ZUGFeRDExporterFromA1 extends ZUGFeRDExporterFromA3 implements IZUGFeRDExporter {
|
||||
protected boolean ignorePDFAErrors = false;
|
||||
|
||||
public ZUGFeRDExporterFromA1 ignorePDFAErrors() {
|
||||
this.ignorePDFAErrors = true;
|
||||
return this;
|
||||
}
|
||||
public class ZUGFeRDExporterFromA1 extends ZUGFeRDExporterFromA3 {
|
||||
|
||||
private static boolean isValidA1(DataSource dataSource) throws IOException {
|
||||
return getPDFAParserValidationResult(new PreflightParser(dataSource));
|
||||
return getPDFAParserValidationResult(PreflightParserHelper.createPreflightParser(dataSource));
|
||||
}
|
||||
|
||||
private static boolean getPDFAParserValidationResult(PreflightParser parser) throws IOException {
|
||||
@@ -46,19 +42,19 @@ public class ZUGFeRDExporterFromA1 extends ZUGFeRDExporterFromA3 implements IZUG
|
||||
* NonSequentialParser. Some additional controls are present to check a set of
|
||||
* PDF/A requirements. (Stream length consistency, EOL after some Keyword...)
|
||||
*/
|
||||
parser.parse();// might add a Format.PDF_A1A as parameter and iterate through A1 and A3
|
||||
// might add a Format.PDF_A1A as parameter and iterate through A1 and A3
|
||||
|
||||
try (PreflightDocument document = parser.getPreflightDocument()) {
|
||||
try (PreflightDocument document = (PreflightDocument) parser.parse()) {
|
||||
/*
|
||||
* Once the syntax validation is done, the parser can provide a
|
||||
* PreflightDocument (that inherits from PDDocument) This document process the
|
||||
* end of PDF/A validation.
|
||||
*/
|
||||
|
||||
document.validate();
|
||||
ValidationResult res = document.validate();
|
||||
|
||||
// Get validation result
|
||||
return document.getResult().isValid();
|
||||
return res.isValid();
|
||||
} catch (ValidationException e) {
|
||||
/*
|
||||
* the parse method can throw a SyntaxValidationException if the PDF file can't
|
||||
@@ -70,13 +66,16 @@ public class ZUGFeRDExporterFromA1 extends ZUGFeRDExporterFromA3 implements IZUG
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ZUGFeRDExporterFromA1 setProfile(Profile p) {
|
||||
return (ZUGFeRDExporterFromA1)super.setProfile(p);
|
||||
}
|
||||
@Override
|
||||
public ZUGFeRDExporterFromA1 setProfile(String profileName) {
|
||||
return (ZUGFeRDExporterFromA1)super.setProfile(profileName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean ensurePDFIsValid(final DataSource dataSource) throws IOException {
|
||||
if (!ignorePDFAErrors && !isValidA1(dataSource)) {
|
||||
throw new IOException("File is not a valid PDF/A-1 input file");
|
||||
@@ -85,34 +84,44 @@ public class ZUGFeRDExporterFromA1 extends ZUGFeRDExporterFromA3 implements IZUG
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ZUGFeRDExporterFromA1 load(String pdfFilename) throws IOException {
|
||||
return (ZUGFeRDExporterFromA1) super.load(pdfFilename);
|
||||
}
|
||||
@Override
|
||||
public ZUGFeRDExporterFromA1 load(byte[] pdfBinary) throws IOException {
|
||||
return (ZUGFeRDExporterFromA1) super.load(pdfBinary);
|
||||
}
|
||||
@Override
|
||||
public ZUGFeRDExporterFromA1 load(InputStream pdfSource) throws IOException{
|
||||
return (ZUGFeRDExporterFromA1) super.load(pdfSource);
|
||||
}
|
||||
@Override
|
||||
public ZUGFeRDExporterFromA1 setCreator(String creator) {
|
||||
return (ZUGFeRDExporterFromA1) super.setCreator(creator);
|
||||
}
|
||||
@Override
|
||||
public ZUGFeRDExporterFromA1 setConformanceLevel(PDFAConformanceLevel newLevel) {
|
||||
return (ZUGFeRDExporterFromA1) super.setConformanceLevel(newLevel);
|
||||
}
|
||||
@Override
|
||||
public ZUGFeRDExporterFromA1 setProducer(String producer){
|
||||
return (ZUGFeRDExporterFromA1) super.setProducer(producer);
|
||||
}
|
||||
@Override
|
||||
public ZUGFeRDExporterFromA1 setZUGFeRDVersion(EStandard est, int version){
|
||||
return (ZUGFeRDExporterFromA1) super.setZUGFeRDVersion(est, version);
|
||||
}
|
||||
@Override
|
||||
public ZUGFeRDExporterFromA1 setZUGFeRDVersion(int version){
|
||||
return (ZUGFeRDExporterFromA1) super.setZUGFeRDVersion(version);
|
||||
}
|
||||
@Override
|
||||
public ZUGFeRDExporterFromA1 setXML(byte[] zugferdData) throws IOException{
|
||||
return (ZUGFeRDExporterFromA1) super.setXML(zugferdData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZUGFeRDExporterFromA1 disableAutoClose(boolean disableAutoClose){
|
||||
return (ZUGFeRDExporterFromA1) super.disableAutoClose(disableAutoClose);
|
||||
}
|
||||
|
||||
@@ -20,9 +20,35 @@
|
||||
*/
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import org.apache.pdfbox.cos.*;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.xml.transform.TransformerException;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.cos.COSArray;
|
||||
import org.apache.pdfbox.cos.COSBase;
|
||||
import org.apache.pdfbox.cos.COSDictionary;
|
||||
import org.apache.pdfbox.cos.COSName;
|
||||
import org.apache.pdfbox.cos.COSObject;
|
||||
import org.apache.pdfbox.io.IOUtils;
|
||||
import org.apache.pdfbox.pdmodel.*;
|
||||
import org.apache.pdfbox.pdfwriter.compress.CompressParameters;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
|
||||
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDResources;
|
||||
import org.apache.pdfbox.pdmodel.common.PDMetadata;
|
||||
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
|
||||
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
|
||||
@@ -33,7 +59,6 @@ import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||
import org.apache.pdfbox.pdmodel.font.PDFontDescriptor;
|
||||
import org.apache.pdfbox.pdmodel.font.PDType0Font;
|
||||
import org.apache.pdfbox.pdmodel.graphics.color.PDOutputIntent;
|
||||
import org.apache.pdfbox.preflight.utils.ByteArrayDataSource;
|
||||
import org.apache.xmpbox.XMPMetadata;
|
||||
import org.apache.xmpbox.schema.AdobePDFSchema;
|
||||
import org.apache.xmpbox.schema.DublinCoreSchema;
|
||||
@@ -47,19 +72,22 @@ import org.apache.xmpbox.xml.XmpSerializer;
|
||||
import org.mustangproject.EStandard;
|
||||
import org.mustangproject.FileAttachment;
|
||||
|
||||
import javax.activation.DataSource;
|
||||
import javax.activation.FileDataSource;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import jakarta.activation.DataSource;
|
||||
import jakarta.activation.FileDataSource;
|
||||
|
||||
public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporter, IExporter, Closeable {
|
||||
public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporter {
|
||||
private boolean isFacturX = true;
|
||||
|
||||
public static final int DefaultZUGFeRDVersion = 2;
|
||||
protected boolean ignorePDFAErrors = false;
|
||||
|
||||
public ZUGFeRDExporterFromA3 ignorePDFAErrors() {
|
||||
this.ignorePDFAErrors = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected PDFAConformanceLevel conformanceLevel = PDFAConformanceLevel.UNICODE;
|
||||
protected ArrayList<FileAttachment> fileAttachments = new ArrayList<FileAttachment>();
|
||||
protected ArrayList<FileAttachment> fileAttachments = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* This flag controls whether or not the metadata is overwritten, or kind of merged.
|
||||
@@ -111,9 +139,6 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
||||
|
||||
protected PDDocument doc;
|
||||
|
||||
private HashMap<String, byte[]> additionalXMLs = new HashMap<String, byte[]>();
|
||||
|
||||
|
||||
protected int ZFVersion = DefaultZUGFeRDVersion;
|
||||
private boolean attachZUGFeRDHeaders = true;
|
||||
|
||||
@@ -199,20 +224,19 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
||||
* @return the filename of the file to be embedded
|
||||
*/
|
||||
public String getFilenameForVersion(int ver, Profile profile) {
|
||||
if (profile.getName().equals("XRECHNUNG")) {
|
||||
return "xrechnung.xml";
|
||||
}
|
||||
if (isFacturX) {
|
||||
return "factur-x.xml";
|
||||
} else {
|
||||
if (ver == 1) {
|
||||
return "ZUGFeRD-invoice.xml";
|
||||
} else {
|
||||
if (profile.getName().equals("XRECHNUNG")) {
|
||||
return "xrechnung.xml";
|
||||
} else {
|
||||
return "zugferd-invoice.xml";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@@ -220,8 +244,9 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
||||
* Generate ZF2.1 files with filename factur-x.xml
|
||||
*
|
||||
* @return this (fluent setter)
|
||||
* @deprecated
|
||||
* @deprecated It's now the default anyway
|
||||
*/
|
||||
@Deprecated
|
||||
public ZUGFeRDExporterFromA3 setFacturX() {
|
||||
isFacturX = true;
|
||||
return this;
|
||||
@@ -235,8 +260,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
||||
*
|
||||
* @param XRechnungVersion the XRechnung version
|
||||
*/
|
||||
public void setXRechnungSpecificVersion(String XRechnungVersion)
|
||||
{
|
||||
public void setXRechnungSpecificVersion(String XRechnungVersion) {
|
||||
this.XRechnungVersion = XRechnungVersion;
|
||||
}
|
||||
|
||||
@@ -258,7 +282,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
||||
*/
|
||||
public ZUGFeRDExporterFromA3 load(byte[] pdfBinary) throws IOException {
|
||||
ensurePDFIsValid(new ByteArrayDataSource(new ByteArrayInputStream(pdfBinary)));
|
||||
doc = PDDocument.load(pdfBinary);
|
||||
doc = Loader.loadPDF(pdfBinary);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -282,6 +306,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
||||
* @param ZUGFeRDfilename the pdf file name
|
||||
* @throws IOException if anything is wrong in the target location
|
||||
*/
|
||||
@Override
|
||||
public void export(String ZUGFeRDfilename) throws IOException {
|
||||
if (!documentPrepared) {
|
||||
prepareDocument();
|
||||
@@ -290,7 +315,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
||||
throw new IOException(
|
||||
"File must be attached (usually with setTransaction) before perfoming this operation");
|
||||
}
|
||||
doc.save(ZUGFeRDfilename);
|
||||
doc.save(ZUGFeRDfilename, CompressParameters.NO_COMPRESSION);
|
||||
if (!disableAutoClose) {
|
||||
close();
|
||||
}
|
||||
@@ -309,6 +334,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
||||
* @param output the OutputStream
|
||||
* @throws IOException if anything is wrong in the OutputStream
|
||||
*/
|
||||
@Override
|
||||
public void export(OutputStream output) throws IOException {
|
||||
if (!documentPrepared) {
|
||||
prepareDocument();
|
||||
@@ -317,7 +343,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
||||
throw new IOException(
|
||||
"File must be attached (usually with setTransaction) before perfoming this operation");
|
||||
}
|
||||
doc.save(output);
|
||||
doc.save(output, CompressParameters.NO_COMPRESSION);
|
||||
if (!disableAutoClose) {
|
||||
close();
|
||||
}
|
||||
@@ -371,7 +397,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
||||
ef.setSize(data.length);
|
||||
ef.setCreationDate(new GregorianCalendar());
|
||||
|
||||
ef.setModDate(GregorianCalendar.getInstance());
|
||||
ef.setModDate(Calendar.getInstance());
|
||||
|
||||
fs.setEmbeddedFile(ef);
|
||||
|
||||
@@ -403,7 +429,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
||||
doc.getDocumentCatalog().setNames(names);
|
||||
|
||||
// AF entry (Array) in catalog with the FileSpec
|
||||
COSBase AFEntry = (COSBase) doc.getDocumentCatalog().getCOSObject().getItem("AF");
|
||||
COSBase AFEntry = doc.getDocumentCatalog().getCOSObject().getItem("AF");
|
||||
if ((AFEntry == null)) {
|
||||
COSArray cosArray = new COSArray();
|
||||
cosArray.add(fs);
|
||||
@@ -511,8 +537,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
||||
|
||||
// The XRechnung version may be settable from outside.
|
||||
if ((this.XRechnungVersion != null) && (this.profile != null) &&
|
||||
this.profile.getName().equalsIgnoreCase(Profiles.getByName("XRECHNUNG").getName()))
|
||||
{
|
||||
this.profile.getName().equalsIgnoreCase(Profiles.getByName("XRECHNUNG").getName())) {
|
||||
metaDataVersion = this.XRechnungVersion;
|
||||
}
|
||||
|
||||
@@ -529,9 +554,8 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
||||
metadata.addSchema(pdfaex);
|
||||
}
|
||||
|
||||
private void removeCidSet(PDDocumentCatalog catalog, PDDocument doc)
|
||||
throws IOException
|
||||
{
|
||||
private void removeCidSet(PDDocument doc)
|
||||
throws IOException {
|
||||
// https://github.com/ZUGFeRD/mustangproject/issues/249
|
||||
|
||||
COSName cidSet = COSName.getPDFName("CIDSet");
|
||||
@@ -550,6 +574,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
||||
PDType0Font typedFont = (PDType0Font) pdFont;
|
||||
|
||||
if (typedFont.getDescendantFont() instanceof PDCIDFontType2) {
|
||||
@SuppressWarnings("unused")
|
||||
PDCIDFontType2 f = (PDCIDFontType2) typedFont.getDescendantFont();
|
||||
PDFontDescriptor fontDescriptor = pdFont.getFontDescriptor();
|
||||
|
||||
@@ -571,7 +596,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
||||
metadata = new PDMetadata(doc);
|
||||
cat.setMetadata(metadata);
|
||||
|
||||
removeCidSet(cat, doc);
|
||||
removeCidSet(doc);
|
||||
xmp = getXmpMetadata();
|
||||
writeAdobePDFSchema(xmp);
|
||||
writePDFAIdentificationSchema(xmp);
|
||||
@@ -608,6 +633,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
||||
* <code>setZUGFeRDXMLData(byte[] zugferdData)</code>
|
||||
* @throws IOException if anything is wrong with already loaded PDF
|
||||
*/
|
||||
@Override
|
||||
public IExporter setTransaction(IExportableTransaction trans) throws IOException {
|
||||
this.trans = trans;
|
||||
return prepare();
|
||||
@@ -622,11 +648,9 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
||||
// ZUGFeRD 2.1.1 Technical Supplement | Part A | 2.2.2. Data Relationship
|
||||
// See documentation ZUGFeRD211_EN/Documentation/ZUGFeRD-2.1.1 - Specification_TA_Part-A.pdf
|
||||
// https://www.ferd-net.de/standards/zugferd-2.1.1/index.html
|
||||
if ((this.profile != null) && (ZFVersion >= 2))
|
||||
{
|
||||
if ((this.profile != null) && (ZFVersion >= 2)) {
|
||||
if (this.profile.getName().equalsIgnoreCase(Profiles.getByName("MINIMUM").getName()) ||
|
||||
this.profile.getName().equalsIgnoreCase(Profiles.getByName("BASICWL").getName()))
|
||||
{
|
||||
this.profile.getName().equalsIgnoreCase(Profiles.getByName("BASICWL").getName())) {
|
||||
relationship = "Data";
|
||||
}
|
||||
}
|
||||
@@ -645,12 +669,12 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
||||
/**
|
||||
* Reads the XMPMetadata from the PDDocument, if it exists.
|
||||
* Otherwise creates XMPMetadata.
|
||||
*
|
||||
* @return the finished XMPMetadata object
|
||||
* @throws IOException when e.g. XmpParsingException
|
||||
*/
|
||||
protected XMPMetadata getXmpMetadata()
|
||||
throws IOException
|
||||
{
|
||||
throws IOException {
|
||||
PDMetadata meta = doc.getDocumentCatalog().getMetadata();
|
||||
if ((meta != null) && (meta.getLength() > 0)) {
|
||||
try {
|
||||
@@ -672,6 +696,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
||||
/**
|
||||
* Sets the producer if the overwrite flag is set or the producer is not already set.
|
||||
* Sets the PDFVersion to 1.4 if the field is empty.
|
||||
*
|
||||
* @param xmp the metadata as XML
|
||||
*/
|
||||
protected void writeAdobePDFSchema(XMPMetadata xmp) {
|
||||
@@ -713,13 +738,13 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
||||
}
|
||||
|
||||
protected PDFAIdentificationSchema getPDFAIdentificationSchema(XMPMetadata xmp) {
|
||||
PDFAIdentificationSchema pdfaid = xmp.getPDFIdentificationSchema();
|
||||
PDFAIdentificationSchema pdfaid = xmp.getPDFAIdentificationSchema();
|
||||
if (pdfaid != null)
|
||||
if (overwrite)
|
||||
xmp.removeSchema(pdfaid);
|
||||
else
|
||||
return pdfaid;
|
||||
return xmp.createAndAddPFAIdentificationSchema();
|
||||
return xmp.createAndAddPDFAIdentificationSchema();
|
||||
}
|
||||
|
||||
protected void writeDublinCoreSchema(XMPMetadata xmp) {
|
||||
@@ -760,7 +785,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
||||
if (overwrite || isEmpty(xsb.getCreatorTool()) || "UnknownApplication".equals(xsb.getCreatorTool()))
|
||||
xsb.setCreatorTool(creatorTool);
|
||||
if (overwrite || xsb.getCreateDate() == null)
|
||||
xsb.setCreateDate(GregorianCalendar.getInstance());
|
||||
xsb.setCreateDate(Calendar.getInstance());
|
||||
}
|
||||
|
||||
protected XMPBasicSchema getXmpBasicSchema(XMPMetadata xmp) {
|
||||
@@ -794,11 +819,11 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
|
||||
|
||||
/**
|
||||
* Adds an OutputIntent and the sRGB color profile if no OutputIntent exist
|
||||
*
|
||||
* @throws IOException if the ICC file cannot be read or attached to doc
|
||||
*/
|
||||
protected void addSRGBOutputIntend()
|
||||
throws IOException
|
||||
{
|
||||
throws IOException {
|
||||
if (!doc.getDocumentCatalog().getOutputIntents().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,13 @@
|
||||
*/
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
||||
import org.apache.pdfbox.pdmodel.common.PDMetadata;
|
||||
@@ -27,11 +34,11 @@ import org.apache.xmpbox.XMPMetadata;
|
||||
import org.apache.xmpbox.schema.PDFAIdentificationSchema;
|
||||
import org.apache.xmpbox.xml.DomXmpParser;
|
||||
import org.apache.xmpbox.xml.XmpParsingException;
|
||||
import org.mustangproject.FileAttachment;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.activation.DataSource;
|
||||
import java.io.*;
|
||||
import jakarta.activation.DataSource;
|
||||
|
||||
/***
|
||||
* Auto-detects the source PDF-A-Version and acts accordingly
|
||||
@@ -43,16 +50,31 @@ public class ZUGFeRDExporterFromPDFA implements IZUGFeRDExporter {
|
||||
|
||||
protected IZUGFeRDExporter theExporter;
|
||||
|
||||
protected boolean ignorePDFAErrors = false;
|
||||
|
||||
public ZUGFeRDExporterFromPDFA ignorePDFAErrors() {
|
||||
this.ignorePDFAErrors = true;
|
||||
return this;
|
||||
}
|
||||
protected void determineAndSetExporter(int PDFAVersion) {
|
||||
if (PDFAVersion == 3) {
|
||||
theExporter = new ZUGFeRDExporterFromA3();
|
||||
if (ignorePDFAErrors) {
|
||||
((ZUGFeRDExporterFromA3)theExporter).ignorePDFAErrors();
|
||||
}
|
||||
} else if (PDFAVersion == 1) {
|
||||
theExporter = new ZUGFeRDExporterFromA1();
|
||||
if (ignorePDFAErrors) {
|
||||
((ZUGFeRDExporterFromA1)theExporter).ignorePDFAErrors();
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("PDF-A version not supported");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
protected IZUGFeRDExporter getExporter() {
|
||||
|
||||
public IZUGFeRDExporter getExporter() {
|
||||
if (theExporter==null) {
|
||||
throw new RuntimeException("In ZUGFeRDExporterFromPDFA, source must always be loaded before other operations are performed.");
|
||||
}
|
||||
@@ -79,7 +101,7 @@ public class ZUGFeRDExporterFromPDFA implements IZUGFeRDExporter {
|
||||
* @throws IOException
|
||||
*/
|
||||
private int getPDFAVersion(byte[] byteArrayInputStream) throws IOException {
|
||||
PDDocument document = PDDocument.load(byteArrayInputStream);
|
||||
PDDocument document = Loader.loadPDF(byteArrayInputStream);
|
||||
PDDocumentCatalog catalog = document.getDocumentCatalog();
|
||||
PDMetadata metadata = catalog.getMetadata();
|
||||
// the PDF version we could get through the document but we want the PDF-A version,
|
||||
@@ -89,7 +111,7 @@ public class ZUGFeRDExporterFromPDFA implements IZUGFeRDExporter {
|
||||
DomXmpParser xmpParser = new DomXmpParser();
|
||||
XMPMetadata xmp = xmpParser.parse(metadata.createInputStream());
|
||||
|
||||
PDFAIdentificationSchema pdfaSchema = xmp.getPDFIdentificationSchema();
|
||||
PDFAIdentificationSchema pdfaSchema = xmp.getPDFAIdentificationSchema();
|
||||
if (pdfaSchema != null) {
|
||||
return pdfaSchema.getPart();
|
||||
}
|
||||
@@ -147,16 +169,16 @@ public class ZUGFeRDExporterFromPDFA implements IZUGFeRDExporter {
|
||||
return getExporter().setCreator(creator);
|
||||
}
|
||||
|
||||
public ZUGFeRDExporterFromPDFA setProfile(Profile p) {
|
||||
return (ZUGFeRDExporterFromPDFA) getExporter().setProfile(p);
|
||||
public IZUGFeRDExporter setProfile(Profile p) {
|
||||
return getExporter().setProfile(p);
|
||||
}
|
||||
|
||||
public ZUGFeRDExporterFromPDFA setProfile(String profileName) {
|
||||
public IZUGFeRDExporter setProfile(String profileName) {
|
||||
Profile p = Profiles.getByName(profileName);
|
||||
if (p==null) {
|
||||
throw new RuntimeException("Profile not found.");
|
||||
}
|
||||
return (ZUGFeRDExporterFromPDFA) getExporter().setProfile(p);
|
||||
return getExporter().setProfile(p);
|
||||
}
|
||||
|
||||
public IZUGFeRDExporter setConformanceLevel(PDFAConformanceLevel newLevel) {
|
||||
@@ -227,5 +249,14 @@ public class ZUGFeRDExporterFromPDFA implements IZUGFeRDExporter {
|
||||
public void export(OutputStream output) throws IOException {
|
||||
getExporter().export(output);
|
||||
}
|
||||
|
||||
public void attachFile(FileAttachment file) {
|
||||
theExporter.attachFile(file);
|
||||
}
|
||||
|
||||
public void attachFile(String filename, byte[] data, String mimetype, String relation) {
|
||||
theExporter.attachFile(filename, data, mimetype, relation);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -13,24 +13,15 @@ package org.mustangproject.ZUGFeRD;
|
||||
* @version 1.1.0
|
||||
* @author jstaerk
|
||||
*/
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.*;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
@@ -41,204 +32,63 @@ import javax.xml.xpath.XPathExpression;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.fop.util.XMLUtil;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.io.IOUtils;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
|
||||
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
|
||||
import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
|
||||
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
|
||||
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
|
||||
import org.mustangproject.EStandard;
|
||||
import org.mustangproject.Item;
|
||||
import org.mustangproject.Product;
|
||||
import org.mustangproject.XMLTools;
|
||||
import org.mustangproject.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
public class ZUGFeRDImporter {
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.xpath.*;
|
||||
import java.io.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* if metadata has been found
|
||||
public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRDImporter.class);
|
||||
|
||||
public ZUGFeRDImporter() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ZUGFeRDImporter(String filename) {
|
||||
super(filename);
|
||||
}
|
||||
|
||||
public ZUGFeRDImporter(InputStream stream) {
|
||||
super(stream);
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* return the file names of all files embedded into the PDF
|
||||
* for XML embedded files please use ZUGFeRDInvoiceImporter.getFileAttachmentsXML
|
||||
* @return a ArrayList of FileAttachments, empty if none
|
||||
*/
|
||||
protected boolean containsMeta = false;
|
||||
/**
|
||||
* map filenames of additional XML files to their contents
|
||||
*/
|
||||
private final HashMap<String, byte[]> additionalXMLs = new HashMap<>();
|
||||
/**
|
||||
* Raw XML form of the extracted data - may be directly obtained.
|
||||
*/
|
||||
private byte[] rawXML = null;
|
||||
/**
|
||||
* XMP metadata
|
||||
*/
|
||||
private String xmpString = null; // XMP metadata
|
||||
/**
|
||||
* parsed Document
|
||||
*/
|
||||
private Document document;
|
||||
private Integer version;
|
||||
|
||||
|
||||
protected ZUGFeRDImporter() {
|
||||
//constructor for extending classes
|
||||
public List<FileAttachment> getFileAttachmentsPDF() {
|
||||
return PDFAttachments;
|
||||
}
|
||||
|
||||
public ZUGFeRDImporter(String pdfFilename) {
|
||||
try (InputStream bis = Files.newInputStream(Paths.get(pdfFilename), StandardOpenOption.READ)) {
|
||||
extractLowLevel(bis);
|
||||
} catch (final IOException e) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
|
||||
throw new ZUGFeRDExportException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public ZUGFeRDImporter(InputStream pdfStream) {
|
||||
try {
|
||||
extractLowLevel(pdfStream);
|
||||
} catch (final IOException e) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
|
||||
throw new ZUGFeRDExportException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extracts a ZUGFeRD invoice from a PDF document represented by an input stream. Errors are reported via exception handling.
|
||||
*
|
||||
* @param inStream a inputstream of a pdf file
|
||||
*/
|
||||
private void extractLowLevel(InputStream inStream) throws IOException {
|
||||
BufferedInputStream pdfStream=new BufferedInputStream(inStream);
|
||||
byte[] pad = new byte[4];
|
||||
pdfStream.mark(0);
|
||||
pdfStream.read(pad);
|
||||
pdfStream.reset();
|
||||
byte[] pdfSignature = { '%', 'P', 'D', 'F' };
|
||||
if (pad.equals(pdfSignature)) { // we have a pdf
|
||||
|
||||
|
||||
try (PDDocument doc = PDDocument.load(pdfStream)) {
|
||||
// PDDocumentInformation info = doc.getDocumentInformation();
|
||||
final PDDocumentNameDictionary names = new PDDocumentNameDictionary(doc.getDocumentCatalog());
|
||||
//start
|
||||
|
||||
if (doc.getDocumentCatalog() == null || doc.getDocumentCatalog().getMetadata() == null) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.INFO, "no-xmlpart");
|
||||
return;
|
||||
}
|
||||
|
||||
final InputStream XMP = doc.getDocumentCatalog().getMetadata().exportXMPMetadata();
|
||||
xmpString = convertStreamToString(XMP);
|
||||
|
||||
final PDEmbeddedFilesNameTreeNode etn = names.getEmbeddedFiles();
|
||||
if (etn == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Map<String, PDComplexFileSpecification> efMap = etn.getNames();
|
||||
// String filePath = "/tmp/";
|
||||
|
||||
if (efMap != null) {
|
||||
extractFiles(efMap); // see
|
||||
// https://memorynotfound.com/apache-pdfbox-extract-embedded-file-pdf-document/
|
||||
} else {
|
||||
|
||||
final List<PDNameTreeNode<PDComplexFileSpecification>> kids = etn.getKids();
|
||||
for (final PDNameTreeNode<PDComplexFileSpecification> node : kids) {
|
||||
final Map<String, PDComplexFileSpecification> namesL = node.getNames();
|
||||
extractFiles(namesL);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// no PDF probably XML
|
||||
containsMeta = true;
|
||||
setRawXML(XMLTools.getBytesFromStream(pdfStream));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void extractFiles(Map<String, PDComplexFileSpecification> names) throws IOException {
|
||||
for (final String alias : names.keySet()) {
|
||||
|
||||
final PDComplexFileSpecification fileSpec = names.get(alias);
|
||||
final String filename = fileSpec.getFilename();
|
||||
/**
|
||||
* filenames for invoice data (ZUGFeRD v1 and v2, Factur-X)
|
||||
*/
|
||||
if ((filename.equals("ZUGFeRD-invoice.xml") || (filename.equals("zugferd-invoice.xml")) || filename.equals("factur-x.xml")) || filename.equals("xrechnung.xml") || filename.equals("order-x.xml") || filename.equals("cida.xml")) {
|
||||
containsMeta = true;
|
||||
|
||||
final PDEmbeddedFile embeddedFile = fileSpec.getEmbeddedFile();
|
||||
// String embeddedFilename = filePath + filename;
|
||||
// File file = new File(filePath + filename);
|
||||
// System.out.println("Writing " + embeddedFilename);
|
||||
// ByteArrayOutputStream fileBytes=new
|
||||
// ByteArrayOutputStream();
|
||||
// FileOutputStream fos = new FileOutputStream(file);
|
||||
|
||||
setRawXML(embeddedFile.toByteArray());
|
||||
|
||||
// fos.write(embeddedFile.getByteArray());
|
||||
// fos.close();
|
||||
}
|
||||
if (filename.startsWith("additional_data")) {
|
||||
final PDEmbeddedFile embeddedFile = fileSpec.getEmbeddedFile();
|
||||
additionalXMLs.put(filename, embeddedFile.toByteArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected Document getDocument() {
|
||||
return document;
|
||||
}
|
||||
|
||||
|
||||
private void setDocument() throws ParserConfigurationException, IOException, SAXException {
|
||||
final DocumentBuilderFactory xmlFact = DocumentBuilderFactory.newInstance();
|
||||
xmlFact.setNamespaceAware(true);
|
||||
final DocumentBuilder builder = xmlFact.newDocumentBuilder();
|
||||
final ByteArrayInputStream is = new ByteArrayInputStream(rawXML);
|
||||
/// is.skip(guessBOMSize(is));
|
||||
document = builder.parse(is);
|
||||
}
|
||||
|
||||
|
||||
public void setRawXML(byte[] rawXML) throws IOException {
|
||||
this.rawXML = rawXML;
|
||||
this.version = null;
|
||||
try {
|
||||
setDocument();
|
||||
} catch (ParserConfigurationException | SAXException e) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
|
||||
throw new ZUGFeRDExportException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
protected String extractString(String xpathStr) {
|
||||
if (!containsMeta) {
|
||||
throw new ZUGFeRDExportException("No suitable data/ZUGFeRD file could be found.");
|
||||
}
|
||||
final String result;
|
||||
try {
|
||||
final Document document = getDocument();
|
||||
final XPathFactory xpathFact = XPathFactory.newInstance();
|
||||
final XPath xpath = xpathFact.newXPath();
|
||||
result = xpath.evaluate(xpathStr, document);
|
||||
} catch (final XPathExpressionException e) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
|
||||
throw new ZUGFeRDExportException(e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/***
|
||||
* Wrapper for protected method extractString
|
||||
@@ -250,22 +100,26 @@ public class ZUGFeRDImporter {
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////
|
||||
|
||||
/**
|
||||
* @return the reference (purpose) the sender specified for this invoice
|
||||
*/
|
||||
public String getForeignReference() {
|
||||
String result = extractString("//*[local-name() = 'ApplicableHeaderTradeSettlement']/*[local-name() = 'PaymentReference']");
|
||||
if (result == null || result.isEmpty()) {
|
||||
result = extractString("//*[local-name() = 'ApplicableSupplyChainTradeSettlement']/*[local-name() = 'PaymentReference']");
|
||||
}
|
||||
return result;
|
||||
|
||||
return importedInvoice.getNumber();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the ZUGFeRD Profile
|
||||
*/
|
||||
public String getZUGFeRDProfil() {
|
||||
switch (extractString("//*[local-name() = 'GuidelineSpecifiedDocumentContextParameter']//*[local-name() = 'ID']")) {
|
||||
|
||||
String guideline = extractString("//*[local-name() = 'GuidelineSpecifiedDocumentContextParameter']//*[local-name() = 'ID']");
|
||||
if (guideline.contains("xrechnung")) {
|
||||
return "XRECHNUNG";
|
||||
}
|
||||
switch (guideline) {
|
||||
case "urn:cen.eu:en16931:2017":
|
||||
case "urn:ferd:CrossIndustryDocument:invoice:1p0:comfort":
|
||||
return "COMFORT";
|
||||
@@ -284,22 +138,6 @@ public class ZUGFeRDImporter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Invoice Currency Code
|
||||
*/
|
||||
public String getInvoiceCurrencyCode() {
|
||||
try {
|
||||
if (getVersion() == 1) {
|
||||
return extractString("//*[local-name() = 'ApplicableSupplyChainTradeSettlement']//*[local-name() = 'InvoiceCurrencyCode']");
|
||||
} else {
|
||||
return extractString("//*[local-name() = 'ApplicableHeaderTradeSettlement']//*[local-name() = 'InvoiceCurrencyCode']");
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the IssuerAssigned ID
|
||||
@@ -322,56 +160,6 @@ public class ZUGFeRDImporter {
|
||||
return extractIssuerAssignedID("ContractReferencedDocument");
|
||||
}
|
||||
|
||||
private String extractIssuerAssignedID(String propertyName) {
|
||||
try {
|
||||
if (getVersion() == 1) {
|
||||
return extractString("//*[local-name() = '" + propertyName + "']//*[local-name() = 'ID']");
|
||||
} else {
|
||||
return extractString("//*[local-name() = '" + propertyName + "']//*[local-name() = 'IssuerAssignedID']");
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the BuyerTradeParty ID
|
||||
*/
|
||||
public String getBuyerTradePartyID() {
|
||||
return extractString("//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'ID']");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Issue Date()
|
||||
*/
|
||||
public String getIssueDate() {
|
||||
try {
|
||||
if (getVersion() == 1) {
|
||||
return extractString("//*[local-name() = 'HeaderExchangedDocument']//*[local-name() = 'IssueDateTime']//*[local-name() = 'DateTimeString']");
|
||||
} else {
|
||||
return extractString("//*[local-name() = 'ExchangedDocument']//*[local-name() = 'IssueDateTime']//*[local-name() = 'DateTimeString']");
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public Date getDetailedDeliveryPeriodFrom(){
|
||||
final String toParse = extractString(
|
||||
"//*[local-name() = 'ApplicableHeaderTradeSettlement']" +
|
||||
"//*[local-name() = 'BillingSpecifiedPeriod']" +
|
||||
"//*[local-name() = 'StartDateTime']//*[local-name() = 'DateTimeString']");
|
||||
return tryDate(toParse);
|
||||
}
|
||||
public Date getDetailedDeliveryPeriodTo(){
|
||||
final String toParse = extractString(
|
||||
"//*[local-name() = 'ApplicableHeaderTradeSettlement']" +
|
||||
"//*[local-name() = 'BillingSpecifiedPeriod']" +
|
||||
"//*[local-name() = 'EndDateTime']//*[local-name() = 'DateTimeString']");
|
||||
return tryDate(toParse);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the TaxBasisTotalAmount
|
||||
@@ -384,7 +172,7 @@ public class ZUGFeRDImporter {
|
||||
return extractString("//*[local-name() = 'SpecifiedTradeSettlementHeaderMonetarySummation']//*[local-name() = 'TaxBasisTotalAmount']");
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
|
||||
// Exception was already logged
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -400,7 +188,7 @@ public class ZUGFeRDImporter {
|
||||
return extractString("//*[local-name() = 'SpecifiedTradeSettlementHeaderMonetarySummation']//*[local-name() = 'TaxTotalAmount']");
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
|
||||
// Exception was already logged
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -416,7 +204,7 @@ public class ZUGFeRDImporter {
|
||||
return extractString("//*[local-name() = 'SpecifiedTradeSettlementHeaderMonetarySummation']//*[local-name() = 'RoundingAmount']");
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
|
||||
// Exception was already logged
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -432,7 +220,7 @@ public class ZUGFeRDImporter {
|
||||
return extractString("//*[local-name() = 'SpecifiedTradeSettlementHeaderMonetarySummation']//*[local-name() = 'TotalPrepaidAmount']");
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
|
||||
// Exception was already logged
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -455,7 +243,16 @@ public class ZUGFeRDImporter {
|
||||
* @return the BuyerTradeParty SpecifiedTaxRegistration ID
|
||||
*/
|
||||
public String getBuyertradePartySpecifiedTaxRegistrationID() {
|
||||
return extractString("//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'SpecifiedTaxRegistration']//*[local-name() = 'ID']");
|
||||
String id = null;
|
||||
if ((importedInvoice.getRecipient()!=null) && (importedInvoice.getRecipient().getLegalOrganisation()!=null)) {
|
||||
// this *should* be the official result
|
||||
id = importedInvoice.getRecipient().getLegalOrganisation().getSchemedID().getID();
|
||||
}
|
||||
// but also provide some fallback
|
||||
if (id == null) {
|
||||
id = getBuyerTradePartyID();
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
@@ -470,7 +267,7 @@ public class ZUGFeRDImporter {
|
||||
return extractString("//*[local-name() = 'ExchangedDocument']//*[local-name() = 'IncludedNote']");
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
|
||||
// Exception was already logged
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -479,16 +276,17 @@ public class ZUGFeRDImporter {
|
||||
* @return the BuyerTradeParty Name
|
||||
*/
|
||||
public String getBuyerTradePartyName() {
|
||||
return extractString("//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'Name']");
|
||||
} /**
|
||||
return importedInvoice.getRecipient().getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the BuyerTradeParty Name
|
||||
*/
|
||||
public String getDeliveryTradePartyName() {
|
||||
return extractString("//*[local-name() = 'ShipToTradeParty']//*[local-name() = 'Name']");
|
||||
return importedInvoice.getDeliveryAddress().getName();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @return the line Total Amount
|
||||
*/
|
||||
@@ -500,7 +298,7 @@ public class ZUGFeRDImporter {
|
||||
return extractString("//*[local-name() = 'SpecifiedTradeSettlementHeaderMonetarySummation']//*[local-name() = 'LineTotalAmount']");
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
|
||||
// Exception was already logged
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -523,7 +321,7 @@ public class ZUGFeRDImporter {
|
||||
return extractString("//*[local-name() = 'ActualDeliverySupplyChainEvent']//*[local-name() = 'OccurrenceDateTime']//*[local-name() = 'DateTimeString']");
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
|
||||
// Exception was already logged
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -532,18 +330,8 @@ public class ZUGFeRDImporter {
|
||||
* @return the Invoice ID
|
||||
*/
|
||||
public String getInvoiceID() {
|
||||
try {
|
||||
if (getVersion() == 1) {
|
||||
return extractString("//*[local-name() = 'HeaderExchangedDocument']//*[local-name() = 'ID']");
|
||||
} else {
|
||||
return extractString("//*[local-name() = 'ExchangedDocument']//*[local-name() = 'ID']");
|
||||
return importedInvoice.getNumber();
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@@ -557,7 +345,7 @@ public class ZUGFeRDImporter {
|
||||
return extractString("//*[local-name() = 'ExchangedDocument']/*[local-name() = 'TypeCode']");
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
|
||||
// Exception was already logged
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -574,7 +362,7 @@ public class ZUGFeRDImporter {
|
||||
return extractString("//*[local-name() = 'ApplicableHeaderTradeAgreement']/*[local-name() = 'BuyerReference']");
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
|
||||
// Exception was already logged
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -600,11 +388,21 @@ public class ZUGFeRDImporter {
|
||||
* @return the sender's account IBAN code
|
||||
*/
|
||||
public String getIBAN() {
|
||||
return extractString("//*[local-name() = 'PayeePartyCreditorFinancialAccount']/*[local-name() = 'IBANID']");
|
||||
for (IZUGFeRDTradeSettlement settlement : importedInvoice.getTradeSettlement()) {
|
||||
if (settlement instanceof IZUGFeRDTradeSettlementDebit) {
|
||||
return ((IZUGFeRDTradeSettlementDebit) settlement).getIBAN();
|
||||
}
|
||||
if (settlement instanceof IZUGFeRDTradeSettlementPayment) {
|
||||
return ((IZUGFeRDTradeSettlementPayment) settlement).getOwnIBAN();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public String getHolder() {
|
||||
|
||||
|
||||
return extractString("//*[local-name() = 'SellerTradeParty']/*[local-name() = 'Name']");
|
||||
}
|
||||
|
||||
@@ -613,14 +411,8 @@ public class ZUGFeRDImporter {
|
||||
* @return the total payable amount
|
||||
*/
|
||||
public String getAmount() {
|
||||
String result = extractString("//*[local-name() = 'SpecifiedTradeSettlementHeaderMonetarySummation']/*[local-name() = 'DuePayableAmount']");
|
||||
if (result == null || result.isEmpty()) {
|
||||
|
||||
/* fx/zf would be SpecifiedTradeSettlementMonetarySummation
|
||||
* but ox is SpecifiedTradeSettlementHeaderMonetarySummation...*/
|
||||
result = extractString("//*[local-name() = 'GrandTotalAmount']");
|
||||
}
|
||||
return result;
|
||||
return importedInvoice.getGrandTotal().toPlainString();
|
||||
}
|
||||
|
||||
|
||||
@@ -628,7 +420,60 @@ public class ZUGFeRDImporter {
|
||||
* @return when the payment is due
|
||||
*/
|
||||
public String getDueDate() {
|
||||
return extractString("//*[local-name() = 'SpecifiedTradePaymentTerms']/*[local-name() = 'DueDateDateTime']/*[local-name() = 'DateTimeString']");
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
|
||||
return sdf.format(importedInvoice.getDueDate());
|
||||
}
|
||||
|
||||
|
||||
////////////////////
|
||||
|
||||
/**
|
||||
* @return the Invoice Currency Code
|
||||
*/
|
||||
public String getInvoiceCurrencyCode() {
|
||||
return importedInvoice.getCurrency();
|
||||
}
|
||||
|
||||
|
||||
private String extractIssuerAssignedID(String propertyName) {
|
||||
try {
|
||||
if (getVersion() == 1) {
|
||||
return extractString("//*[local-name() = '" + propertyName + "']//*[local-name() = 'ID']");
|
||||
} else {
|
||||
return extractString("//*[local-name() = '" + propertyName + "']//*[local-name() = 'IssuerAssignedID']");
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
// Exception was already logged
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the BuyerTradeParty ID
|
||||
*/
|
||||
public String getBuyerTradePartyID() {
|
||||
String id = importedInvoice.getRecipient().getID();
|
||||
if (id == null) {
|
||||
// provide some fallback
|
||||
id = importedInvoice.getRecipient().getVATID();
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Issue Date()
|
||||
*/
|
||||
public String getIssueDate() {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
|
||||
return sdf.format(importedInvoice.getIssueDate());
|
||||
}
|
||||
|
||||
public Date getDetailedDeliveryPeriodFrom() {
|
||||
return importedInvoice.getDetailedDeliveryPeriodFrom();
|
||||
}
|
||||
|
||||
public Date getDetailedDeliveryPeriodTo() {
|
||||
return importedInvoice.getDetailedDeliveryPeriodTo();
|
||||
}
|
||||
|
||||
|
||||
@@ -676,27 +521,6 @@ public class ZUGFeRDImporter {
|
||||
}
|
||||
|
||||
|
||||
public EStandard getStandard() throws Exception {
|
||||
if (!containsMeta) {
|
||||
throw new Exception("Not yet parsed");
|
||||
}
|
||||
final String head = getUTF8();
|
||||
String rootNode=extractString("local-name(/*)");
|
||||
if (rootNode.equals("CrossIndustryDocument")) {
|
||||
return EStandard.zugferd;
|
||||
} else if (rootNode.equals("Invoice")) {
|
||||
return EStandard.ubl;
|
||||
} else if (rootNode.equals("CrossIndustryInvoice")) {
|
||||
return EStandard.facturx;
|
||||
} else if (rootNode.equals("SCRDMCCBDACIDAMessageStructure")) {
|
||||
return EStandard.despatchadvice;
|
||||
} else if (head.contains("<rsm:SCRDMCCBDACIOMessageStructure")) {
|
||||
return EStandard.orderx;
|
||||
}
|
||||
|
||||
throw new Exception("ZUGFeRD version could not be determined");
|
||||
|
||||
}
|
||||
public int getVersion() throws Exception {
|
||||
if (!containsMeta) {
|
||||
throw new Exception("Not yet parsed");
|
||||
@@ -713,43 +537,13 @@ public class ZUGFeRDImporter {
|
||||
version = 1;
|
||||
} else if (head.contains("<rsm:CrossIndustryInvoice")) {
|
||||
version = 2;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
throw new Exception("ZUGFeRD version could not be determined");
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return return UTF8 XML (without BOM) of the invoice
|
||||
*/
|
||||
public String getUTF8() {
|
||||
if (rawXML == null) {
|
||||
return null;
|
||||
}
|
||||
if (rawXML.length < 3) {
|
||||
return new String(rawXML);
|
||||
}
|
||||
|
||||
|
||||
final byte[] bomlessData;
|
||||
|
||||
if ((rawXML[0] == (byte) 0xEF)
|
||||
&& (rawXML[1] == (byte) 0xBB)
|
||||
&& (rawXML[2] == (byte) 0xBF)) {
|
||||
// I don't like BOMs, lets remove it
|
||||
bomlessData = new byte[rawXML.length - 3];
|
||||
System.arraycopy(rawXML, 3, bomlessData, 0,
|
||||
rawXML.length - 3);
|
||||
} else {
|
||||
bomlessData = rawXML;
|
||||
}
|
||||
|
||||
return new String(bomlessData);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the raw XML data as extracted from the ZUGFeRD PDF file.
|
||||
*
|
||||
@@ -775,15 +569,9 @@ public class ZUGFeRDImporter {
|
||||
}
|
||||
|
||||
|
||||
static String convertStreamToString(java.io.InputStream is) {
|
||||
// source https://stackoverflow.com/questions/309424/how-do-i-read-convert-an-inputstream-into-a-string-in-java referring to
|
||||
// https://community.oracle.com/blogs/pat/2004/10/23/stupid-scanner-tricks
|
||||
final Scanner s = new Scanner(is, "UTF-8").useDelimiter("\\A");
|
||||
return s.hasNext() ? s.next() : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* returns an instance of PostalTradeAddress for SellerTradeParty section
|
||||
*
|
||||
* @return an instance of PostalTradeAddress
|
||||
*/
|
||||
public PostalTradeAddress getBuyerTradePartyAddress() {
|
||||
@@ -792,12 +580,12 @@ public class ZUGFeRDImporter {
|
||||
|
||||
try {
|
||||
if (getVersion() == 1) {
|
||||
nl = getNodeListByPath("//*[local-name() = 'CrossIndustryDocument']//*[local-name() = 'SpecifiedSupplyChainTradeTransaction']/*[local-name() = 'ApplicableSupplyChainTradeAgreement']//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'PostalTradeAddress']");
|
||||
nl = getNodeListByPath("//*[localname() = 'CrossIndustryDocument']//*[local-name() = 'SpecifiedSupplyChainTradeTransaction']/*[local-name() = 'ApplicableSupplyChainTradeAgreement']//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'PostalTradeAddress']");
|
||||
} else {
|
||||
nl = getNodeListByPath("//*[local-name() = 'CrossIndustryInvoice']//*[local-name() = 'SupplyChainTradeTransaction']//*[local-name() = 'ApplicableHeaderTradeAgreement']//*[local-name() = 'BuyerTradeParty']//*[local-name() = 'PostalTradeAddress']");
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
|
||||
// Exception was already logged
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -806,6 +594,7 @@ public class ZUGFeRDImporter {
|
||||
|
||||
/**
|
||||
* returns an instance of PostalTradeAddress for SellerTradeParty section
|
||||
*
|
||||
* @return an instance of PostalTradeAddress
|
||||
*/
|
||||
public PostalTradeAddress getSellerTradePartyAddress() {
|
||||
@@ -818,7 +607,7 @@ public class ZUGFeRDImporter {
|
||||
nl = getNodeListByPath("//*[local-name() = 'CrossIndustryInvoice']//*[local-name() = 'SupplyChainTradeTransaction']//*[local-name() = 'ApplicableHeaderTradeAgreement']//*[local-name() = 'SellerTradeParty']//*[local-name() = 'PostalTradeAddress']");
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
|
||||
// Exception was already logged
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -827,6 +616,7 @@ public class ZUGFeRDImporter {
|
||||
|
||||
/**
|
||||
* returns an instance of PostalTradeAddress for ShipToTradeParty section
|
||||
*
|
||||
* @return an instance of PostalTradeAddress
|
||||
*/
|
||||
public PostalTradeAddress getDeliveryTradePartyAddress() {
|
||||
@@ -839,7 +629,7 @@ public class ZUGFeRDImporter {
|
||||
nl = getNodeListByPath("//*[local-name() = 'CrossIndustryInvoice']//*[local-name() = 'SupplyChainTradeTransaction']//*[local-name() = 'ApplicableHeaderTradeDelivery']//*[local-name() = 'ShipToTradeParty']//*[local-name() = 'PostalTradeAddress']");
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
|
||||
// Exception was already logged
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -910,6 +700,7 @@ public class ZUGFeRDImporter {
|
||||
|
||||
/**
|
||||
* returns a list of LineItems
|
||||
*
|
||||
* @return a List of LineItem instances
|
||||
*/
|
||||
public List<Item> getLineItemList() {
|
||||
@@ -933,7 +724,7 @@ public class ZUGFeRDImporter {
|
||||
if (node != null) {
|
||||
final NodeList tradeAgreementChildren = node.getChildNodes();
|
||||
node = getNodeByName(tradeAgreementChildren, "ChargeAmount");
|
||||
lineItem.setPrice(tryBigDecimal(getNodeValue(node)));
|
||||
lineItem.setPrice(XMLTools.tryBigDecimal(node));
|
||||
node = getNodeByName(tradeAgreementChildren, "BasisQuantity");
|
||||
if (node != null && node.getAttributes() != null) {
|
||||
final Node unitCodeAttribute = node.getAttributes().getNamedItem("unitCode");
|
||||
@@ -946,48 +737,55 @@ public class ZUGFeRDImporter {
|
||||
node = getNodeByName(nn.getChildNodes(), "GrossPriceProductTradePrice");
|
||||
if (node != null) {
|
||||
node = getNodeByName(node.getChildNodes(), "ChargeAmount");
|
||||
lineItem.setGrossPrice(tryBigDecimal(getNodeValue(node)));
|
||||
lineItem.setGrossPrice(XMLTools.tryBigDecimal(node));
|
||||
}
|
||||
break;
|
||||
|
||||
case "AssociatedDocumentLineDocument":
|
||||
|
||||
node = getNodeByName(nn.getChildNodes(), "LineID");
|
||||
lineItem.setId(getNodeValue(node));
|
||||
lineItem.setId(XMLTools.getNodeValue(node));
|
||||
break;
|
||||
|
||||
case "SpecifiedTradeProduct":
|
||||
|
||||
node = getNodeByName(nn.getChildNodes(), "GlobalID");
|
||||
if (node != null) {
|
||||
SchemedID globalId = new SchemedID()
|
||||
.setScheme(node.getAttributes()
|
||||
.getNamedItem("schemeID").getNodeValue())
|
||||
.setId(getNodeValue(node));
|
||||
lineItem.getProduct().addGlobalID(globalId);
|
||||
}
|
||||
node = getNodeByName(nn.getChildNodes(), "SellerAssignedID");
|
||||
lineItem.getProduct().setSellerAssignedID(getNodeValue(node));
|
||||
lineItem.getProduct().setSellerAssignedID(XMLTools.getNodeValue(node));
|
||||
|
||||
node = getNodeByName(nn.getChildNodes(), "BuyerAssignedID");
|
||||
lineItem.getProduct().setBuyerAssignedID(getNodeValue(node));
|
||||
lineItem.getProduct().setBuyerAssignedID(XMLTools.getNodeValue(node));
|
||||
|
||||
node = getNodeByName(nn.getChildNodes(), "Name");
|
||||
lineItem.getProduct().setName(getNodeValue(node));
|
||||
lineItem.getProduct().setName(XMLTools.getNodeValue(node));
|
||||
|
||||
node = getNodeByName(nn.getChildNodes(), "Description");
|
||||
lineItem.getProduct().setDescription(getNodeValue(node));
|
||||
lineItem.getProduct().setDescription(XMLTools.getNodeValue(node));
|
||||
break;
|
||||
|
||||
case "SpecifiedLineTradeDelivery":
|
||||
case "SpecifiedSupplyChainTradeDelivery":
|
||||
node = getNodeByName(nn.getChildNodes(), "BilledQuantity");
|
||||
lineItem.setQuantity(tryBigDecimal(getNodeValue(node)));
|
||||
lineItem.setQuantity(XMLTools.tryBigDecimal(node));
|
||||
break;
|
||||
|
||||
case "SpecifiedLineTradeSettlement":
|
||||
node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax");
|
||||
if (node != null) {
|
||||
node = getNodeByName(node.getChildNodes(), "RateApplicablePercent");
|
||||
lineItem.getProduct().setVATPercent(tryBigDecimal(getNodeValue(node)));
|
||||
lineItem.getProduct().setVATPercent(XMLTools.tryBigDecimal(node));
|
||||
}
|
||||
|
||||
node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax");
|
||||
if (node != null) {
|
||||
node = getNodeByName(node.getChildNodes(), "CalculatedAmount");
|
||||
lineItem.setTax(tryBigDecimal(getNodeValue(node)));
|
||||
lineItem.setTax(XMLTools.tryBigDecimal(node));
|
||||
}
|
||||
node = getNodeByName(nn.getChildNodes(), "BillingSpecifiedPeriod");
|
||||
if (node != null) {
|
||||
@@ -1001,13 +799,13 @@ public class ZUGFeRDImporter {
|
||||
if (end != null) {
|
||||
dateTimeEnd = getNodeByName(end.getChildNodes(), "DateTimeString");
|
||||
}
|
||||
lineItem.setDetailedDeliveryPeriod(tryDate(dateTimeStart), tryDate(dateTimeEnd));
|
||||
lineItem.setDetailedDeliveryPeriod(XMLTools.tryDate(dateTimeStart), XMLTools.tryDate(dateTimeEnd));
|
||||
}
|
||||
|
||||
node = getNodeByName(nn.getChildNodes(), "SpecifiedTradeSettlementLineMonetarySummation");
|
||||
if (node != null) {
|
||||
node = getNodeByName(node.getChildNodes(), "LineTotalAmount");
|
||||
lineItem.setLineTotalAmount(tryBigDecimal(getNodeValue(node)));
|
||||
lineItem.setLineTotalAmount(XMLTools.tryBigDecimal(node));
|
||||
}
|
||||
break;
|
||||
case "SpecifiedSupplyChainTradeSettlement":
|
||||
@@ -1016,19 +814,19 @@ public class ZUGFeRDImporter {
|
||||
node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax");
|
||||
if (node != null) {
|
||||
node = getNodeByName(node.getChildNodes(), "ApplicablePercent");
|
||||
lineItem.getProduct().setVATPercent(tryBigDecimal(getNodeValue(node)));
|
||||
lineItem.getProduct().setVATPercent(XMLTools.tryBigDecimal(node));
|
||||
}
|
||||
|
||||
node = getNodeByName(nn.getChildNodes(), "ApplicableTradeTax");
|
||||
if (node != null) {
|
||||
node = getNodeByName(node.getChildNodes(), "CalculatedAmount");
|
||||
lineItem.setTax(tryBigDecimal(getNodeValue(node)));
|
||||
lineItem.setTax(XMLTools.tryBigDecimal(node));
|
||||
}
|
||||
|
||||
node = getNodeByName(nn.getChildNodes(), "SpecifiedTradeSettlementMonetarySummation");
|
||||
if (node != null) {
|
||||
node = getNodeByName(node.getChildNodes(), "LineTotalAmount");
|
||||
lineItem.setLineTotalAmount(tryBigDecimal(getNodeValue(node)));
|
||||
lineItem.setLineTotalAmount(XMLTools.tryBigDecimal(node));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1041,6 +839,7 @@ public class ZUGFeRDImporter {
|
||||
|
||||
/**
|
||||
* returns a List of LineItem Nodes from ZUGFeRD XML
|
||||
*
|
||||
* @return a List of Node instances
|
||||
*/
|
||||
public List<Node> getLineItemNodes() {
|
||||
@@ -1050,7 +849,7 @@ public class ZUGFeRDImporter {
|
||||
nl = getNodeListByPath("//*[local-name() = 'IncludedSupplyChainTradeLineItem']");
|
||||
|
||||
} catch (final Exception e) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
|
||||
// Exception was already logged
|
||||
}
|
||||
|
||||
for (int i = 0; i < nl.getLength(); i++) {
|
||||
@@ -1062,6 +861,7 @@ public class ZUGFeRDImporter {
|
||||
|
||||
/**
|
||||
* Returns a node, found by name. If more nodes with the same name are present, the first occurence will be returned
|
||||
*
|
||||
* @param nl - A NodeList which may contains the searched node
|
||||
* @param name The nodes name
|
||||
* @return a Node or null, if nothing is found
|
||||
@@ -1082,6 +882,7 @@ public class ZUGFeRDImporter {
|
||||
|
||||
/**
|
||||
* Get a NodeList by providing an path
|
||||
*
|
||||
* @param path a compliable Path
|
||||
* @return a Nodelist or null, if an error occurs
|
||||
*/
|
||||
@@ -1095,53 +896,9 @@ public class ZUGFeRDImporter {
|
||||
final XPathExpression xpr = xPath.compile(s);
|
||||
return (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
} catch (final Exception e) {
|
||||
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
|
||||
LOGGER.error("Failed to evaluate XPath", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the value of an node
|
||||
* @param node the Node to get the value from
|
||||
* @return A String or empty String, if no value was found
|
||||
*/
|
||||
private String getNodeValue(Node node) {
|
||||
if (node != null && node.getFirstChild() != null) {
|
||||
return node.getFirstChild().getNodeValue();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* tries to convert an String to BigDecimal.
|
||||
* @param nodeValue The value as String
|
||||
* @return a BigDecimal with the value provides as String or a BigDecimal with value 0.00 if an error occurs
|
||||
*/
|
||||
private BigDecimal tryBigDecimal(String nodeValue) {
|
||||
try {
|
||||
return new BigDecimal(nodeValue);
|
||||
} catch (final Exception e) {
|
||||
try {
|
||||
return BigDecimal.valueOf(Float.valueOf(nodeValue));
|
||||
} catch (final Exception ex) {
|
||||
return new BigDecimal("0.00");
|
||||
}
|
||||
}
|
||||
}
|
||||
private Date tryDate(Node node) {
|
||||
final String nodeValue = getNodeValue(node);
|
||||
if (nodeValue.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return tryDate(nodeValue);
|
||||
}
|
||||
|
||||
private static Date tryDate(String toParse) {
|
||||
final SimpleDateFormat formatter = ZUGFeRDDateFormat.DATE.getFormatter();
|
||||
try {
|
||||
return formatter.parse(toParse);
|
||||
} catch (final Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +1,275 @@
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathExpression;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
|
||||
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
|
||||
import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
|
||||
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
|
||||
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
|
||||
import org.mustangproject.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.xpath.*;
|
||||
import java.io.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
public class ZUGFeRDInvoiceImporter {
|
||||
|
||||
public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRDInvoiceImporter.class.getCanonicalName()); // log
|
||||
private boolean recalcPrice = false;
|
||||
private boolean ignoreCalculationErrors = false;
|
||||
/**
|
||||
* map filenames of additional XML files to their contents
|
||||
*/
|
||||
protected final HashMap<String, byte[]> additionalXMLs = new HashMap<>();
|
||||
/**
|
||||
* map filenames of all embedded files in the respective PDF
|
||||
*/
|
||||
protected final ArrayList<FileAttachment> PDFAttachments = new ArrayList<>();
|
||||
/**
|
||||
* if metadata has been found
|
||||
*/
|
||||
protected boolean containsMeta = false;
|
||||
/**
|
||||
* Raw XML form of the extracted data - may be directly obtained.
|
||||
*/
|
||||
protected byte[] rawXML = null;
|
||||
/**
|
||||
* XMP metadata
|
||||
*/
|
||||
protected String xmpString = null; // XMP metadata
|
||||
/**
|
||||
* parsed Document
|
||||
*/
|
||||
protected Document document;
|
||||
/***
|
||||
* automatically parse into importedInvoice
|
||||
*/
|
||||
protected boolean parseAutomatically = true;
|
||||
protected Integer version;
|
||||
protected CalculatedInvoice importedInvoice = null;
|
||||
protected boolean recalcPrice = false;
|
||||
protected boolean ignoreCalculationErrors = false;
|
||||
protected ArrayList<FileAttachment> fileAttachments = new ArrayList<>();
|
||||
|
||||
public ZUGFeRDInvoiceImporter() {
|
||||
super();
|
||||
//constructor for extending classes
|
||||
}
|
||||
|
||||
public ZUGFeRDInvoiceImporter(String filename) {
|
||||
super(filename);
|
||||
public ZUGFeRDInvoiceImporter(String pdfFilename) {
|
||||
setPDFFilename(pdfFilename);
|
||||
}
|
||||
|
||||
public ZUGFeRDInvoiceImporter(InputStream stream) {
|
||||
super(stream);
|
||||
public ZUGFeRDInvoiceImporter(InputStream pdfStream) {
|
||||
setInputStream(pdfStream);
|
||||
}
|
||||
|
||||
public void fromXML(String XML) {
|
||||
public void setPDFFilename(String pdfFilename) {
|
||||
try (InputStream bis = Files.newInputStream(Paths.get(pdfFilename), StandardOpenOption.READ)) {
|
||||
extractLowLevel(bis);
|
||||
} catch (final IOException e) {
|
||||
LOGGER.error("Failed to extract ZUGFeRD data", e);
|
||||
throw new ZUGFeRDExportException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void setInputStream(InputStream pdfStream) {
|
||||
try {
|
||||
containsMeta = true;
|
||||
setRawXML(XML.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (IOException e) {
|
||||
LOGGER.error(e.getMessage(), e);
|
||||
extractLowLevel(pdfStream);
|
||||
} catch (final IOException e) {
|
||||
LOGGER.error("Failed to extract ZUGFeRD data", e);
|
||||
throw new ZUGFeRDExportException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* return the file names of all files embedded into the PDF
|
||||
* @see for XML embedded files please use ZUGFeRDInvoiceImporter.getFileAttachmentsXML
|
||||
* @return a ArrayList of FileAttachments, empty if none
|
||||
*/
|
||||
public List<FileAttachment> getFileAttachmentsPDF() {
|
||||
return PDFAttachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a ZUGFeRD invoice from a PDF document represented by an input stream. Errors are reported via exception handling.
|
||||
*
|
||||
* @param inStream a inputstream of a pdf file
|
||||
*/
|
||||
private void extractLowLevel(InputStream inStream) throws IOException {
|
||||
BufferedInputStream pdfStream = new BufferedInputStream(inStream);
|
||||
byte[] pad = new byte[4];
|
||||
pdfStream.mark(0);
|
||||
pdfStream.read(pad);
|
||||
pdfStream.reset();
|
||||
byte[] pdfSignature = {'%', 'P', 'D', 'F'};
|
||||
if (Arrays.equals(pad, pdfSignature)) { // we have a pdf
|
||||
|
||||
|
||||
try {
|
||||
PDDocument doc = Loader.loadPDF(IOUtils.toByteArray(pdfStream));
|
||||
// PDDocumentInformation info = doc.getDocumentInformation();
|
||||
final PDDocumentNameDictionary names = new PDDocumentNameDictionary(doc.getDocumentCatalog());
|
||||
//start
|
||||
|
||||
if (doc.getDocumentCatalog() == null || doc.getDocumentCatalog().getMetadata() == null) {
|
||||
LOGGER.info("no-xmlpart");
|
||||
return;
|
||||
}
|
||||
|
||||
final InputStream XMP = doc.getDocumentCatalog().getMetadata().exportXMPMetadata();
|
||||
|
||||
xmpString = new String(XMLTools.getBytesFromStream(XMP), StandardCharsets.UTF_8);
|
||||
|
||||
final PDEmbeddedFilesNameTreeNode etn = names.getEmbeddedFiles();
|
||||
if (etn == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Map<String, PDComplexFileSpecification> efMap = etn.getNames();
|
||||
// String filePath = "/tmp/";
|
||||
|
||||
if (efMap != null) {
|
||||
extractFiles(efMap); // see
|
||||
// https://memorynotfound.com/apache-pdfbox-extract-embedded-file-pdf-document/
|
||||
} else {
|
||||
|
||||
final List<PDNameTreeNode<PDComplexFileSpecification>> kids = etn.getKids();
|
||||
if (kids == null) {
|
||||
return;
|
||||
}
|
||||
for (final PDNameTreeNode<PDComplexFileSpecification> node : kids) {
|
||||
final Map<String, PDComplexFileSpecification> namesL = node.getNames();
|
||||
extractFiles(namesL);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to parse PDF", e);
|
||||
//ignore otherwise
|
||||
}
|
||||
} else {
|
||||
// no PDF probably XML
|
||||
containsMeta = true;
|
||||
setRawXML(XMLTools.getBytesFromStream(pdfStream));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* have the item prices be determined from the line total.
|
||||
* That's a workaround for some invoices which just put 0 as item price
|
||||
*/
|
||||
public void doRecalculateItemPricesFromLineTotals() {
|
||||
recalcPrice = true;
|
||||
}
|
||||
|
||||
/***
|
||||
* do not raise ParseExceptions even if the reproduced invoice total does not match the given value
|
||||
*/
|
||||
public void doIgnoreCalculationErrors() {
|
||||
ignoreCalculationErrors = true;
|
||||
}
|
||||
|
||||
/***
|
||||
* sets th pdf attachments, and if a file is recognized (e.g. a factur-x.xml) triggers processing
|
||||
* @param names the Hashmap of String, PDComplexFileSpecification
|
||||
* @throws IOException
|
||||
*/
|
||||
private void extractFiles(Map<String, PDComplexFileSpecification> names) throws IOException {
|
||||
for (final String alias : names.keySet()) {
|
||||
|
||||
final PDComplexFileSpecification fileSpec = names.get(alias);
|
||||
final String filename = fileSpec.getFilename();
|
||||
/**
|
||||
* filenames for invoice data (ZUGFeRD v1 and v2, Factur-X)
|
||||
*/
|
||||
|
||||
final PDEmbeddedFile embeddedFile = fileSpec.getEmbeddedFile();
|
||||
if ((filename.equals("ZUGFeRD-invoice.xml") || (filename.equals("zugferd-invoice.xml")) || filename.equals("factur-x.xml")) || filename.equals("xrechnung.xml") || filename.equals("order-x.xml") || filename.equals("cida.xml")) {
|
||||
containsMeta = true;
|
||||
|
||||
// String embeddedFilename = filePath + filename;
|
||||
// File file = new File(filePath + filename);
|
||||
// System.out.println("Writing " + embeddedFilename);
|
||||
// ByteArrayOutputStream fileBytes=new
|
||||
// ByteArrayOutputStream();
|
||||
// FileOutputStream fos = new FileOutputStream(file);
|
||||
|
||||
setRawXML(embeddedFile.toByteArray());
|
||||
|
||||
// fos.write(embeddedFile.getByteArray());
|
||||
// fos.close();
|
||||
}
|
||||
if (filename.startsWith("additional_data")) {
|
||||
additionalXMLs.put(filename, embeddedFile.toByteArray());
|
||||
}
|
||||
PDFAttachments.add(new FileAttachment(filename, embeddedFile.getSubtype(), "Data", embeddedFile.toByteArray()));
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* set the xml of a CII invoice
|
||||
* @param rawXML the xml string
|
||||
* @param doParse automatically parse input for zugferdImporter (not ZUGFeRDInvoiceImporter)
|
||||
* @throws IOException
|
||||
*/
|
||||
public void setRawXML(byte[] rawXML, boolean doParse) throws IOException {
|
||||
this.containsMeta = true;
|
||||
this.rawXML = rawXML;
|
||||
this.version = null;
|
||||
parseAutomatically = doParse;
|
||||
|
||||
try {
|
||||
setDocument();
|
||||
} catch (ParserConfigurationException | SAXException e) {
|
||||
LOGGER.error("Failed to parse XML", e);
|
||||
throw new ZUGFeRDExportException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/***
|
||||
* set the xml of a CII invoice, simple version
|
||||
* @param rawXML the cii(?) as a string
|
||||
* @throws IOException
|
||||
*/
|
||||
public void setRawXML(byte[] rawXML) throws IOException {
|
||||
setRawXML(rawXML, true);
|
||||
}
|
||||
|
||||
private void setDocument() throws ParserConfigurationException, IOException, SAXException {
|
||||
final DocumentBuilderFactory xmlFact = DocumentBuilderFactory.newInstance();
|
||||
xmlFact.setNamespaceAware(true);
|
||||
final DocumentBuilder builder = xmlFact.newDocumentBuilder();
|
||||
final ByteArrayInputStream is = new ByteArrayInputStream(rawXML);
|
||||
/// is.skip(guessBOMSize(is));
|
||||
document = builder.parse(is);
|
||||
if (parseAutomatically) {
|
||||
try {
|
||||
importedInvoice = new CalculatedInvoice();
|
||||
extractInto(importedInvoice);
|
||||
} catch (XPathExpressionException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (ParseException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* This will parse a XML into the given invoice object
|
||||
@@ -59,6 +281,9 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
|
||||
public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseException {
|
||||
|
||||
String number = "";
|
||||
String typeCode = null;
|
||||
String deliveryPeriodStart = null;
|
||||
String deliveryPeriodEnd = null;
|
||||
/*
|
||||
* dummywerte sind derzeit noch setDueDate setIssueDate setDeliveryDate
|
||||
* setSender setRecipient setnumber bspw. due date
|
||||
@@ -68,9 +293,19 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
|
||||
XPath xpath = xpathFact.newXPath();
|
||||
XPathExpression xpr = xpath.compile("//*[local-name()=\"SellerTradeParty\"]|//*[local-name()=\"AccountingSupplierParty\"]/*");
|
||||
NodeList SellerNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
XPathExpression shipEx = xpath.compile("//*[local-name()=\"ShipToTradeParty\"]");
|
||||
NodeList deliveryNodes = (NodeList) shipEx.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
if (deliveryNodes != null) {
|
||||
zpp.setDeliveryAddress(new TradeParty(deliveryNodes));
|
||||
}
|
||||
|
||||
|
||||
xpr = xpath.compile("//*[local-name()=\"BuyerTradeParty\"]|//*[local-name()=\"AccountingCustomerParty\"]/*");
|
||||
NodeList BuyerNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
|
||||
xpr = xpath.compile("//*[local-name()=\"PayeeTradeParty\"]|//*[local-name()=\"PayeeParty\"]/*");
|
||||
NodeList payeeNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
|
||||
xpr = xpath.compile("//*[local-name()=\"ExchangedDocument\"]|//*[local-name()=\"HeaderExchangedDocument\"]");
|
||||
NodeList ExchangedDocumentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
|
||||
@@ -78,32 +313,42 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
|
||||
BigDecimal expectedGrandTotal = null;
|
||||
NodeList totalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
if (totalNodes.getLength() > 0) {
|
||||
expectedGrandTotal = new BigDecimal(totalNodes.item(0).getTextContent());
|
||||
expectedGrandTotal = new BigDecimal(XMLTools.trimOrNull(totalNodes.item(0)));
|
||||
if (zpp instanceof CalculatedInvoice) {
|
||||
// usually we would re-calculate the invoice to get expectedGrandTotal
|
||||
// however, for "minimal" invoices or other invoices without lines
|
||||
// this will not work
|
||||
((CalculatedInvoice) zpp).setGrandTotal(expectedGrandTotal);
|
||||
}
|
||||
}
|
||||
|
||||
xpr = xpath.compile("//*[local-name()=\"PrepaidAmount\"]");
|
||||
NodeList prepaidNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
if (prepaidNodes.getLength() > 0) {
|
||||
zpp.setTotalPrepaidAmount(new BigDecimal(XMLTools.trimOrNull(prepaidNodes.item(0))));
|
||||
}
|
||||
|
||||
Date issueDate = null;
|
||||
Date dueDate = null;
|
||||
Date deliveryDate = null;
|
||||
String despatchAdviceReferencedDocument = null;
|
||||
for (int i = 0; i < ExchangedDocumentNodes.getLength(); i++) {
|
||||
|
||||
// nodes.item(i).getTextContent())) {
|
||||
Node exchangedDocumentNode = ExchangedDocumentNodes.item(i);
|
||||
NodeList exchangedDocumentChilds = exchangedDocumentNode.getChildNodes();
|
||||
for (int documentChildIndex = 0; documentChildIndex < exchangedDocumentChilds
|
||||
.getLength(); documentChildIndex++) {
|
||||
for (int documentChildIndex = 0; documentChildIndex < exchangedDocumentChilds.getLength(); documentChildIndex++) {
|
||||
Node item = exchangedDocumentChilds.item(documentChildIndex);
|
||||
if ((item.getLocalName() != null) && (item.getLocalName().equals("ID"))) {
|
||||
number = item.getTextContent();
|
||||
number = XMLTools.trimOrNull(item);
|
||||
}
|
||||
if ((item.getLocalName() != null) && (item.getLocalName().equals("TypeCode"))) {
|
||||
typeCode = XMLTools.trimOrNull(item);
|
||||
}
|
||||
if ((item.getLocalName() != null) && (item.getLocalName().equals("IssueDateTime"))) {
|
||||
NodeList issueDateTimeChilds = item.getChildNodes();
|
||||
for (int issueDateChildIndex = 0; issueDateChildIndex < issueDateTimeChilds
|
||||
.getLength(); issueDateChildIndex++) {
|
||||
for (int issueDateChildIndex = 0; issueDateChildIndex < issueDateTimeChilds.getLength(); issueDateChildIndex++) {
|
||||
if ((issueDateTimeChilds.item(issueDateChildIndex).getLocalName() != null)
|
||||
&& (issueDateTimeChilds.item(issueDateChildIndex).getLocalName()
|
||||
.equals("DateTimeString"))) {
|
||||
issueDate = new SimpleDateFormat("yyyyMMdd")
|
||||
.parse(issueDateTimeChilds.item(issueDateChildIndex).getTextContent());
|
||||
&& (issueDateTimeChilds.item(issueDateChildIndex).getLocalName().equals("DateTimeString"))) {
|
||||
issueDate = new SimpleDateFormat("yyyyMMdd").parse(XMLTools.trimOrNull(issueDateTimeChilds.item(issueDateChildIndex)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,51 +358,50 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
|
||||
if (rootNode.equals("Invoice")) {
|
||||
// UBL...
|
||||
number = extractString("//*[local-name()=\"Invoice\"]/*[local-name()=\"ID\"]").trim();
|
||||
issueDate = new SimpleDateFormat("yyyy-MM-dd")
|
||||
.parse(extractString("//*[local-name()=\"Invoice\"]/*[local-name()=\"IssueDate\"]").trim());
|
||||
issueDate = new SimpleDateFormat("yyyy-MM-dd").parse(extractString("//*[local-name()=\"Invoice\"]/*[local-name()=\"IssueDate\"]").trim());
|
||||
String dueDt = extractString("//*[local-name()=\"Invoice\"]/*[local-name()=\"DueDate\"]").trim();
|
||||
if (dueDt.length() > 0) {
|
||||
dueDate = new SimpleDateFormat("yyyy-MM-dd")
|
||||
.parse(dueDt);
|
||||
dueDate = new SimpleDateFormat("yyyy-MM-dd").parse(dueDt);
|
||||
}
|
||||
String deliveryDt = extractString("//*[local-name()=\"Delivery\"]/*[local-name()=\"ActualDeliveryDate\"]").trim();
|
||||
if (deliveryDt.length() > 0) {
|
||||
deliveryDate = new SimpleDateFormat("yyyy-MM-dd")
|
||||
.parse(deliveryDt);
|
||||
deliveryDate = new SimpleDateFormat("yyyy-MM-dd").parse(deliveryDt);
|
||||
}
|
||||
}
|
||||
xpr = xpath.compile("//*[local-name()=\"ApplicableHeaderTradeDelivery\"]");
|
||||
NodeList headerTradeDeliveryNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
|
||||
for (int i = 0; i < headerTradeDeliveryNodes.getLength(); i++) {
|
||||
// nodes.item(i).getTextContent())) {
|
||||
Node headerTradeDeliveryNode = headerTradeDeliveryNodes.item(i);
|
||||
NodeList headerTradeDeliveryChilds = headerTradeDeliveryNode.getChildNodes();
|
||||
for (int deliveryChildIndex = 0; deliveryChildIndex < headerTradeDeliveryChilds
|
||||
.getLength(); deliveryChildIndex++) {
|
||||
if ((headerTradeDeliveryChilds.item(deliveryChildIndex).getLocalName() != null)
|
||||
&& (headerTradeDeliveryChilds.item(deliveryChildIndex).getLocalName()
|
||||
.equals("ActualDeliverySupplyChainEvent"))) {
|
||||
for (int deliveryChildIndex = 0; deliveryChildIndex < headerTradeDeliveryChilds.getLength(); deliveryChildIndex++) {
|
||||
if (headerTradeDeliveryChilds.item(deliveryChildIndex).getLocalName() != null) {
|
||||
if (headerTradeDeliveryChilds.item(deliveryChildIndex).getLocalName().equals("ActualDeliverySupplyChainEvent")) {
|
||||
NodeList actualDeliveryChilds = headerTradeDeliveryChilds.item(deliveryChildIndex).getChildNodes();
|
||||
for (int actualDeliveryChildIndex = 0; actualDeliveryChildIndex < actualDeliveryChilds
|
||||
.getLength(); actualDeliveryChildIndex++) {
|
||||
for (int actualDeliveryChildIndex = 0; actualDeliveryChildIndex < actualDeliveryChilds.getLength(); actualDeliveryChildIndex++) {
|
||||
if ((actualDeliveryChilds.item(actualDeliveryChildIndex).getLocalName() != null)
|
||||
&& (actualDeliveryChilds.item(actualDeliveryChildIndex).getLocalName()
|
||||
.equals("OccurrenceDateTime"))) {
|
||||
NodeList occurenceChilds = actualDeliveryChilds.item(actualDeliveryChildIndex)
|
||||
.getChildNodes();
|
||||
for (int occurenceChildIndex = 0; occurenceChildIndex < occurenceChilds
|
||||
.getLength(); occurenceChildIndex++) {
|
||||
&& (actualDeliveryChilds.item(actualDeliveryChildIndex).getLocalName().equals("OccurrenceDateTime"))) {
|
||||
NodeList occurenceChilds = actualDeliveryChilds.item(actualDeliveryChildIndex).getChildNodes();
|
||||
for (int occurenceChildIndex = 0; occurenceChildIndex < occurenceChilds.getLength(); occurenceChildIndex++) {
|
||||
if ((occurenceChilds.item(occurenceChildIndex).getLocalName() != null)
|
||||
&& (occurenceChilds.item(occurenceChildIndex).getLocalName()
|
||||
.equals("DateTimeString"))) {
|
||||
deliveryDate = new SimpleDateFormat("yyyyMMdd")
|
||||
.parse(occurenceChilds.item(occurenceChildIndex).getTextContent());
|
||||
&& (occurenceChilds.item(occurenceChildIndex).getLocalName().equals("DateTimeString"))) {
|
||||
deliveryDate = new SimpleDateFormat("yyyyMMdd").parse(XMLTools.trimOrNull(occurenceChilds.item(occurenceChildIndex)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (headerTradeDeliveryChilds.item(deliveryChildIndex).getLocalName().equals("DespatchAdviceReferencedDocument")) {
|
||||
NodeList despatchAdviceChilds = headerTradeDeliveryChilds.item(deliveryChildIndex).getChildNodes();
|
||||
for (int despatchAdviceChildIndex = 0; despatchAdviceChildIndex < despatchAdviceChilds.getLength(); despatchAdviceChildIndex++) {
|
||||
if (despatchAdviceChilds.item(despatchAdviceChildIndex).getLocalName() != null
|
||||
&& despatchAdviceChilds.item(despatchAdviceChildIndex).getLocalName().equals("IssuerAssignedID")) {
|
||||
despatchAdviceReferencedDocument = XMLTools.trimOrNull(despatchAdviceChilds.item(despatchAdviceChildIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,34 +410,28 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
|
||||
String buyerOrderIssuerAssignedID = null;
|
||||
String sellerOrderIssuerAssignedID = null;
|
||||
for (int i = 0; i < headerTradeAgreementNodes.getLength(); i++) {
|
||||
// nodes.item(i).getTextContent())) {
|
||||
// XMLTools.trimOrNull(nodes.item(i)))) {
|
||||
Node headerTradeAgreementNode = headerTradeAgreementNodes.item(i);
|
||||
NodeList headerTradeAgreementChilds = headerTradeAgreementNode.getChildNodes();
|
||||
for (int agreementChildIndex = 0; agreementChildIndex < headerTradeAgreementChilds
|
||||
.getLength(); agreementChildIndex++) {
|
||||
if ((headerTradeAgreementChilds.item(agreementChildIndex).getLocalName() != null)
|
||||
&& (headerTradeAgreementChilds.item(agreementChildIndex).getLocalName()
|
||||
.equals("BuyerOrderReferencedDocument"))) {
|
||||
for (int agreementChildIndex = 0; agreementChildIndex < headerTradeAgreementChilds.getLength(); agreementChildIndex++) {
|
||||
if (headerTradeAgreementChilds.item(agreementChildIndex).getLocalName() != null) {
|
||||
if (headerTradeAgreementChilds.item(agreementChildIndex).getLocalName().equals("BuyerOrderReferencedDocument")) {
|
||||
NodeList buyerOrderChilds = headerTradeAgreementChilds.item(agreementChildIndex).getChildNodes();
|
||||
for (int buyerOrderChildIndex = 0; buyerOrderChildIndex < buyerOrderChilds
|
||||
.getLength(); buyerOrderChildIndex++) {
|
||||
for (int buyerOrderChildIndex = 0; buyerOrderChildIndex < buyerOrderChilds.getLength(); buyerOrderChildIndex++) {
|
||||
if ((buyerOrderChilds.item(buyerOrderChildIndex).getLocalName() != null)
|
||||
&& (buyerOrderChilds.item(buyerOrderChildIndex).getLocalName()
|
||||
.equals("IssuerAssignedID"))) {
|
||||
buyerOrderIssuerAssignedID = buyerOrderChilds.item(buyerOrderChildIndex).getTextContent();
|
||||
&& (buyerOrderChilds.item(buyerOrderChildIndex).getLocalName().equals("IssuerAssignedID"))) {
|
||||
buyerOrderIssuerAssignedID = XMLTools.trimOrNull(buyerOrderChilds.item(buyerOrderChildIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((headerTradeAgreementChilds.item(agreementChildIndex).getLocalName() != null)
|
||||
&& (headerTradeAgreementChilds.item(agreementChildIndex).getLocalName()
|
||||
.equals("SellerOrderReferencedDocument"))) {
|
||||
|
||||
if (headerTradeAgreementChilds.item(agreementChildIndex).getLocalName().equals("SellerOrderReferencedDocument")) {
|
||||
NodeList sellerOrderChilds = headerTradeAgreementChilds.item(agreementChildIndex).getChildNodes();
|
||||
for (int sellerOrderChildIndex = 0; sellerOrderChildIndex < sellerOrderChilds
|
||||
.getLength(); sellerOrderChildIndex++) {
|
||||
for (int sellerOrderChildIndex = 0; sellerOrderChildIndex < sellerOrderChilds.getLength(); sellerOrderChildIndex++) {
|
||||
if ((sellerOrderChilds.item(sellerOrderChildIndex).getLocalName() != null)
|
||||
&& (sellerOrderChilds.item(sellerOrderChildIndex).getLocalName()
|
||||
.equals("IssuerAssignedID"))) {
|
||||
sellerOrderIssuerAssignedID = sellerOrderChilds.item(sellerOrderChildIndex).getTextContent();
|
||||
&& (sellerOrderChilds.item(sellerOrderChildIndex).getLocalName().equals("IssuerAssignedID"))) {
|
||||
sellerOrderIssuerAssignedID = XMLTools.trimOrNull(sellerOrderChilds.item(sellerOrderChildIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -201,104 +439,139 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
|
||||
|
||||
}
|
||||
|
||||
String currency = extractString("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]/*[local-name()=\"InvoiceCurrencyCode\"]|*[local-name()=\"DocumentCurrencyCode\"]");
|
||||
zpp.setCurrency(currency);
|
||||
|
||||
xpr = xpath.compile("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]|//*[local-name()=\"ApplicableSupplyChainTradeSettlement\"]");
|
||||
NodeList headerTradeSettlementNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
List<BankDetails> bankDetails = new ArrayList<>();
|
||||
String directDebitMandateID = null;
|
||||
String IBAN = null, BIC = null;
|
||||
|
||||
for (int i = 0; i < headerTradeSettlementNodes.getLength(); i++) {
|
||||
// nodes.item(i).getTextContent())) {
|
||||
// XMLTools.trimOrNull(nodes.item(i)))) {
|
||||
Node headerTradeSettlementNode = headerTradeSettlementNodes.item(i);
|
||||
|
||||
NodeList headerTradeSettlementChilds = headerTradeSettlementNode.getChildNodes();
|
||||
for (int settlementChildIndex = 0; settlementChildIndex < headerTradeSettlementChilds
|
||||
.getLength(); settlementChildIndex++) {
|
||||
for (int settlementChildIndex = 0; settlementChildIndex < headerTradeSettlementChilds.getLength(); settlementChildIndex++) {
|
||||
if ((headerTradeSettlementChilds.item(settlementChildIndex).getLocalName() != null)
|
||||
&& (headerTradeSettlementChilds.item(settlementChildIndex).getLocalName()
|
||||
.equals("SpecifiedTradePaymentTerms"))) {
|
||||
&& (headerTradeSettlementChilds.item(settlementChildIndex).getLocalName().equals("SpecifiedTradePaymentTerms"))) {
|
||||
NodeList paymentTermChilds = headerTradeSettlementChilds.item(settlementChildIndex).getChildNodes();
|
||||
for (int paymentTermChildIndex = 0; paymentTermChildIndex < paymentTermChilds
|
||||
.getLength(); paymentTermChildIndex++) {
|
||||
if ((paymentTermChilds.item(paymentTermChildIndex).getLocalName() != null) && (paymentTermChilds
|
||||
.item(paymentTermChildIndex).getLocalName().equals("DueDateDateTime"))) {
|
||||
for (int paymentTermChildIndex = 0; paymentTermChildIndex < paymentTermChilds.getLength(); paymentTermChildIndex++) {
|
||||
if ((paymentTermChilds.item(paymentTermChildIndex).getLocalName() != null) && (paymentTermChilds.item(paymentTermChildIndex).getLocalName().equals("DueDateDateTime"))) {
|
||||
NodeList dueDateChilds = paymentTermChilds.item(paymentTermChildIndex).getChildNodes();
|
||||
for (int dueDateChildIndex = 0; dueDateChildIndex < dueDateChilds
|
||||
.getLength(); dueDateChildIndex++) {
|
||||
if ((dueDateChilds.item(dueDateChildIndex).getLocalName() != null) && (dueDateChilds
|
||||
.item(dueDateChildIndex).getLocalName().equals("DateTimeString"))) {
|
||||
dueDate = new SimpleDateFormat("yyyyMMdd")
|
||||
.parse(dueDateChilds.item(dueDateChildIndex).getTextContent());
|
||||
for (int dueDateChildIndex = 0; dueDateChildIndex < dueDateChilds.getLength(); dueDateChildIndex++) {
|
||||
if ((dueDateChilds.item(dueDateChildIndex).getLocalName() != null) && (dueDateChilds.item(dueDateChildIndex).getLocalName().equals("DateTimeString"))) {
|
||||
dueDate = new SimpleDateFormat("yyyyMMdd").parse(XMLTools.trimOrNull(dueDateChilds.item(dueDateChildIndex)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((paymentTermChilds.item(paymentTermChildIndex).getLocalName() != null) && (paymentTermChilds.item(paymentTermChildIndex).getLocalName().equals("DirectDebitMandateID"))) {
|
||||
directDebitMandateID = paymentTermChilds.item(paymentTermChildIndex).getTextContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((headerTradeSettlementChilds.item(settlementChildIndex).getLocalName() != null)
|
||||
&& (headerTradeSettlementChilds.item(settlementChildIndex).getLocalName()
|
||||
.equals("SpecifiedTradeSettlementPaymentMeans"))) {
|
||||
&& (headerTradeSettlementChilds.item(settlementChildIndex).getLocalName().equals("SpecifiedTradeSettlementPaymentMeans"))) {
|
||||
NodeList paymentMeansChilds = headerTradeSettlementChilds.item(settlementChildIndex).getChildNodes();
|
||||
for (int paymentMeansChildIndex = 0; paymentMeansChildIndex < paymentMeansChilds
|
||||
.getLength(); paymentMeansChildIndex++) {
|
||||
if ((paymentMeansChilds.item(paymentMeansChildIndex).getLocalName() != null) && (paymentMeansChilds
|
||||
.item(paymentMeansChildIndex).getLocalName().equals("PayeePartyCreditorFinancialAccount"))) {
|
||||
IBAN = null;
|
||||
BIC = null;
|
||||
for (int paymentMeansChildIndex = 0; paymentMeansChildIndex < paymentMeansChilds.getLength(); paymentMeansChildIndex++) {
|
||||
|
||||
if ((paymentMeansChilds.item(paymentMeansChildIndex).getLocalName() != null) && (paymentMeansChilds.item(paymentMeansChildIndex).getLocalName().equals("PayeePartyCreditorFinancialAccount") || paymentMeansChilds.item(paymentMeansChildIndex).getLocalName().equals("PayerPartyDebtorFinancialAccount"))) {
|
||||
NodeList accountChilds = paymentMeansChilds.item(paymentMeansChildIndex).getChildNodes();
|
||||
for (int accountChildIndex = 0; accountChildIndex < accountChilds
|
||||
.getLength(); accountChildIndex++) {
|
||||
if ((accountChilds.item(accountChildIndex).getLocalName() != null) && (accountChilds
|
||||
.item(accountChildIndex).getLocalName().equals("IBANID"))) {//CII
|
||||
IBAN = accountChilds.item(accountChildIndex).getTextContent();
|
||||
for (int accountChildIndex = 0; accountChildIndex < accountChilds.getLength(); accountChildIndex++) {
|
||||
if ((accountChilds.item(accountChildIndex).getLocalName() != null) && (accountChilds.item(accountChildIndex).getLocalName().equals("IBANID"))) {//CII
|
||||
IBAN = XMLTools.trimOrNull(accountChilds.item(accountChildIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((paymentMeansChilds.item(paymentMeansChildIndex).getLocalName() != null) && (paymentMeansChilds
|
||||
.item(paymentMeansChildIndex).getLocalName().equals("PayeePartyCreditorFinancialInstitution"))) {
|
||||
if ((paymentMeansChilds.item(paymentMeansChildIndex).getLocalName() != null) && (paymentMeansChilds.item(paymentMeansChildIndex).getLocalName().equals("PayeeSpecifiedCreditorFinancialInstitution"))) {
|
||||
NodeList accountChilds = paymentMeansChilds.item(paymentMeansChildIndex).getChildNodes();
|
||||
for (int accountChildIndex = 0; accountChildIndex < accountChilds
|
||||
.getLength(); accountChildIndex++) {
|
||||
if ((accountChilds.item(accountChildIndex).getLocalName() != null) && (accountChilds
|
||||
.item(accountChildIndex).getLocalName().equals("BICID"))) {//CII
|
||||
BIC = accountChilds.item(accountChildIndex).getTextContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int accountChildIndex = 0; accountChildIndex < accountChilds.getLength(); accountChildIndex++) {
|
||||
if ((accountChilds.item(accountChildIndex).getLocalName() != null) && (accountChilds.item(accountChildIndex).getLocalName().equals("BICID"))) {//CII
|
||||
BIC = XMLTools.trimOrNull(accountChilds.item(accountChildIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
xpr = xpath.compile("//*[local-name()=\"PaymentMeans\"]"); //UBL only
|
||||
NodeList paymentMeansNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
|
||||
for (int i = 0; i < paymentMeansNodes.getLength(); i++) {
|
||||
// nodes.item(i).getTextContent())) {
|
||||
Node paymentMeansNode = paymentMeansNodes.item(i);
|
||||
NodeList paymentMeansChilds = paymentMeansNode.getChildNodes();
|
||||
for (int meansChildIndex = 0; meansChildIndex < paymentMeansChilds
|
||||
.getLength(); meansChildIndex++) {
|
||||
if ((paymentMeansChilds.item(meansChildIndex).getLocalName() != null)
|
||||
&& (paymentMeansChilds.item(meansChildIndex).getLocalName()
|
||||
.equals("PayeeFinancialAccount"))) {
|
||||
NodeList paymentTermChilds = paymentMeansChilds.item(meansChildIndex).getChildNodes();
|
||||
for (int paymentTermChildIndex = 0; paymentTermChildIndex < paymentTermChilds
|
||||
.getLength(); paymentTermChildIndex++) {
|
||||
if ((paymentTermChilds.item(paymentTermChildIndex).getLocalName() != null) && (paymentTermChilds
|
||||
.item(paymentTermChildIndex).getLocalName().equals("ID"))) {
|
||||
IBAN = paymentTermChilds.item(paymentTermChildIndex).getTextContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zpp.setDueDate(dueDate).setDeliveryDate(deliveryDate).setIssueDate(issueDate)
|
||||
.setSender(new TradeParty(SellerNodes)).setRecipient(new TradeParty(BuyerNodes)).setNumber(number);
|
||||
if (IBAN != null) {
|
||||
BankDetails bd = new BankDetails(IBAN);
|
||||
if (BIC != null) {
|
||||
bd.setBIC(BIC);
|
||||
}
|
||||
zpp.getSender().addBankDetails(bd);
|
||||
bankDetails.add(bd);
|
||||
}
|
||||
}
|
||||
if ((headerTradeSettlementChilds.item(settlementChildIndex).getLocalName() != null)
|
||||
&& (headerTradeSettlementChilds.item(settlementChildIndex).getLocalName().equals("BillingSpecifiedPeriod"))) {
|
||||
NodeList periodChilds = headerTradeSettlementChilds.item(settlementChildIndex).getChildNodes();
|
||||
for (int periodChildIndex = 0; periodChildIndex < periodChilds.getLength(); periodChildIndex++) {
|
||||
if ((periodChilds.item(periodChildIndex).getLocalName() != null) && (periodChilds.item(periodChildIndex).getLocalName().equals("StartDateTime"))) {
|
||||
|
||||
NodeList startPeriodChilds = periodChilds.item(periodChildIndex).getChildNodes();
|
||||
for (int startPeriodIndex = 0; startPeriodIndex < startPeriodChilds.getLength(); startPeriodIndex++) {
|
||||
if ((startPeriodChilds.item(startPeriodIndex).getLocalName() != null) && (startPeriodChilds.item(startPeriodIndex).getLocalName().equals("DateTimeString"))) {//CII
|
||||
deliveryPeriodStart = XMLTools.trimOrNull(startPeriodChilds.item(startPeriodIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((periodChilds.item(periodChildIndex).getLocalName() != null) && (periodChilds.item(periodChildIndex).getLocalName().equals("EndDateTime"))) {
|
||||
NodeList endPeriodChilds = periodChilds.item(periodChildIndex).getChildNodes();
|
||||
for (int endPeriodIndex = 0; endPeriodIndex < endPeriodChilds.getLength(); endPeriodIndex++) {
|
||||
if ((endPeriodChilds.item(endPeriodIndex).getLocalName() != null) && (endPeriodChilds.item(endPeriodIndex).getLocalName().equals("DateTimeString"))) {//CII
|
||||
deliveryPeriodEnd = XMLTools.trimOrNull(endPeriodChilds.item(endPeriodIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((deliveryPeriodStart != null) && (deliveryPeriodEnd != null)) {
|
||||
zpp.setDetailedDeliveryPeriod(XMLTools.tryDate(deliveryPeriodStart), XMLTools.tryDate(deliveryPeriodEnd));
|
||||
} else if (deliveryPeriodStart != null) {
|
||||
zpp.setDeliveryDate(XMLTools.tryDate(deliveryPeriodStart));
|
||||
}
|
||||
|
||||
xpr = xpath.compile("//*[local-name()=\"PaymentMeans\"]"); //UBL only
|
||||
NodeList paymentMeansNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
|
||||
for (int i = 0; i < paymentMeansNodes.getLength(); i++) {
|
||||
// XMLTools.trimOrNull(nodes.item(i)))) {
|
||||
Node paymentMeansNode = paymentMeansNodes.item(i);
|
||||
NodeList paymentMeansChilds = paymentMeansNode.getChildNodes();
|
||||
for (int meansChildIndex = 0; meansChildIndex < paymentMeansChilds.getLength(); meansChildIndex++) {
|
||||
if ((paymentMeansChilds.item(meansChildIndex).getLocalName() != null)
|
||||
&& (paymentMeansChilds.item(meansChildIndex).getLocalName().equals("PayeeFinancialAccount"))) {
|
||||
NodeList paymentTermChilds = paymentMeansChilds.item(meansChildIndex).getChildNodes();
|
||||
for (int paymentTermChildIndex = 0; paymentTermChildIndex < paymentTermChilds.getLength(); paymentTermChildIndex++) {
|
||||
if ((paymentTermChilds.item(paymentTermChildIndex).getLocalName() != null) && (paymentTermChilds.item(paymentTermChildIndex).getLocalName().equals("ID"))) {
|
||||
IBAN = XMLTools.trimOrNull(paymentTermChilds.item(paymentTermChildIndex));
|
||||
if (IBAN != null) {
|
||||
BankDetails bd = new BankDetails(IBAN);
|
||||
bankDetails.add(bd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zpp.setDueDate(dueDate).setDeliveryDate(deliveryDate).setIssueDate(issueDate).setSender(new TradeParty(SellerNodes)).setRecipient(new TradeParty(BuyerNodes)).setNumber(number).setDocumentCode(typeCode);
|
||||
|
||||
if ((directDebitMandateID != null) && (IBAN != null)) {
|
||||
DirectDebit d = new DirectDebit(IBAN, directDebitMandateID);
|
||||
zpp.getSender().addDebitDetails(d);
|
||||
}
|
||||
|
||||
bankDetails.forEach(bankDetail -> zpp.getSender().addBankDetails(bankDetail));
|
||||
|
||||
if (payeeNodes.getLength() > 0) {
|
||||
zpp.setPayee(new TradeParty(payeeNodes));
|
||||
}
|
||||
|
||||
if (buyerOrderIssuerAssignedID != null) {
|
||||
@@ -307,15 +580,17 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
|
||||
if (sellerOrderIssuerAssignedID != null) {
|
||||
zpp.setSellerOrderReferencedDocumentID(sellerOrderIssuerAssignedID);
|
||||
}
|
||||
if (despatchAdviceReferencedDocument != null) {
|
||||
zpp.setDespatchAdviceReferencedDocumentID(despatchAdviceReferencedDocument);
|
||||
}
|
||||
|
||||
//.addItem(new Item(new Product("Testprodukt","","C62",BigDecimal.ZERO),amount,new BigDecimal(1.0)))
|
||||
zpp.setOwnOrganisationName(extractString("//*[local-name()=\"SellerTradeParty\"]/*[local-name()=\"Name\"]|//*[local-name()=\"AccountingSupplierParty\"]/*[local-name()=\"Party\"]/*[local-name()=\"PartyName\"]").trim());
|
||||
|
||||
xpr = xpath.compile("//*[local-name()=\"BuyerReference\"]");
|
||||
String buyerReference = null;
|
||||
totalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
if (totalNodes.getLength() > 0) {
|
||||
buyerReference = totalNodes.item(0).getTextContent();
|
||||
prepaidNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
if (prepaidNodes.getLength() > 0) {
|
||||
buyerReference = XMLTools.trimOrNull(prepaidNodes.item(0));
|
||||
}
|
||||
if (buyerReference != null) {
|
||||
zpp.setReferenceNumber(buyerReference);
|
||||
@@ -333,6 +608,16 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
|
||||
|
||||
}
|
||||
|
||||
// now handling base64 encoded attachments AttachmentBinaryObject=CII, EmbeddedDocumentBinaryObject=UBL
|
||||
xpr = xpath.compile("//*[local-name()=\"AttachmentBinaryObject\"]|//*[local-name()=\"EmbeddedDocumentBinaryObject\"]");
|
||||
NodeList attachmentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
for (int i = 0; i < attachmentNodes.getLength(); i++) {
|
||||
FileAttachment fa = new FileAttachment(attachmentNodes.item(i).getAttributes().getNamedItem("filename").getNodeValue(), attachmentNodes.item(i).getAttributes().getNamedItem("mimeCode").getNodeValue(), "Data", Base64.getDecoder().decode(XMLTools.trimOrNull(attachmentNodes.item(i))));
|
||||
fileAttachments.add(fa);
|
||||
// filename = "Aufmass.png" mimeCode = "image/png"
|
||||
//EmbeddedDocumentBinaryObject cbc:EmbeddedDocumentBinaryObject mimeCode="image/png" filename="Aufmass.png"
|
||||
}
|
||||
|
||||
// item level charges+allowances are not yet handled but a lower item price will
|
||||
// be read,
|
||||
// so the invoice remains arithmetically correct
|
||||
@@ -344,6 +629,7 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
|
||||
boolean isCharge = true;
|
||||
String chargeAmount = null;
|
||||
String reason = null;
|
||||
String reasonCode = null;
|
||||
String taxPercent = null;
|
||||
for (int chargeChildIndex = 0; chargeChildIndex < chargeNodeChilds.getLength(); chargeChildIndex++) {
|
||||
String chargeChildName = chargeNodeChilds.item(chargeChildIndex).getLocalName();
|
||||
@@ -351,26 +637,24 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
|
||||
|
||||
if (chargeChildName.equals("ChargeIndicator")) {
|
||||
NodeList indicatorChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes();
|
||||
for (int indicatorChildIndex = 0; indicatorChildIndex < indicatorChilds
|
||||
.getLength(); indicatorChildIndex++) {
|
||||
for (int indicatorChildIndex = 0; indicatorChildIndex < indicatorChilds.getLength(); indicatorChildIndex++) {
|
||||
if ((indicatorChilds.item(indicatorChildIndex).getLocalName() != null)
|
||||
&& (indicatorChilds.item(indicatorChildIndex).getLocalName()
|
||||
.equals("Indicator"))) {
|
||||
isCharge = indicatorChilds.item(indicatorChildIndex).getTextContent()
|
||||
.equalsIgnoreCase("true");
|
||||
&& (indicatorChilds.item(indicatorChildIndex).getLocalName().equals("Indicator"))) {
|
||||
isCharge = XMLTools.trimOrNull(indicatorChilds.item(indicatorChildIndex)).equalsIgnoreCase("true");
|
||||
}
|
||||
}
|
||||
} else if (chargeChildName.equals("ActualAmount")) {
|
||||
chargeAmount = chargeNodeChilds.item(chargeChildIndex).getTextContent();
|
||||
chargeAmount = XMLTools.trimOrNull(chargeNodeChilds.item(chargeChildIndex));
|
||||
} else if (chargeChildName.equals("Reason")) {
|
||||
reason = chargeNodeChilds.item(chargeChildIndex).getTextContent();
|
||||
reason = XMLTools.trimOrNull(chargeNodeChilds.item(chargeChildIndex));
|
||||
} else if (chargeChildName.equals("ReasonCode")) {
|
||||
reasonCode = XMLTools.trimOrNull(chargeNodeChilds.item(chargeChildIndex));
|
||||
} else if (chargeChildName.equals("CategoryTradeTax")) {
|
||||
NodeList taxChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes();
|
||||
for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) {
|
||||
String taxItemName = taxChilds.item(taxChildIndex).getLocalName();
|
||||
if ((taxItemName != null) && (taxItemName.equals("RateApplicablePercent")
|
||||
|| taxItemName.equals("ApplicablePercent"))) {
|
||||
taxPercent = taxChilds.item(taxChildIndex).getTextContent();
|
||||
if ((taxItemName != null) && (taxItemName.equals("RateApplicablePercent") || taxItemName.equals("ApplicablePercent"))) {
|
||||
taxPercent = XMLTools.trimOrNull(taxChilds.item(taxChildIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -382,16 +666,21 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
|
||||
if (reason != null) {
|
||||
c.setReason(reason);
|
||||
}
|
||||
if (reasonCode != null) {
|
||||
c.setReasonCode(reasonCode);
|
||||
}
|
||||
if (taxPercent != null) {
|
||||
c.setTaxPercent(new BigDecimal(taxPercent));
|
||||
}
|
||||
|
||||
zpp.addCharge(c);
|
||||
} else {
|
||||
Allowance a = new Allowance(new BigDecimal(chargeAmount));
|
||||
if (reason != null) {
|
||||
a.setReason(reason);
|
||||
}
|
||||
if (reasonCode != null) {
|
||||
a.setReasonCode(reasonCode);
|
||||
}
|
||||
if (taxPercent != null) {
|
||||
a.setTaxPercent(new BigDecimal(taxPercent));
|
||||
}
|
||||
@@ -401,7 +690,8 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
|
||||
}
|
||||
|
||||
TransactionCalculator tc = new TransactionCalculator(zpp);
|
||||
String expectedStringTotalGross = tc.getGrandTotal().toPlainString();
|
||||
String expectedStringTotalGross = tc.getGrandTotal()
|
||||
.subtract(Objects.requireNonNullElse(zpp.getTotalPrepaidAmount(), BigDecimal.ZERO)).toPlainString();
|
||||
EStandard whichType;
|
||||
try {
|
||||
whichType = getStandard();
|
||||
@@ -414,13 +704,95 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
|
||||
&& ((!expectedStringTotalGross.equals(XMLTools.nDigitFormat(expectedGrandTotal, 2)))
|
||||
&& (!ignoreCalculationErrors))) {
|
||||
throw new ParseException(
|
||||
"Could not reproduce the invoice, this could mean that it could not be read properly", 0);
|
||||
"Could not reproduce the invoice, this could mean that it could not be read properly exp "+expectedStringTotalGross+" is "+XMLTools.nDigitFormat(expectedGrandTotal, 2), 0);
|
||||
}
|
||||
}
|
||||
return zpp;
|
||||
|
||||
}
|
||||
|
||||
protected Document getDocument() {
|
||||
return document;
|
||||
}
|
||||
|
||||
protected String extractString(String xpathStr) {
|
||||
if (!containsMeta) {
|
||||
throw new ZUGFeRDExportException("No suitable data/ZUGFeRD file could be found.");
|
||||
}
|
||||
final String result;
|
||||
try {
|
||||
final Document document = getDocument();
|
||||
final XPathFactory xpathFact = XPathFactory.newInstance();
|
||||
final XPath xpath = xpathFact.newXPath();
|
||||
result = xpath.evaluate(xpathStr, document);
|
||||
} catch (final XPathExpressionException e) {
|
||||
LOGGER.error("Failed to evaluate XPath", e);
|
||||
throw new ZUGFeRDExportException(e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public EStandard getStandard() throws Exception {
|
||||
if (!containsMeta) {
|
||||
throw new Exception("Not yet parsed");
|
||||
}
|
||||
final String head = getUTF8();
|
||||
String rootNode = extractString("local-name(/*)");
|
||||
if (rootNode.equals("CrossIndustryDocument")) {
|
||||
return EStandard.zugferd;
|
||||
} else if (rootNode.equals("Invoice")) {
|
||||
return EStandard.ubl;
|
||||
} else if (rootNode.equals("CreditNote")) {
|
||||
return EStandard.ubl;
|
||||
} else if (rootNode.equals("CrossIndustryInvoice")) {
|
||||
return EStandard.facturx;
|
||||
} else if (rootNode.equals("SCRDMCCBDACIDAMessageStructure")) {
|
||||
return EStandard.despatchadvice;
|
||||
} else if (head.contains("<rsm:SCRDMCCBDACIOMessageStructure")) {
|
||||
return EStandard.orderx;
|
||||
}
|
||||
|
||||
throw new Exception("ZUGFeRD version could not be determined");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return return UTF8 XML (without BOM) of the invoice
|
||||
*/
|
||||
public String getUTF8() {
|
||||
if (rawXML == null) {
|
||||
return null;
|
||||
}
|
||||
if (rawXML.length < 3) {
|
||||
return new String(rawXML);
|
||||
}
|
||||
|
||||
|
||||
final byte[] bomlessData;
|
||||
|
||||
if ((rawXML[0] == (byte) 0xEF)
|
||||
&& (rawXML[1] == (byte) 0xBB)
|
||||
&& (rawXML[2] == (byte) 0xBF)) {
|
||||
// I don't like BOMs, lets remove it
|
||||
bomlessData = new byte[rawXML.length - 3];
|
||||
System.arraycopy(rawXML, 3, bomlessData, 0,
|
||||
rawXML.length - 3);
|
||||
} else {
|
||||
bomlessData = rawXML;
|
||||
}
|
||||
|
||||
return new String(bomlessData);
|
||||
}
|
||||
|
||||
/***
|
||||
*
|
||||
* @return the file attachments embedded in XML (using base64) decoded as byte array,
|
||||
* for PDF embedded files in FX use getFileAttachmentsPDF()
|
||||
*/
|
||||
public List<FileAttachment> getFileAttachmentsXML() {
|
||||
return fileAttachments;
|
||||
}
|
||||
|
||||
/***
|
||||
* This will parse a XML into a invoice object
|
||||
*
|
||||
@@ -435,18 +807,18 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
|
||||
|
||||
}
|
||||
|
||||
/***
|
||||
* have the item prices be determined from the line total.
|
||||
* That's a workaround for some invoices which just put 0 as item price
|
||||
*/
|
||||
public void doRecalculateItemPricesFromLineTotals() {
|
||||
recalcPrice = true;
|
||||
}
|
||||
|
||||
/***
|
||||
* do not raise ParseExceptions even if the reproduced invoice total does not match the given value
|
||||
* sets the XML for the importer to parse
|
||||
* @param XML the UBL or CII
|
||||
*/
|
||||
public void doIgnoreCalculationErrors() {
|
||||
ignoreCalculationErrors = true;
|
||||
public void fromXML(String XML) {
|
||||
try {
|
||||
containsMeta = true;
|
||||
setRawXML(XML.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (IOException e) {
|
||||
LOGGER.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,28 +20,57 @@
|
||||
*/
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import org.apache.fop.apps.*;
|
||||
import org.apache.fop.configuration.Configuration;
|
||||
import org.apache.fop.configuration.ConfigurationException;
|
||||
import org.apache.fop.configuration.DefaultConfigurationBuilder;
|
||||
import org.apache.xmlgraphics.util.MimeConstants;
|
||||
import org.mustangproject.CII.CIIToUBL;
|
||||
import org.mustangproject.ClasspathResolverURIAdapter;
|
||||
import org.xml.sax.SAXException;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import javax.xml.transform.*;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.Result;
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.Templates;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerConfigurationException;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.URIResolver;
|
||||
import javax.xml.transform.sax.SAXResult;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.apache.fop.apps.FOPException;
|
||||
import org.apache.fop.apps.FOUserAgent;
|
||||
import org.apache.fop.apps.Fop;
|
||||
import org.apache.fop.apps.FopFactory;
|
||||
import org.apache.fop.apps.FopFactoryBuilder;
|
||||
import org.apache.fop.apps.io.ResourceResolverFactory;
|
||||
import org.apache.fop.configuration.Configuration;
|
||||
import org.apache.fop.configuration.ConfigurationException;
|
||||
import org.apache.fop.configuration.DefaultConfigurationBuilder;
|
||||
import org.apache.xmlgraphics.util.MimeConstants;
|
||||
import org.mustangproject.ClasspathResolverURIAdapter;
|
||||
import org.mustangproject.EStandard;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.helger.commons.io.stream.StreamHelper;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
public class ZUGFeRDVisualizer {
|
||||
|
||||
@@ -53,7 +82,7 @@ public class ZUGFeRDVisualizer {
|
||||
|
||||
static final ClassLoader CLASS_LOADER = ZUGFeRDVisualizer.class.getClassLoader();
|
||||
private static final String RESOURCE_PATH = "";
|
||||
private static final Logger LOG = Logger.getLogger(ZUGFeRDVisualizer.class.getName());
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRDVisualizer.class);
|
||||
// private static File createTempFileResult(final Transformer transformer, final
|
||||
// StreamSource toTransform,
|
||||
// final String suffix) throws TransformerException, IOException {
|
||||
@@ -68,6 +97,7 @@ public class ZUGFeRDVisualizer {
|
||||
private TransformerFactory mFactory = null;
|
||||
private Templates mXsltXRTemplate = null;
|
||||
private Templates mXsltUBLTemplate = null;
|
||||
private Templates mXsltCIOTemplate = null;
|
||||
private Templates mXsltHTMLTemplate = null;
|
||||
private Templates mXsltPDFTemplate = null;
|
||||
private Templates mXsltZF1HTMLTemplate = null;
|
||||
@@ -78,14 +108,46 @@ public class ZUGFeRDVisualizer {
|
||||
mFactory.setURIResolver(new ClasspathResourceURIResolver());
|
||||
}
|
||||
|
||||
/***
|
||||
* returns which standard is used, CII or UBL
|
||||
* @param fis inputstream (will be consumed)
|
||||
* @return (facturx = cii)
|
||||
*/
|
||||
public EStandard findOutStandardFromRootNode(InputStream fis) {
|
||||
|
||||
String zf1Signature = "CrossIndustryDocument";
|
||||
String zf2Signature = "CrossIndustryInvoice";
|
||||
String ublSignature = "Invoice";
|
||||
String ublCreditNoteSignature = "CreditNote";
|
||||
String cioSignature = "SCRDMCCBDACIOMessageStructure";
|
||||
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
dbf.setNamespaceAware(true);
|
||||
try {
|
||||
DocumentBuilder db = dbf.newDocumentBuilder();
|
||||
Document doc = db.parse(new InputSource(fis));
|
||||
Element root = doc.getDocumentElement();
|
||||
if (root.getLocalName().equals(zf1Signature)) {
|
||||
return EStandard.zugferd;
|
||||
} else if (root.getLocalName().equals(zf2Signature)) {
|
||||
return EStandard.facturx;
|
||||
} else if (root.getLocalName().equals(ublSignature)) {
|
||||
return EStandard.ubl;
|
||||
} else if (root.getLocalName().equals(ublCreditNoteSignature)) {
|
||||
return EStandard.ubl_creditnote;
|
||||
} else if (root.getLocalName().equals(cioSignature)) {
|
||||
return EStandard.orderx;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to recognize standard", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String visualize(String xmlFilename, Language lang)
|
||||
throws FileNotFoundException, TransformerException, UnsupportedEncodingException {
|
||||
throws FileNotFoundException, TransformerException, IOException, SAXException, ParserConfigurationException {
|
||||
|
||||
try {
|
||||
if (mXsltXRTemplate == null) {
|
||||
mXsltXRTemplate = mFactory.newTemplates(
|
||||
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/cii-xr.xsl")));
|
||||
}
|
||||
if (mXsltPDFTemplate == null) {
|
||||
mXsltPDFTemplate = mFactory.newTemplates(
|
||||
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/xr-pdf.xsl")));
|
||||
@@ -99,7 +161,7 @@ public class ZUGFeRDVisualizer {
|
||||
CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/ZUGFeRD_1p0_c1p0_s1p0.xslt")));
|
||||
}
|
||||
} catch (TransformerConfigurationException ex) {
|
||||
LOG.log(Level.SEVERE, null, ex);
|
||||
LOGGER.error("Failed to init XSLT templates", ex);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,35 +175,39 @@ public class ZUGFeRDVisualizer {
|
||||
try {
|
||||
fileContent = new String(Files.readAllBytes(Paths.get(xmlFilename)), StandardCharsets.UTF_8);
|
||||
} catch (IOException e2) {
|
||||
LOG.log(Level.SEVERE, null, e2);
|
||||
LOGGER.error("Failed to read file content", e2);
|
||||
}
|
||||
|
||||
ByteArrayOutputStream iaos = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
String zf1Signature = "rsm:CrossIndustryDocument";
|
||||
String zf2Signature = "rsm:CrossIndustryInvoice";
|
||||
String ublSignature = "ubl:Invoice";
|
||||
String ublCreditNoteSignature = "ubl:CreditNote";
|
||||
boolean doPostProcessing = false;
|
||||
if (fileContent.contains(zf1Signature)) {
|
||||
|
||||
|
||||
fis = new FileInputStream(xmlFilename); // fis wont reset() so re-read from beginning
|
||||
EStandard thestandard = findOutStandardFromRootNode(fis);
|
||||
fis = new FileInputStream(xmlFilename); // fis wont reset() so re-read from beginning
|
||||
|
||||
if (thestandard == EStandard.zugferd) {
|
||||
applyZF1XSLT(fis, baos);
|
||||
|
||||
} else if (fileContent.contains(zf2Signature)) {
|
||||
|
||||
} else if (thestandard == EStandard.facturx) {
|
||||
//zf2 or fx
|
||||
applyZF2XSLT(fis, iaos);
|
||||
doPostProcessing = true;
|
||||
} else if (fileContent.contains(ublSignature)) {
|
||||
} else if (thestandard == EStandard.ubl) {
|
||||
//zf2 or fx
|
||||
applyUBL2XSLT(fis, iaos);
|
||||
doPostProcessing = true;
|
||||
|
||||
} else if (fileContent.contains(ublCreditNoteSignature)) {
|
||||
} else if (thestandard == EStandard.ubl_creditnote) {
|
||||
//zf2 or fx
|
||||
applyUBLCreditNote2XSLT(fis, iaos);
|
||||
doPostProcessing = true;
|
||||
|
||||
} else if (thestandard == EStandard.orderx) {
|
||||
//zf2 or fx
|
||||
applyCIO2XSLT(fis, iaos);
|
||||
doPostProcessing = true;
|
||||
} else {
|
||||
throw new IllegalArgumentException("File does not look like CII or UBL");
|
||||
}
|
||||
if (doPostProcessing) {
|
||||
// take the copy of the stream and re-write it to an InputStream
|
||||
@@ -158,61 +224,52 @@ public class ZUGFeRDVisualizer {
|
||||
// ByteArrayOutputStream
|
||||
iaos.writeTo(out);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.SEVERE, null, e);
|
||||
LOGGER.error("Failed to write to stream", e);
|
||||
} finally {
|
||||
// close the PipedOutputStream here because we're done writing data
|
||||
// once this thread has completed its run
|
||||
if (out != null) {
|
||||
// close the PipedOutputStream cleanly
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
LOG.log(Level.SEVERE, null, e);
|
||||
}
|
||||
}
|
||||
StreamHelper.close(out);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
applyXSLTToHTML(in, baos);
|
||||
} catch (IOException e1) {
|
||||
LOG.log(Level.SEVERE, null, e1);
|
||||
LOGGER.error("Failed to create HTML", e1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return baos.toString("UTF-8");
|
||||
return baos.toString(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
protected String toFOP(String xmlFilename)
|
||||
throws FileNotFoundException, TransformerException, UnsupportedEncodingException {
|
||||
throws FileNotFoundException, TransformerException {
|
||||
|
||||
FileInputStream fis = new FileInputStream(xmlFilename);
|
||||
EStandard theStandard = findOutStandardFromRootNode(fis);
|
||||
fis = new FileInputStream(xmlFilename);//rewind :-(
|
||||
|
||||
try {
|
||||
if (mXsltXRTemplate == null) {
|
||||
mXsltXRTemplate = mFactory.newTemplates(
|
||||
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/cii-xr.xsl")));
|
||||
}
|
||||
if (mXsltPDFTemplate == null) {
|
||||
mXsltPDFTemplate = mFactory.newTemplates(
|
||||
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/xr-pdf.xsl")));
|
||||
}
|
||||
} catch (TransformerConfigurationException ex) {
|
||||
LOG.log(Level.SEVERE, null, ex);
|
||||
}
|
||||
|
||||
FileInputStream fis = new FileInputStream(xmlFilename);
|
||||
String fileContent = "";
|
||||
try {
|
||||
fileContent = new String(Files.readAllBytes(Paths.get(xmlFilename)), StandardCharsets.UTF_8);
|
||||
} catch (IOException e2) {
|
||||
LOG.log(Level.SEVERE, null, e2);
|
||||
LOGGER.error("Failed to init XSLT templates", ex);
|
||||
}
|
||||
|
||||
ByteArrayOutputStream iaos = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
//zf2 or fx
|
||||
if (theStandard == EStandard.facturx) {
|
||||
applyZF2XSLT(fis, iaos);
|
||||
} else if (theStandard == EStandard.ubl) {
|
||||
applyUBL2XSLT(fis, iaos);
|
||||
} else if (theStandard == EStandard.ubl_creditnote) {
|
||||
applyUBLCreditNote2XSLT(fis, iaos);
|
||||
}
|
||||
|
||||
|
||||
PipedInputStream in = new PipedInputStream();
|
||||
PipedOutputStream out;
|
||||
@@ -227,68 +284,38 @@ public class ZUGFeRDVisualizer {
|
||||
// ByteArrayOutputStream
|
||||
iaos.writeTo(out);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.SEVERE, null, e);
|
||||
LOGGER.error("Failed to write to stream", e);
|
||||
} finally {
|
||||
// close the PipedOutputStream here because we're done writing data
|
||||
// once this thread has completed its run
|
||||
if (out != null) {
|
||||
// close the PipedOutputStream cleanly
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
LOG.log(Level.SEVERE, null, e);
|
||||
}
|
||||
}
|
||||
StreamHelper.close(out);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
applyXSLTToPDF(in, baos);
|
||||
} catch (IOException e1) {
|
||||
LOG.log(Level.SEVERE, null, e1);
|
||||
LOGGER.error("Failed to create PDF", e1);
|
||||
}
|
||||
|
||||
|
||||
return baos.toString("UTF-8");
|
||||
return baos.toString(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public void toPDF(String xmlFilename, String pdfFilename) {
|
||||
|
||||
// the writing part
|
||||
CIIToUBL c2u = new CIIToUBL();
|
||||
String sourceFilename = "factur-x.xml";
|
||||
File CIIinputFile = new File(xmlFilename);
|
||||
File XMLinputFile = new File(xmlFilename);
|
||||
|
||||
String expected = null;
|
||||
String result = null;
|
||||
|
||||
ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer();
|
||||
/* remove file endings so that tests can also pass after checking
|
||||
out from git with arbitrary options (which may include CSRF changes)
|
||||
*/
|
||||
try {
|
||||
result = zvi.toFOP(CIIinputFile.getAbsolutePath());
|
||||
} catch (FileNotFoundException e) {
|
||||
Logger.getLogger(ZUGFeRDVisualizer.class.getName()).log(Level.SEVERE, null, e);
|
||||
} catch (TransformerException e) {
|
||||
Logger.getLogger(ZUGFeRDVisualizer.class.getName()).log(Level.SEVERE, null, e);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Logger.getLogger(ZUGFeRDVisualizer.class.getName()).log(Level.SEVERE, null, e);
|
||||
result = this.toFOP(XMLinputFile.getAbsolutePath());
|
||||
} catch (FileNotFoundException | TransformerException e) {
|
||||
LOGGER.error("Failed to apply FOP", e);
|
||||
}
|
||||
/*
|
||||
FopConfParser parser = null;
|
||||
try {
|
||||
//parsing configuration
|
||||
parser = new FopConfParser(CLASS_LOADER.getResourceAsStream("fop-config.xconf"), new URI("file:///"));
|
||||
|
||||
} catch (SAXException e) {
|
||||
throw new UncheckedIOException(new IOException(e));
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
} catch (URISyntaxException e) {
|
||||
Logger.getLogger(ZUGFeRDVisualizer.class.getName()).log(Level.SEVERE, null, e);
|
||||
}*/
|
||||
// FopFactoryBuilder builder = parser.getFopFactoryBuilder();
|
||||
DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder();
|
||||
|
||||
Configuration cfg = null;
|
||||
@@ -304,9 +331,14 @@ public class ZUGFeRDVisualizer {
|
||||
|
||||
FopFactory fopFactory = builder.build();//FopFactory.newInstance(new File("c:\\Users\\jstaerk\\temp\\fop-config.xconf"));
|
||||
|
||||
fopFactory.getFontManager().setResourceResolver(
|
||||
ResourceResolverFactory.createInternalResourceResolver(
|
||||
new File(".").toURI(),
|
||||
new ClasspathResolverURIAdapter()));
|
||||
|
||||
FOUserAgent userAgent = fopFactory.newFOUserAgent();
|
||||
|
||||
userAgent.getRendererOptions().put("pdf-a-mode", "PDF/A-1b");
|
||||
userAgent.getRendererOptions().put("pdf-a-mode", "PDF/A-3b");
|
||||
|
||||
// Step 2: Set up output stream.
|
||||
// Note: Using BufferedOutputStream for performance reasons (helpful with FileOutputStreams).
|
||||
@@ -330,28 +362,34 @@ public class ZUGFeRDVisualizer {
|
||||
// Step 6: Start XSLT transformation and FOP processing
|
||||
transformer.transform(src, res);
|
||||
|
||||
//Files.write(Paths.get("C:\\Users\\jstaerk\\temp\\fop.pdf"), res.toString().getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
} catch (FOPException e) {
|
||||
Logger.getLogger(ZUGFeRDVisualizer.class.getName()).log(Level.SEVERE, null, e);
|
||||
} catch (TransformerConfigurationException e) {
|
||||
Logger.getLogger(ZUGFeRDVisualizer.class.getName()).log(Level.SEVERE, null, e);
|
||||
} catch (IOException e) {
|
||||
Logger.getLogger(ZUGFeRDVisualizer.class.getName()).log(Level.SEVERE, null, e);
|
||||
} catch (TransformerException e) {
|
||||
Logger.getLogger(ZUGFeRDVisualizer.class.getName()).log(Level.SEVERE, null, e);
|
||||
} catch (FOPException | IOException | TransformerException e) {
|
||||
LOGGER.error("Failed to create PDF", e);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected void applyZF2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
|
||||
throws TransformerException {
|
||||
if (mXsltXRTemplate == null) {
|
||||
mXsltXRTemplate = mFactory.newTemplates(
|
||||
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/cii-xr.xsl")));
|
||||
|
||||
}
|
||||
Transformer transformer = mXsltXRTemplate.newTransformer();
|
||||
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
|
||||
}
|
||||
|
||||
protected void applyCIO2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
|
||||
throws TransformerException {
|
||||
if (mXsltCIOTemplate == null) {
|
||||
mXsltCIOTemplate = mFactory.newTemplates(
|
||||
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/cio-xr.xsl")));
|
||||
}
|
||||
Transformer transformer = mXsltCIOTemplate.newTransformer();
|
||||
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
|
||||
}
|
||||
|
||||
protected void applyUBL2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
|
||||
throws TransformerException {
|
||||
if (mXsltUBLTemplate == null) {
|
||||
|
||||
@@ -30,5 +30,5 @@ public class TaxCategoryCodeTypeConstants {
|
||||
public static final String UNTAXEDSERVICE = "O";
|
||||
public static final String INTRACOMMUNITY = "K";
|
||||
|
||||
public static Set<String> CATEGORY_CODES_WITH_EXEMPTION_REASON = Stream.of(INTRACOMMUNITY, REVERSECHARGE).collect(Collectors.toSet());
|
||||
public static Set<String> CATEGORY_CODES_WITH_EXEMPTION_REASON = Stream.of(INTRACOMMUNITY, REVERSECHARGE, TAXEXEMPT).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.mustangproject.util;
|
||||
|
||||
public final class ByteArraySearcher {
|
||||
|
||||
private ByteArraySearcher() {
|
||||
}
|
||||
|
||||
public static int indexOf(byte[] haystack, byte[] needle) {
|
||||
if (needle.length > haystack.length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Any needle to search?
|
||||
if (needle.length == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i <= haystack.length - needle.length; i++) {
|
||||
boolean found = true;
|
||||
for (int j = 0; j < needle.length; j++) {
|
||||
if (haystack[i + j] != needle[j]) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static boolean contains(byte[] haystack, byte[] needle) {
|
||||
return indexOf(haystack, needle) >= 0;
|
||||
}
|
||||
|
||||
public static boolean startsWith(byte[] haystack, byte[] needle) {
|
||||
if (needle.length > haystack.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Any needle to search?
|
||||
if (needle.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int j = 0; j < needle.length; j++) {
|
||||
if (haystack[j] != needle[j]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
144
library/src/main/java/org/mustangproject/util/NodeMap.java
Normal file
144
library/src/main/java/org/mustangproject/util/NodeMap.java
Normal file
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* *********************************************************************
|
||||
* <p>
|
||||
* Copyright (c) 2024 Jan N. Klug
|
||||
* <p>
|
||||
* Use is subject to license terms.
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* <p>
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* <p>
|
||||
* **********************************************************************
|
||||
*/
|
||||
package org.mustangproject.util;
|
||||
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* The {@link NodeMap} contains a {@link Map} representation of DOM object
|
||||
* It can be constructed either from the children of a single {@link Node} or a {@link NodeList}.
|
||||
*/
|
||||
public class NodeMap {
|
||||
private final Map<String, List<Node>> map = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Create a new {@link NodeMap}
|
||||
* The {@link Node} that is passed to the constructor must not be null.
|
||||
* The {@link NodeMap} will be empty when no child nodes are present.
|
||||
*
|
||||
* @param node the node that shall be represented by this {@link NodeMap}
|
||||
* @throws IllegalArgumentException when argument is null
|
||||
*/
|
||||
public NodeMap(Node node) {
|
||||
if (node == null) {
|
||||
throw new IllegalArgumentException("node cannot be null");
|
||||
}
|
||||
if (node.hasChildNodes()) {
|
||||
mapNodeList(node.getChildNodes());
|
||||
}
|
||||
}
|
||||
|
||||
public NodeMap(NodeList nodeList) {
|
||||
if (nodeList == null) {
|
||||
throw new IllegalArgumentException("nodeList cannot be null");
|
||||
}
|
||||
mapNodeList(nodeList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get matching node by {@code LocalName}
|
||||
* In case more than one node matches, it is not guaranteed that the first match is selected
|
||||
*
|
||||
* @param localNames one or more {@code LocalName}s
|
||||
* @return the matching node
|
||||
*/
|
||||
public Optional<Node> getNode(String... localNames) {
|
||||
return getAllNodes(localNames).findAny();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a {@link NodeMap} of the child of a matching node by {@code LocalName}
|
||||
* In case more than one node matches, it is not guaranteed that the first match is selected
|
||||
*
|
||||
* @param localNames one or more {@code LocalName}s
|
||||
* @return the {@link NodeMap} representation of the matching node
|
||||
*/
|
||||
public Optional<NodeMap> getAsNodeMap(String... localNames) {
|
||||
return getNode(localNames).filter(Node::hasChildNodes).map(NodeMap::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text content of a matching node
|
||||
* In case more than one node matches, it is not guaranteed that the first match is selected
|
||||
*
|
||||
* @param localNames one or more {@code LocalName}s
|
||||
* @return the text content of the matching node
|
||||
*/
|
||||
public Optional<String> getAsString(String... localNames) {
|
||||
return getNode(localNames).map(Node::getTextContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text content of a matching node
|
||||
* In case more than one node matches, it is not guaranteed that the first match is selected
|
||||
*
|
||||
* @param localNames one or more {@code LocalName}s
|
||||
* @return the text content of the matching node, converted to BigDecimal
|
||||
*/
|
||||
public Optional<BigDecimal> getAsBigDecimal(String... localNames) {
|
||||
return getNode(localNames).map(Node::getTextContent).map(s->new BigDecimal(s.trim()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text content of a matching node
|
||||
* In case more than one node matches, it is not guaranteed that the first match is selected
|
||||
*
|
||||
* @param localNames one or more {@code LocalName}s
|
||||
* @return the text content of the matching node or {@code null} when no matching node could be found
|
||||
*/
|
||||
public String getAsStringOrNull(String... localNames) {
|
||||
return getAsString(localNames).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all matching nodes
|
||||
*
|
||||
* @param localNames one or more {@code LocalName}s
|
||||
* @return a {@code Stream} that contains all matching nodes (can be empty if nothing matches)
|
||||
*/
|
||||
public Stream<Node> getAllNodes(String... localNames) {
|
||||
List<String> localNamesList = Arrays.asList(localNames);
|
||||
return map.entrySet().stream().filter(e -> localNamesList.contains(e.getKey())).flatMap(e -> e.getValue().stream());
|
||||
}
|
||||
|
||||
private void mapNodeList(NodeList nodeList) {
|
||||
IntStream.range(0, nodeList.getLength()).mapToObj(nodeList::item)
|
||||
.filter(node -> node != null && node.getLocalName() != null)
|
||||
.forEach(node -> map.computeIfAbsent(node.getLocalName(), k -> new ArrayList<>()).add(node));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return map.toString();
|
||||
}
|
||||
}
|
||||
BIN
library/src/main/resources/FreeSans.ttf
Normal file
BIN
library/src/main/resources/FreeSans.ttf
Normal file
Binary file not shown.
@@ -25,7 +25,7 @@
|
||||
<renderer mime="application/pdf">
|
||||
<fonts><!-- https://xmlgraphics.apache.org/fop/1.1/fonts.html auto-embed -->
|
||||
<auto-detect/>
|
||||
<font kerning="no" embed-url="FreeSans.ttf" embedding-mode="subset">
|
||||
<font kerning="no" embed-url="classpath:FreeSans.ttf" embedding-mode="subset">
|
||||
<font-triplet name="SourceSerifPro" style="normal" weight="normal" />
|
||||
<font-triplet name="Times-Bold" style="normal" weight="normal" />
|
||||
</font>
|
||||
|
||||
2225
library/src/main/resources/stylesheets/cio-xr.xsl
Normal file
2225
library/src/main/resources/stylesheets/cio-xr.xsl
Normal file
File diff suppressed because it is too large
Load Diff
@@ -198,7 +198,7 @@
|
||||
<entry key="xr:Third_party_payment_type" id="BT-DEX-001">Art der Fremdforderung</entry>
|
||||
<entry key="xr:Third_party_payment_amount" id="BT-DEX-002">Betrag der Fremdforderung</entry>
|
||||
<entry key="xr:Third_party_payment_description" id="BT-DEX-003">Beschreibung der Fremdforderung</entry>
|
||||
<entry key="uebersicht">Übersicht</entry>
|
||||
<entry key="uebersicht">Daten der E-Rechnung</entry>
|
||||
<entry key="uebersichtKaeufer" id="BG-7">Informationen zum Käufer</entry>
|
||||
<entry key="uebersichtVerkaeufer" id="BG-4">Informationen zum Verkäufer</entry>
|
||||
<entry key="uebersichtRechnungsInfo" id="invoice-data">Rechnungsdaten</entry>
|
||||
@@ -214,7 +214,7 @@
|
||||
<entry key="uebersichtZahlungLastschrift" id="BG-19">Lastschrift</entry>
|
||||
<entry key="uebersichtZahlungUeberweisung" id="BG-17">Überweisung</entry>
|
||||
<entry key="uebersichtBemerkungen" id="BG-1">Bemerkungen zur Rechnung</entry>
|
||||
<entry key="details">Details</entry>
|
||||
<entry key="details">Rechnungspositionen</entry>
|
||||
<entry key="detailsPositionAbrechnungszeitraum" id="BG-26">Abrechnungszeitraum</entry>
|
||||
<entry key="detailsPositionPreiseinzelheiten" id="BG-29">Preiseinzelheiten</entry>
|
||||
<entry key="detailsPositionNachlaesse" id="BG-27">Nachlässe auf Ebene der Rechnungsposition</entry>
|
||||
|
||||
@@ -807,10 +807,11 @@
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="zusaetzeVertrag">
|
||||
<xsl:call-template name="box">
|
||||
<xsl:call-template name="spanned-box">
|
||||
<xsl:with-param name="identifier" select="'zusaetzeVertrag'"/>
|
||||
<xsl:with-param name="content">
|
||||
<xsl:call-template name="list">
|
||||
<xsl:with-param name="layout" select="'einspaltig'"/>
|
||||
<xsl:with-param name="content">
|
||||
<xsl:apply-templates mode="list-entry" select="xr:Tender_or_lot_reference"/>
|
||||
<xsl:apply-templates mode="list-entry" select="xr:Receiving_advice_reference"/>
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
@@ -65,7 +66,7 @@ public class BackwardCompatibilityTest extends TestCase implements IExportableTr
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ze.export(baos);
|
||||
ze.close();
|
||||
String pdfContent = baos.toString("UTF-8");
|
||||
String pdfContent = baos.toString(StandardCharsets.UTF_8);
|
||||
assertFalse(pdfContent.indexOf("(via mustangproject.org") == -1);
|
||||
// check for pdf-a schema extension
|
||||
// assertFalse(pdfContent.indexOf("<zf:ConformanceLevel>EN 16931</zf:ConformanceLevel>") == -1);
|
||||
@@ -105,7 +106,7 @@ public class BackwardCompatibilityTest extends TestCase implements IExportableTr
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ze.export(baos);
|
||||
ze.close();
|
||||
String pdfContent = baos.toString("UTF-8");
|
||||
String pdfContent = baos.toString(StandardCharsets.UTF_8);
|
||||
assertFalse(pdfContent.indexOf("(via mustangproject.org") == -1);
|
||||
// check for pdf-a schema extension
|
||||
// assertFalse(pdfContent.indexOf("<zf:ConformanceLevel>EN 16931</zf:ConformanceLevel>") == -1);
|
||||
@@ -143,7 +144,7 @@ public class BackwardCompatibilityTest extends TestCase implements IExportableTr
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ze.export(baos);
|
||||
ze.close();
|
||||
String pdfContent = baos.toString("UTF-8");
|
||||
String pdfContent = baos.toString(StandardCharsets.UTF_8);
|
||||
assertFalse(pdfContent.indexOf("(via mustangproject.org") == -1);
|
||||
// check for pdf-a schema extension
|
||||
// assertFalse(pdfContent.indexOf("<zf:ConformanceLevel>EN 16931</zf:ConformanceLevel>") == -1);
|
||||
|
||||
@@ -22,16 +22,7 @@ import junit.framework.Test;
|
||||
import junit.framework.TestSuite;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.runners.MethodSorters;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import org.mustangproject.XMLTools;
|
||||
|
||||
public class BaseTest extends TestCase {
|
||||
@@ -61,6 +52,15 @@ public class BaseTest extends TestCase {
|
||||
assertEquals("12.00", XMLTools.nDigitFormat(new BigDecimal("12"),2));
|
||||
assertEquals("12", XMLTools.nDigitFormat(new BigDecimal("12"),0));
|
||||
assertEquals("20000123.342", XMLTools.nDigitFormat(new BigDecimal("20000123.3419"),3));
|
||||
|
||||
assertEquals("0.00", XMLTools.nDigitFormatDecimalRange(BigDecimal.ZERO,2, 2));
|
||||
assertEquals("-1.10", XMLTools.nDigitFormatDecimalRange(new BigDecimal("-1.100000"), 4,2));
|
||||
assertEquals("-1.101", XMLTools.nDigitFormatDecimalRange(new BigDecimal("-1.101000"),10, 3));
|
||||
assertEquals("-1.10", XMLTools.nDigitFormatDecimalRange(new BigDecimal("-1.103"), 2,2));
|
||||
assertEquals("4", XMLTools.nDigitFormatDecimalRange(new BigDecimal("4"),2, 0));
|
||||
assertEquals("3.14", XMLTools.nDigitFormatDecimalRange(new BigDecimal("3.141526"),2, 0));
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mustangproject.util.ByteArraySearcher;
|
||||
|
||||
public class ByteArraySearcherTest
|
||||
{
|
||||
@Test
|
||||
public void testIndexOf () {
|
||||
byte [] haystack = "Hello World".getBytes (StandardCharsets.ISO_8859_1);
|
||||
assertEquals (0, ByteArraySearcher.indexOf (haystack, new byte [] { 'H' }));
|
||||
assertEquals (1, ByteArraySearcher.indexOf (haystack, new byte [] { 'e' }));
|
||||
assertEquals (0, ByteArraySearcher.indexOf (haystack, new byte [] { 'H', 'e' }));
|
||||
assertEquals (0, ByteArraySearcher.indexOf (haystack, new byte [] { 'H', 'e' }));
|
||||
assertEquals (0, ByteArraySearcher.indexOf (haystack, new byte [] { 'H', 'e', 'l', 'l' }));
|
||||
assertEquals (0, ByteArraySearcher.indexOf (haystack, haystack));
|
||||
assertEquals (-1, ByteArraySearcher.indexOf (haystack, new byte [0]));
|
||||
assertEquals (-1, ByteArraySearcher.indexOf (haystack, new byte [] { 'a' }));
|
||||
assertEquals (-1, ByteArraySearcher.indexOf (haystack, new byte [] { 'h' }));
|
||||
assertEquals (-1, ByteArraySearcher.indexOf (haystack, new byte [] { 'r', 'o' }));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartsWith () {
|
||||
byte [] haystack = "Hello World".getBytes (StandardCharsets.ISO_8859_1);
|
||||
assertTrue (ByteArraySearcher.startsWith (haystack, new byte [] { 'H' }));
|
||||
assertFalse (ByteArraySearcher.startsWith (haystack, new byte [] { 'e' }));
|
||||
assertTrue (ByteArraySearcher.startsWith (haystack, new byte [] { 'H', 'e' }));
|
||||
assertTrue (ByteArraySearcher.startsWith (haystack, new byte [] { 'H', 'e' }));
|
||||
assertTrue (ByteArraySearcher.startsWith (haystack, new byte [] { 'H', 'e', 'l', 'l' }));
|
||||
assertTrue (ByteArraySearcher.startsWith (haystack, haystack));
|
||||
assertFalse (ByteArraySearcher.startsWith (haystack, new byte [0]));
|
||||
assertFalse (ByteArraySearcher.startsWith (haystack, new byte [] { 'a' }));
|
||||
assertFalse (ByteArraySearcher.startsWith (haystack, new byte [] { 'h' }));
|
||||
assertFalse (ByteArraySearcher.startsWith (haystack, new byte [] { 'r', 'o' }));
|
||||
}
|
||||
}
|
||||
@@ -6,17 +6,18 @@ import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mustangproject.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/***
|
||||
* tests the linecalculator and transactioncalculator classes
|
||||
*
|
||||
*/
|
||||
public class CalculationTest {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(CalculationTest.class);
|
||||
|
||||
@Test
|
||||
public void testLineCalculator_simpleAmounts_resultInValidVATAmount() {
|
||||
@@ -92,111 +93,112 @@ public class CalculationTest {
|
||||
invoice.setDeliveryDate(sqlDate.parse("2020-12-31"));
|
||||
invoice.setDueDate(sqlDate.parse("2021-01-15"));
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger(CalculationTest.class.getName()).log(Level.SEVERE, null, e);
|
||||
LOGGER.error("Failed to set dates", e);
|
||||
|
||||
}
|
||||
|
||||
/* trade party (sender) */
|
||||
|
||||
TradeParty sender = new TradeParty("Maier GmbH", "Musterweg 5", "11111", "Testung", "DE");
|
||||
sender.addVATID("DE2222222222");
|
||||
invoice.setSender(sender);
|
||||
/* trade party (recipient) */
|
||||
|
||||
/* trade party (recipient) */
|
||||
TradeParty recipient = new TradeParty("Teston GmbH" + " " + "Zentrale" + " " + "", "Testweg 5", "11111", "Testung", "DE");
|
||||
recipient.setID("111111");
|
||||
recipient.addVATID("DE111111111");
|
||||
invoice.setRecipient(recipient);
|
||||
/* item */
|
||||
|
||||
/* item */
|
||||
Product product;
|
||||
Item item;
|
||||
BigDecimal item_increase = BigDecimal.ZERO;
|
||||
BigDecimal item_discount = BigDecimal.ZERO;
|
||||
|
||||
product = new Product("AAA", "", "H84", sales_tax_percent1).setSellerAssignedID("1AAA");
|
||||
item = new Item(product, new BigDecimal("4.750"), new BigDecimal(5.00));
|
||||
|
||||
item_discount = new BigDecimal("10.00");
|
||||
|
||||
// set values for additional charge and discount used for next lines
|
||||
BigDecimal item_increase = BigDecimal.ZERO;
|
||||
BigDecimal item_discount = BigDecimal.valueOf(10.00);
|
||||
|
||||
/* lines */
|
||||
if (item_increase.compareTo(BigDecimal.ZERO) > 0) {
|
||||
item.addCharge(new Charge().setPercent(item_increase).setTaxPercent(sales_tax_percent1).setCategoryCode("ZZZ").setReason("Zuschlag"));
|
||||
item.addCharge(new Charge().setPercent(item_increase).setTaxPercent(sales_tax_percent1).setReasonCode("ZZZ").setReason("Zuschlag"));
|
||||
}
|
||||
|
||||
if (item_discount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
item.addAllowance(new Allowance().setPercent(item_discount).setTaxPercent(sales_tax_percent1).setCategoryCode("95").setReason("Rabatt"));
|
||||
item.addAllowance(new Allowance().setPercent(item_discount).setTaxPercent(sales_tax_percent1).setReasonCode("95").setReason("Rabatt"));
|
||||
}
|
||||
|
||||
invoice.addItem(item);
|
||||
|
||||
// reset values for additional charge and discount used for next lines
|
||||
item_increase = BigDecimal.ZERO;
|
||||
item_discount = BigDecimal.ZERO;
|
||||
|
||||
product = new Product("BBB", "", "H84", sales_tax_percent1).setSellerAssignedID("2BBB");
|
||||
item = new Item(product, new BigDecimal("5.750"), new BigDecimal(4.00));
|
||||
item_discount = BigDecimal.ZERO;
|
||||
if (item_increase.compareTo(BigDecimal.ZERO) > 0) {
|
||||
item.addCharge(new Charge().setPercent(item_increase).setTaxPercent(sales_tax_percent1).setCategoryCode("ZZZ").setReason("Zuschlag"));
|
||||
item.addCharge(new Charge().setPercent(item_increase).setTaxPercent(sales_tax_percent1).setReasonCode("ZZZ").setReason("Zuschlag"));
|
||||
}
|
||||
if (item_discount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
item.addAllowance(new Allowance().setPercent(item_discount).setTaxPercent(sales_tax_percent1).setCategoryCode("95").setReason("Rabatt"));
|
||||
item.addAllowance(new Allowance().setPercent(item_discount).setTaxPercent(sales_tax_percent1).setReasonCode("95").setReason("Rabatt"));
|
||||
}
|
||||
|
||||
invoice.addItem(item);
|
||||
|
||||
product = new Product("CCC", "", "H84", sales_tax_percent1).setSellerAssignedID("3CCC");
|
||||
item = new Item(product, new BigDecimal("6.750"), new BigDecimal(3.00));
|
||||
item_discount = new BigDecimal("10.00");
|
||||
if (item_increase.compareTo(BigDecimal.ZERO) > 0) {
|
||||
item.addCharge(new Charge().setPercent(item_increase).setTaxPercent(sales_tax_percent1).setCategoryCode("ZZZ").setReason("Zuschlag"));
|
||||
item.addCharge(new Charge().setPercent(item_increase).setTaxPercent(sales_tax_percent1).setReasonCode("ZZZ").setReason("Zuschlag"));
|
||||
}
|
||||
|
||||
if (item_discount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
item.addAllowance(new Allowance().setPercent(item_discount).setTaxPercent(sales_tax_percent1).setCategoryCode("95").setReason("Rabatt"));
|
||||
item.addAllowance(new Allowance().setPercent(item_discount).setTaxPercent(sales_tax_percent1).setReasonCode("95").setReason("Rabatt"));
|
||||
}
|
||||
|
||||
invoice.addItem(item);
|
||||
|
||||
|
||||
product = new Product("DDD", "", "H84", sales_tax_percent1).setSellerAssignedID("4DDD");
|
||||
item = new Item(product, new BigDecimal("7.750"), new BigDecimal(2.00));
|
||||
|
||||
item_discount = BigDecimal.ZERO;
|
||||
|
||||
|
||||
if (item_increase.compareTo(BigDecimal.ZERO) > 0) {
|
||||
item.addCharge(new Charge().setPercent(item_increase).setTaxPercent(sales_tax_percent1).setCategoryCode("ZZZ").setReason("Zuschlag"));
|
||||
item.addCharge(new Charge().setPercent(item_increase).setTaxPercent(sales_tax_percent1).setReasonCode("ZZZ").setReason("Zuschlag"));
|
||||
}
|
||||
|
||||
if (item_discount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
item.addAllowance(new Allowance().setPercent(item_discount).setTaxPercent(sales_tax_percent1).setCategoryCode("95").setReason("Rabatt"));
|
||||
item.addAllowance(new Allowance().setPercent(item_discount).setTaxPercent(sales_tax_percent1).setReasonCode("95").setReason("Rabatt"));
|
||||
}
|
||||
|
||||
invoice.addItem(item);
|
||||
|
||||
|
||||
product = new Product("EEE", "", "H84", sales_tax_percent1).setSellerAssignedID("5EEE");
|
||||
item = new Item(product, new BigDecimal("8.750"), new BigDecimal(1.00));
|
||||
|
||||
item_discount = BigDecimal.ZERO;
|
||||
|
||||
|
||||
if (item_increase.compareTo(BigDecimal.ZERO) > 0) {
|
||||
item.addCharge(new Charge().setPercent(item_increase).setTaxPercent(sales_tax_percent1).setCategoryCode("ZZZ").setReason("Zuschlag"));
|
||||
item.addCharge(new Charge().setPercent(item_increase).setTaxPercent(sales_tax_percent1).setReasonCode("ZZZ").setReason("Zuschlag"));
|
||||
}
|
||||
|
||||
if (item_discount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
item.addAllowance(new Allowance().setPercent(item_discount).setTaxPercent(sales_tax_percent1).setCategoryCode("95").setReason("Rabatt"));
|
||||
item.addAllowance(new Allowance().setPercent(item_discount).setTaxPercent(sales_tax_percent1).setReasonCode("95").setReason("Rabatt"));
|
||||
|
||||
}
|
||||
invoice.addItem(item);
|
||||
|
||||
if (total_increase_percent.compareTo(BigDecimal.ZERO) > 0) {
|
||||
invoice.addCharge(new Charge().setPercent(total_increase_percent).setTaxPercent(sales_tax_percent1).setCategoryCode("ZZZ").setReason("Zuschläge"));
|
||||
}
|
||||
// reset values for additional charge and discount used on invoice level
|
||||
item_increase = BigDecimal.valueOf(3.50);
|
||||
item_discount = BigDecimal.valueOf(10.00);
|
||||
|
||||
if (total_increase_percent.compareTo(BigDecimal.ZERO) > 0) {
|
||||
invoice.addCharge(new Charge().setPercent(total_increase_percent).setTaxPercent(sales_tax_percent1).setReasonCode("ZZZ").setReason("Zuschläge"));
|
||||
}
|
||||
if (total_discount_percent.compareTo(BigDecimal.ZERO) > 0) {
|
||||
invoice.addAllowance(new Allowance().setPercent(total_discount_percent).setTaxPercent(sales_tax_percent1).setCategoryCode("95").setReason("Rabatte"));
|
||||
invoice.addAllowance(new Allowance().setPercent(total_discount_percent).setTaxPercent(sales_tax_percent1).setReasonCode("95").setReason("Rabatte"));
|
||||
}
|
||||
TransactionCalculator calculator = new TransactionCalculator(invoice);
|
||||
assertEquals(valueOf(99.54).stripTrailingZeros(), calculator.getGrandTotal().stripTrailingZeros());
|
||||
assertEquals(valueOf(101.86).stripTrailingZeros(), calculator.getGrandTotal().stripTrailingZeros());
|
||||
}
|
||||
|
||||
/**
|
||||
* LineCalculator should not throw an exception when calculating a non-terminating decimal expansion
|
||||
* */
|
||||
@Test
|
||||
public void testNonTerminatingDecimalExpansion() {
|
||||
final Product product = new Product();
|
||||
final IZUGFeRDExportableItem currentItem = new Item().setPrice(valueOf(386.52))
|
||||
.setQuantity(BigDecimal.valueOf(31))
|
||||
.setBasisQuantity(BigDecimal.valueOf(366))
|
||||
.setProduct(product);
|
||||
final LineCalculator calculator = new LineCalculator(currentItem);
|
||||
assertEquals(BigDecimal.valueOf(32.74), calculator.getItemTotalNetAmount());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ import java.util.GregorianCalendar;
|
||||
import static org.xmlunit.assertj.XmlAssert.assertThat;
|
||||
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class DXTest extends MustangReaderTestCase implements IExportableTransaction {
|
||||
public class DXTest extends MustangReaderTestCase {
|
||||
final String TARGET_PDF = "./target/testout-DX.pdf";
|
||||
final String TARGET_XML = "./target/testout-DX.xml";
|
||||
|
||||
|
||||
@@ -21,12 +21,9 @@
|
||||
*/
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParseException;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import junit.framework.TestCase;
|
||||
import org.assertj.core.util.Lists;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.mustangproject.*;
|
||||
@@ -39,7 +36,7 @@ public class DeSerializationTest extends TestCase {
|
||||
public void testJackson() throws JsonProcessingException {
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
Invoice i = new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setSender(new TradeParty("some org", "teststr", "55232", "teststadt", "DE").addTaxID("taxID")).setOwnVATID("DE0815").setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addVATID("DE4711").setContact(new Contact("Franz Müller", "01779999999", "franz@mueller.de", "teststr. 12", "55232", "Entenhausen", "DE"))).setNumber("0185").addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), new BigDecimal("1"), new BigDecimal(1.0)));
|
||||
Invoice i = new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setSender(new TradeParty("some org", "teststr", "55232", "teststadt", "DE").addTaxID("taxID").addBankDetails(new BankDetails("DE3600000123456", "ABCDEFG1001").setAccountName("Donald Duck")).setEmail("info@company.com")).setOwnVATID("DE0815").setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addVATID("DE4711").setContact(new Contact("Franz Müller", "01779999999", "franz@mueller.de", "teststr. 12", "55232", "Entenhausen", "DE"))).setNumber("0185").addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), new BigDecimal("1"), new BigDecimal(1.0)));
|
||||
String jsonArray = mapper.writeValueAsString(i);
|
||||
|
||||
// [{"stringValue":"a","intValue":1,"booleanValue":true},
|
||||
@@ -48,6 +45,90 @@ public class DeSerializationTest extends TestCase {
|
||||
Invoice fromJSON = mapper.readValue(jsonArray, Invoice.class);
|
||||
assertEquals(fromJSON.getNumber(), i.getNumber());
|
||||
assertEquals(fromJSON.getZFItems().length, i.getZFItems().length);
|
||||
assertEquals("info@company.com", fromJSON.getSender().getEmail());
|
||||
assertEquals("info@company.com", fromJSON.getSender().getUriUniversalCommunicationID());
|
||||
|
||||
}
|
||||
|
||||
public void testAllowanceRead() throws JsonProcessingException {
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
// [{"stringValue":"a","intValue":1,"booleanValue":true},
|
||||
// {"stringValue":"bc","intValue":3,"booleanValue":false}]
|
||||
|
||||
Invoice fromJSON = mapper.readValue("{\n" +
|
||||
" \"documentCode\": \"380\",\n" +
|
||||
" \"number\": \"471102\",\n" +
|
||||
" \"ownOrganisationName\": \"Lieferant GmbH\",\n" +
|
||||
" \"currency\": \"EUR\",\n" +
|
||||
" \"issueDate\": \"2018-03-03T23:00:00.000+00:00\",\n" +
|
||||
" \"deliveryDate\": \"2018-03-03T23:00:00.000+00:00\",\n" +
|
||||
" \"sender\": {\n" +
|
||||
" \"name\": \"Lieferant GmbH\",\n" +
|
||||
" \"zip\": \"80333\",\n" +
|
||||
" \"street\": \"Lieferantenstraße 20\",\n" +
|
||||
" \"location\": \"München\",\n" +
|
||||
" \"country\": \"DE\",\n" +
|
||||
" \"taxID\": \"201/113/40209\",\n" +
|
||||
" \"vatID\": \"DE123456789\",\n" +
|
||||
" \"vatid\": \"DE123456789\"\n" +
|
||||
" },\n" +
|
||||
" \"recipient\": {\n" +
|
||||
" \"name\": \"Kunden AG Mitte\",\n" +
|
||||
" \"zip\": \"69876\",\n" +
|
||||
" \"street\": \"Kundenstraße 15\",\n" +
|
||||
" \"location\": \"Frankfurt\",\n" +
|
||||
" \"country\": \"DE\"\n" +
|
||||
" },\n" +
|
||||
" \"grandTotal\": 234.43,\n" +
|
||||
" \"zfitems\": [\n" +
|
||||
" {\n" +
|
||||
" \"price\": 9.9,\n" +
|
||||
" \"quantity\": 20,\n" +
|
||||
" \"tax\": null,\n" +
|
||||
" \"grossPrice\": null,\n" +
|
||||
" \"lineTotalAmount\": null,\n" +
|
||||
" \"basisQuantity\": 1,\n" +
|
||||
" \"detailedDeliveryPeriodFrom\": null,\n" +
|
||||
" \"detailedDeliveryPeriodTo\": null,\n" +
|
||||
" \"id\": null,\n" +
|
||||
" \"product\": {\n" +
|
||||
" \"unit\": \"H87\",\n" +
|
||||
" \"name\": \"Trennblätter A4\",\n" +
|
||||
" \"taxCategoryCode\": \"S\",\n" +
|
||||
" \"attributes\": null,\n" +
|
||||
" \"vatpercent\": 19\n" +
|
||||
" },\n" +
|
||||
" \"value\": 9.9\n" +
|
||||
" }\n" +
|
||||
" ],\n" +
|
||||
" \"tradeSettlement\": null,\n" +
|
||||
" \"ownTaxID\": \"201/113/40209\",\n" +
|
||||
" \"ownVATID\": \"DE123456789\",\n" +
|
||||
" \"ownStreet\": \"Lieferantenstraße 20\",\n" +
|
||||
" \"ownZIP\": \"80333\",\n" +
|
||||
" \"ownLocation\": \"München\",\n" +
|
||||
" \"ownCountry\": \"DE\",\n" +
|
||||
" \"zfallowances\": [\n" +
|
||||
" {\n" +
|
||||
" \"totalAmount\": 1,\n" +
|
||||
" \"taxPercent\": 19,\n" +
|
||||
" \"reason\": \"Sondernachlass\",\n" +
|
||||
" \"reasonCode\": null,\n" +
|
||||
" \"categoryCode\": \"S\",\n" +
|
||||
" \"charge\": false\n" +
|
||||
" }\n" +
|
||||
" ]\n" +
|
||||
"}", Invoice.class);
|
||||
TransactionCalculator tc=new TransactionCalculator(fromJSON);
|
||||
assertEquals(tc.getGrandTotal(),new BigDecimal("234.43"));
|
||||
assertEquals(fromJSON.getNumber(), fromJSON.getNumber());
|
||||
assertEquals(fromJSON.getZFItems().length, fromJSON.getZFItems().length);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.math.BigDecimal;
|
||||
public class IZUGFeRDAllowanceChargeImpl implements IZUGFeRDAllowanceCharge {
|
||||
private BigDecimal totalAmount;
|
||||
private String reason;
|
||||
private String reasonCode;
|
||||
private BigDecimal taxPercent;
|
||||
boolean isCharge=true;
|
||||
|
||||
@@ -36,6 +37,11 @@ public class IZUGFeRDAllowanceChargeImpl implements IZUGFeRDAllowanceCharge {
|
||||
return reason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReasonCode() {
|
||||
return reasonCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getTaxPercent() {
|
||||
return taxPercent;
|
||||
@@ -61,6 +67,11 @@ public class IZUGFeRDAllowanceChargeImpl implements IZUGFeRDAllowanceCharge {
|
||||
return this;
|
||||
}
|
||||
|
||||
public IZUGFeRDAllowanceChargeImpl setReasonCode(String reasonCode) {
|
||||
this.reasonCode = reasonCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IZUGFeRDAllowanceChargeImpl setTaxPercent(BigDecimal taxPercent) {
|
||||
this.taxPercent = taxPercent;
|
||||
return this;
|
||||
|
||||
@@ -256,13 +256,13 @@ public class MustangReaderWriterCustomXMLTest extends TestCase {
|
||||
"</ram:ApplicableHeaderTradeSettlement>\n" +
|
||||
"</rsm:SupplyChainTradeTransaction>\n" +
|
||||
"</rsm:CrossIndustryInvoice>";
|
||||
zea1.setXML(ownZUGFeRDXML.getBytes("UTF-8"));
|
||||
zea1.setXML(ownZUGFeRDXML.getBytes(StandardCharsets.UTF_8));
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
zea1.disableAutoClose(true);
|
||||
zea1.export(TARGET_PDF);
|
||||
zea1.export(baos);
|
||||
zea1.close();
|
||||
String pdfContent = baos.toString("UTF-8");
|
||||
String pdfContent = baos.toString(StandardCharsets.UTF_8);
|
||||
assertFalse(pdfContent.indexOf("(via mustangproject.org") == -1);
|
||||
assertFalse(pdfContent.indexOf("<fx:ConformanceLevel>EN 16931</fx:ConformanceLevel>") == -1);
|
||||
|
||||
@@ -437,14 +437,14 @@ public class MustangReaderWriterCustomXMLTest extends TestCase {
|
||||
+ "<ram:Name>Heiße Luft pro Liter</ram:Name>\n" + "<ram:Description/>\n"
|
||||
+ "</ram:SpecifiedTradeProduct>\n" + "</ram:IncludedSupplyChainTradeLineItem>\n"
|
||||
+ "</rsm:SpecifiedSupplyChainTradeTransaction>\n" + "</rsm:CrossIndustryDocument>";
|
||||
zea1.setXML(ownZUGFeRDXML.getBytes("UTF-8"));
|
||||
zea1.setXML(ownZUGFeRDXML.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
zea1.disableAutoClose(true);
|
||||
zea1.export(TARGET_PDF);
|
||||
zea1.export(baos);
|
||||
zea1.close();
|
||||
String pdfContent = baos.toString("UTF-8");
|
||||
String pdfContent = baos.toString(StandardCharsets.UTF_8);
|
||||
assertFalse(pdfContent.indexOf("(via mustangproject.org") == -1);
|
||||
assertFalse(pdfContent.indexOf("<zf:ConformanceLevel>BASIC</zf:ConformanceLevel>") == -1);
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ package org.mustangproject.ZUGFeRD;
|
||||
import junit.framework.Test;
|
||||
import junit.framework.TestSuite;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.common.PDMetadata;
|
||||
import org.apache.xmpbox.XMPMetadata;
|
||||
@@ -42,7 +43,7 @@ import java.util.GregorianCalendar;
|
||||
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class MustangReaderWriterEdgeTest extends MustangReaderTestCase {
|
||||
protected class EasyRecipientTradeParty extends RecipientTradeParty implements IZUGFeRDExportableTradeParty {
|
||||
protected class EasyRecipientTradeParty extends RecipientTradeParty {
|
||||
// Not testing extended profile here, lineThree not possible
|
||||
@Override
|
||||
public String getAdditionalAddressExtension() {
|
||||
@@ -252,7 +253,7 @@ public class MustangReaderWriterEdgeTest extends MustangReaderTestCase {
|
||||
setupPdfUnderTest(SOURCE_PDF, TARGET_PDF);
|
||||
|
||||
// now check the contents
|
||||
PDDocument doc = PDDocument.load(new File(TARGET_PDF));
|
||||
PDDocument doc = Loader.loadPDF(new File(TARGET_PDF));
|
||||
PDMetadata meta = doc.getDocumentCatalog().getMetadata();
|
||||
assertNotNull("The pdf must contain XMPMetadata", meta);
|
||||
|
||||
@@ -262,7 +263,7 @@ public class MustangReaderWriterEdgeTest extends MustangReaderTestCase {
|
||||
DomXmpParser xmpParser = new DomXmpParser();
|
||||
XMPMetadata xmp = xmpParser.parse(xmpBytes);
|
||||
|
||||
PDFAIdentificationSchema pdfai = xmp.getPDFIdentificationSchema();
|
||||
PDFAIdentificationSchema pdfai = xmp.getPDFAIdentificationSchema();
|
||||
assertNotNull("The pdf must contain a PDFIdentificationSchema", pdfai);
|
||||
|
||||
assertTrue("The PDF/A conformance must be A, B or U",
|
||||
|
||||
@@ -21,6 +21,7 @@ package org.mustangproject.ZUGFeRD;
|
||||
import junit.framework.Test;
|
||||
import junit.framework.TestSuite;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.common.PDMetadata;
|
||||
import org.apache.pdfbox.pdmodel.encryption.InvalidPasswordException;
|
||||
@@ -38,6 +39,7 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
@@ -212,8 +214,10 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
|
||||
|
||||
public void testForeignImport() {
|
||||
InputStream inputStream = this.getClass().getResourceAsStream("/zugferd_invoice.pdf");
|
||||
ZUGFeRDImporter zi = new ZUGFeRDImporter(inputStream);
|
||||
|
||||
ZUGFeRDImporter zi = new ZUGFeRDImporter();
|
||||
zi.doRecalculateItemPricesFromLineTotals();
|
||||
zi.doIgnoreCalculationErrors();
|
||||
zi.setInputStream(inputStream);
|
||||
// Reading ZUGFeRD
|
||||
String amount = zi.getAmount();
|
||||
|
||||
@@ -328,13 +332,13 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
|
||||
}
|
||||
|
||||
private void checkPdfA3B(File tempFile) throws IOException, InvalidPasswordException {
|
||||
try (PDDocument doc = PDDocument.load(tempFile)) {
|
||||
try (PDDocument doc = Loader.loadPDF(tempFile)) {
|
||||
PDMetadata metadata = doc.getDocumentCatalog().getMetadata();
|
||||
InputStream exportXMPMetadata = metadata.exportXMPMetadata();
|
||||
byte[] xmpBytes = new byte[exportXMPMetadata.available()];
|
||||
exportXMPMetadata.read(xmpBytes);
|
||||
final XMPMetadata xmp = new DomXmpParser().parse(xmpBytes);
|
||||
PDFAIdentificationSchema pdfaid = xmp.getPDFIdentificationSchema();
|
||||
PDFAIdentificationSchema pdfaid = xmp.getPDFAIdentificationSchema();
|
||||
assertEquals(pdfaid.getPart().intValue(), 3);
|
||||
assertEquals(pdfaid.getConformance(), "U");
|
||||
} catch (XmpParsingException e) {
|
||||
@@ -367,7 +371,7 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ze.export(baos);
|
||||
ze.close();
|
||||
String pdfContent = baos.toString("UTF-8");
|
||||
String pdfContent = baos.toString(StandardCharsets.UTF_8);
|
||||
assertFalse(pdfContent.indexOf("(via mustangproject.org") == -1);
|
||||
// check for pdf-a schema extension
|
||||
// assertFalse(pdfContent.indexOf("<zf:ConformanceLevel>EN 16931</zf:ConformanceLevel>") == -1);
|
||||
@@ -411,7 +415,7 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ze.export(baos);
|
||||
ze.close();
|
||||
String pdfContent = baos.toString("UTF-8");
|
||||
String pdfContent = baos.toString(StandardCharsets.UTF_8);
|
||||
assertFalse(pdfContent.indexOf(DocumentContextParameterTypeConstants.BASIC) >= 0);
|
||||
assertFalse(pdfContent.indexOf(DocumentContextParameterTypeConstants.EXTENDED) >= 0);
|
||||
assertTrue(pdfContent.indexOf(DocumentContextParameterTypeConstants.COMFORT) >= 0);
|
||||
@@ -447,7 +451,7 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ze.export(baos);
|
||||
ze.close();
|
||||
String pdfContent = baos.toString("UTF-8");
|
||||
String pdfContent = baos.toString(StandardCharsets.UTF_8);
|
||||
assertFalse(pdfContent.indexOf("(via mustangproject.org") == -1);
|
||||
// check for pdf-a schema extension
|
||||
assertFalse(pdfContent.indexOf("<fx:ConformanceLevel>EN 16931</fx:ConformanceLevel>") == -1);
|
||||
|
||||
@@ -28,7 +28,6 @@ import org.junit.runners.MethodSorters;
|
||||
import org.mustangproject.*;
|
||||
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
@@ -42,7 +41,7 @@ import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class OXTest extends MustangReaderTestCase implements IExportableTransaction {
|
||||
public class OXTest extends MustangReaderTestCase {
|
||||
final String TARGET_PDF = "./target/testout-OX.pdf";
|
||||
final String TARGET_PDF_EDGE = "./target/testout-OX-edge.pdf";
|
||||
final String TARGET_XML = "./target/testout-OX.xml";
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
|
||||
/**
|
||||
* *********************************************************************
|
||||
* <p>
|
||||
* Copyright 2019 Jochen Staerk
|
||||
* <p>
|
||||
* Use is subject to license terms.
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* <p>
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* <p>
|
||||
* **********************************************************************
|
||||
*/
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.mustangproject.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class PreValidationTest extends TestCase {
|
||||
final String TARGET_PDF = "./target/testout-Preval.pdf";
|
||||
final String SOURCE_PDF = "/veraPDFtestsuite6-7-11-t01-fail-a.pdf";
|
||||
|
||||
public void testFailIgnore() {
|
||||
|
||||
// the writing part
|
||||
|
||||
|
||||
boolean hasEx = false;
|
||||
try (InputStream source = this.getClass()
|
||||
.getResourceAsStream(SOURCE_PDF)) {
|
||||
ZUGFeRDExporterFromA1 ze = new ZUGFeRDExporterFromA1().setProducer("My Application")
|
||||
.setCreator(System.getProperty("user.name")).setZUGFeRDVersion(2)
|
||||
.load(source);
|
||||
ze.setTransaction(createInvoice());
|
||||
|
||||
ze.export(TARGET_PDF);
|
||||
} catch (IOException e) {
|
||||
hasEx = true;
|
||||
}
|
||||
assertTrue(hasEx);
|
||||
hasEx = false;
|
||||
try (InputStream source = this.getClass()
|
||||
.getResourceAsStream(SOURCE_PDF)) {
|
||||
ZUGFeRDExporterFromA1 ze = new ZUGFeRDExporterFromA1();
|
||||
ze.ignorePDFAErrors().load(source);
|
||||
|
||||
ze.setProducer("My Application").setCreator(System.getProperty("user.name")).setZUGFeRDVersion(2).
|
||||
setTransaction(createInvoice());
|
||||
ze.export(TARGET_PDF);
|
||||
} catch (IOException e) {
|
||||
hasEx = true;
|
||||
}
|
||||
assertFalse(hasEx);
|
||||
}
|
||||
|
||||
private Invoice createInvoice() {
|
||||
String orgname = "Test company";
|
||||
String number = "123";
|
||||
String amountStr = "1.00";
|
||||
BigDecimal amount = new BigDecimal(amountStr);
|
||||
return new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date())
|
||||
.setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addTaxID("DE4711").addVATID("DE0815").setEmail("info@example.org").setContact(new Contact("Hans Test", "+49123456789", "test@example.org")).addBankDetails(new BankDetails("DE12500105170648489890", "COBADEFXXX")))
|
||||
.setRecipient(new TradeParty("Theodor Est", "Bahnstr. 42", "88802", "Spielkreis", "DE"))
|
||||
.setReferenceNumber("991-01484-64")//leitweg-id
|
||||
// not using any VAT, this is also a test of zero-rated goods:
|
||||
.setNumber(number).addItem(new Item(new Product("Testprodukt", "", "C62", BigDecimal.ZERO), amount, new BigDecimal(1.0)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,13 +23,10 @@ package org.mustangproject.ZUGFeRD;
|
||||
import junit.framework.TestCase;
|
||||
import org.mustangproject.*;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
/***
|
||||
* This is a test to confirm the minimum steps to implement a interface are still sufficient
|
||||
@@ -39,7 +36,8 @@ import java.util.GregorianCalendar;
|
||||
*/
|
||||
public class ProfilesMinimumBasicWLTest extends TestCase {
|
||||
|
||||
final String TARGET_PDF_FX_MINIMUM = "./target/testout-Minimum.pdf";
|
||||
final String TARGET_PDF_FX_MINIMUM_INV = "./target/testout-Minimum-INV.pdf";
|
||||
final String TARGET_PDF_FX_MINIMUM_CN = "./target/testout-Minimum-CN.pdf";
|
||||
|
||||
public void testMinimumCreditNote() {
|
||||
String ownNumber = "NUMFACTURE";
|
||||
@@ -63,14 +61,14 @@ public class ProfilesMinimumBasicWLTest extends TestCase {
|
||||
Invoice i = new Invoice()
|
||||
.setIssueDate(new Date())
|
||||
.setSender(
|
||||
new TradeParty().setName(ownOrgName).setCountry("FR")
|
||||
new TradeParty().setName(ownOrgName).setCountry("FR").setVATID("FR555444333222111")
|
||||
.addBankDetails(new BankDetails(ownIBAN, ownBIC)))
|
||||
.setRecipient(recipient)
|
||||
.setNumber(ownNumber)
|
||||
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), new BigDecimal(123), new BigDecimal(1)))
|
||||
.setCreditNote();
|
||||
ze.setTransaction(i);
|
||||
ze.export(TARGET_PDF_FX_MINIMUM);
|
||||
ze.export(TARGET_PDF_FX_MINIMUM_CN);
|
||||
|
||||
// check for pdf-a schema extension
|
||||
// assertFalse(pdfContent.indexOf("<zf:ConformanceLevel>EN 16931</zf:ConformanceLevel>") == -1);
|
||||
@@ -82,7 +80,7 @@ public class ProfilesMinimumBasicWLTest extends TestCase {
|
||||
}
|
||||
|
||||
// now check the contents (like MustangReaderTest)
|
||||
ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_PDF_FX_MINIMUM);
|
||||
ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_PDF_FX_MINIMUM_CN);
|
||||
|
||||
// Reading ZUGFeRD
|
||||
assertEquals("146.37",zi.getAmount());
|
||||
@@ -118,13 +116,13 @@ public class ProfilesMinimumBasicWLTest extends TestCase {
|
||||
.setIssueDate(new Date())
|
||||
.setDueDate(new Date())
|
||||
.setSender(
|
||||
new TradeParty().setName(ownOrgName).setCountry("FR")
|
||||
new TradeParty().setName(ownOrgName).setCountry("FR").setVATID("FR555444333222111")
|
||||
.addBankDetails(new BankDetails(ownIBAN)))
|
||||
.setRecipient(recipient)
|
||||
.setNumber(ownNumber).setTotalPrepaidAmount(new BigDecimal("1"))
|
||||
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), new BigDecimal(123), new BigDecimal(1)));
|
||||
ze.setTransaction(i);
|
||||
ze.export(TARGET_PDF_FX_MINIMUM);
|
||||
ze.export(TARGET_PDF_FX_MINIMUM_INV);
|
||||
|
||||
// check for pdf-a schema extension
|
||||
// assertFalse(pdfContent.indexOf("<zf:ConformanceLevel>EN 16931</zf:ConformanceLevel>") == -1);
|
||||
@@ -136,10 +134,10 @@ public class ProfilesMinimumBasicWLTest extends TestCase {
|
||||
}
|
||||
|
||||
// now check the contents (like MustangReaderTest)
|
||||
ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_PDF_FX_MINIMUM);
|
||||
ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_PDF_FX_MINIMUM_INV);
|
||||
|
||||
// Reading ZUGFeRD
|
||||
assertEquals("145.37",zi.getAmount());
|
||||
assertEquals("146.37",zi.getAmount());
|
||||
// assertEquals(zi.getBIC(), ownBIC);
|
||||
// assertEquals(zi.getIBAN(), ownIBAN);
|
||||
assertEquals(ownOrgName, zi.getHolder());
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -9,6 +11,7 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
@Ignore
|
||||
public class ResourceCase extends TestCase {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ResourceCase.class.getCanonicalName()); // log output is
|
||||
|
||||
|
||||
@@ -22,15 +22,16 @@ import java.net.URISyntaxException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
/**
|
||||
* This test utility class is providing usability functions to test file resources
|
||||
*/
|
||||
public class ResourceUtilities {
|
||||
private static final Logger LOG = Logger.getLogger(ResourceUtilities.class.getName());
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger (ResourceUtilities.class);
|
||||
|
||||
/**
|
||||
* Loading a File into a String using a certain encoding and file path
|
||||
@@ -73,7 +74,7 @@ public class ResourceUtilities {
|
||||
uri = ResourceUtilities.class.getClassLoader().getResource(relativeFilePath).toURI();
|
||||
uri = new URI(toExternalForm(uri));
|
||||
} catch (URISyntaxException ex) {
|
||||
LOG.log(Level.SEVERE, null, ex);
|
||||
LOGGER.error("Failed to parse URI", ex);
|
||||
}
|
||||
if (uri == null) {
|
||||
throw new FileNotFoundException("Could not find the file '" + relativeFilePath + "'!");
|
||||
@@ -129,7 +130,7 @@ public class ResourceUtilities {
|
||||
try {
|
||||
filepath = ResourceUtilities.class.getClassLoader().getResource("").toURI().getPath() + relativeFilePath;
|
||||
} catch (URISyntaxException ex) {
|
||||
LOG.log(Level.SEVERE, null, ex);
|
||||
LOGGER.error("Failed to parse URI", ex);
|
||||
}
|
||||
return new File(filepath);
|
||||
}
|
||||
@@ -142,7 +143,7 @@ public class ResourceUtilities {
|
||||
try {
|
||||
testFolder = ResourceUtilities.class.getClassLoader().getResource("").toURI().getPath();
|
||||
} catch (URISyntaxException ex) {
|
||||
LOG.log(Level.SEVERE, null, ex);
|
||||
LOGGER.error("Failed to parse URI", ex);
|
||||
}
|
||||
return testFolder;
|
||||
}
|
||||
@@ -206,7 +207,7 @@ public class ResourceUtilities {
|
||||
try {
|
||||
ret = new URI(sb.toString()).toASCIIString();
|
||||
} catch (URISyntaxException ex) {
|
||||
LOG.log(Level.SEVERE, null, ex);
|
||||
LOGGER.error("Failed to parse URI", ex);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ public class UBLTest extends ResourceCase {
|
||||
final ByteArrayOutputStream baos=new ByteArrayOutputStream();
|
||||
oe.export(baos);
|
||||
|
||||
final String theXML = baos.toString("UTF-8");
|
||||
final String theXML = baos.toString(StandardCharsets.UTF_8);
|
||||
assertTrue(theXML.contains("<DespatchAdvice"));
|
||||
Files.write(Paths.get(TARGET_XML), theXML.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (final IOException e) {
|
||||
@@ -123,33 +123,8 @@ public class UBLTest extends ResourceCase {
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void testInvoiceImportOtherUBL() {
|
||||
|
||||
|
||||
File UBLinputFile = getResourceAsFile("EN16931_2_Teilrechnung.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("EN16931_AbweichenderZahlungsempf.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("EN16931_Betriebskostenabrechnung.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("EN16931_Einfach.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("EN16931_Einfach_DueDate.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("EN16931_Einfach_negativePaymentDue.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("EN16931_Elektron.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("EN16931_ElektronischeAdresse.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("EN16931_Gutschrift.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("EN16931_Haftpflichtversicherung_Versicherungssteuer.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("EN16931_Innergemeinschaftliche_Lieferungen.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("EN16931_Kraftfahrversicherung_Bruttopreise.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("EN16931_Miete.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("EN16931_OEPNV.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("EN16931_Physiotherapeut.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("EN16931_Rabatte.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("EN16931_Rechnungskorrektur.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("EN16931_RechnungsUebertragung.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("EN16931_Reisekostenabrechnung.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("EN16931_Sachversicherung_berechneter_Steuersatz.ubl.xml");
|
||||
File UBLinputFile = getResourceAsFile("EN16931_SEPA_Prenotification.ubl.xml");
|
||||
|
||||
File UBLinputFile = getResourceAsFile("ubl/EN16931_1_Teilrechnung.ubl.xml");
|
||||
public void testEdgeInvoiceImportUBL2() {
|
||||
File UBLinputFile = getResourceAsFile("ubl/04.01a-INVOICE_ubl.xml");
|
||||
boolean hasExceptions = false;
|
||||
|
||||
ZUGFeRDInvoiceImporter zii = null;
|
||||
@@ -158,15 +133,12 @@ public class UBLTest extends ResourceCase {
|
||||
zii = new ZUGFeRDInvoiceImporter(new FileInputStream(UBLinputFile));
|
||||
invoice = zii.extractInvoice();
|
||||
} catch (XPathExpressionException | ParseException | FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
hasExceptions = true;
|
||||
}
|
||||
assertFalse(hasExceptions);
|
||||
// Reading ZUGFeRD
|
||||
assertEquals(new BigDecimal("288.79"), invoice.getZFItems()[0].getPrice());
|
||||
assertEquals("04011000-12345-03", invoice.getReferenceNumber());
|
||||
assertEquals("seller@email.de", invoice.getSender().getContact().getEMail());
|
||||
assertEquals("12345", invoice.getRecipient().getZIP());
|
||||
assertEquals("DE75512108001245126199", invoice.getSender().getBankDetails().get(0).getIBAN());
|
||||
|
||||
assertEquals(new BigDecimal("10000.0"), invoice.getTotalPrepaidAmount());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user