Merge branch 'master' into test-fuer-inputstream-validation

This commit is contained in:
Jochen Staerk
2024-10-24 08:24:35 +02:00
committed by GitHub
304 changed files with 122949 additions and 1032927 deletions

View File

@@ -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 - uses: actions/setup-java@v3 # the log states the default ~/.m2/toolchains.xml is being created pointing to the JDK
with: with:
distribution: 'adopt' # for latest JDKs use temurin https://blog.adoptopenjdk.net/2021/08/goodbye-adoptopenjdk-hello-adoptium/ 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 cache: 'maven' #cache/restore any dependencies to improve the workflow execution time
- name: Build with Maven - name: Build with Maven
run: mvn -B package --file pom.xml run: mvn -B package --file pom.xml

View File

@@ -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 - EN16931 validation 1.3.12 codelists v11 #357
- Fonts removed #358 - Fonts removed #358
- xrechnungimporter to read from filename, inputstream - xrechnungimporter to read from filename, inputstream
@@ -6,7 +106,11 @@
- zugferdimporter to accept xml files - zugferdimporter to accept xml files
- UBL importer to also parse contacts - UBL importer to also parse contacts
- https://github.com/ZUGFeRD/mustangproject/pull/369 - 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 2.10.0
======= =======

View File

@@ -1,258 +1,267 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent> <parent>
<groupId>org.mustangproject</groupId>
<artifactId>core</artifactId>
<version>2.15.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.mustangproject</groupId> <groupId>org.mustangproject</groupId>
<artifactId>core</artifactId> <artifactId>Mustang-CLI</artifactId>
<version>2.10.1-SNAPSHOT</version> <name>e-invoices commandline tool, allowing to create(embed), split and validate Factur-X/ZUGFeRD files. Validation
</parent> should also work for XRechnung/CII.
<modelVersion>4.0.0</modelVersion> </name>
<groupId>org.mustangproject</groupId> <packaging>jar</packaging>
<artifactId>Mustang-CLI</artifactId> <version>2.15.0-SNAPSHOT</version>
<name>e-invoices commandline tool, allowing to create(embed), split and validate Factur-X/ZUGFeRD files. Validation should also work for XRechnung/CII. </name> <properties>
<packaging>jar</packaging> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<version>2.10.1-SNAPSHOT</version> <maven.compiler.compilerVersion>11</maven.compiler.compilerVersion>
<properties> <maven.compiler.source>11</maven.compiler.source>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.target>11</maven.compiler.target>
<maven.compiler.compilerVersion>8</maven.compiler.compilerVersion> </properties>
<maven.compiler.source>8</maven.compiler.source> <dependencies>
<maven.compiler.target>8</maven.compiler.target> <dependency>
</properties> <groupId>org.mustangproject</groupId>
<dependencies> <artifactId>validator</artifactId>
<dependency> <version>2.15.0-SNAPSHOT</version>
<groupId>org.mustangproject</groupId> <!-- prototypes of new mustangproject versions can be installed by referring to them and installed to the local repo from a jar file with
<artifactId>validator</artifactId> 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
<version>2.10.1-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 </dependency>
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 <!-- https://mvnrepository.com/artifact/commons-cli/commons-cli -->
--> <dependency><!-- apache commons cli to parse command line -->
</dependency> <groupId>commons-cli</groupId>
<!-- https://mvnrepository.com/artifact/commons-cli/commons-cli --> <artifactId>commons-cli</artifactId>
<dependency><!-- apache commons cli to parse command line --> <version>1.8.0</version>
<groupId>commons-cli</groupId> </dependency>
<artifactId>commons-cli</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>org.riversun</groupId>
<artifactId>bigdoc</artifactId>
<version>0.3.0</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>
<!-- for directory validation: -->
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-assertj</artifactId>
<version>2.9.1</version>
</dependency>
<!-- /directory validation: --> <!-- logging -->
<dependency>
<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 -->
</dependencies> <dependency>
<build> <groupId>org.junit.jupiter</groupId>
<pluginManagement> <artifactId>junit-jupiter-api</artifactId>
<plugins> <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>
<!-- for directory validation: -->
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-assertj</artifactId>
<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>
<plugins>
<plugin>
<!-- mvn help:effective-pom will otherwise tell it just defaults
to 2.3.2 - which does not release in the maven repo, and neither shows any
error message :-( -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>2.5.3</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin> <plugin>
<!-- mvn help:effective-pom will otherwise tell it just defaults <groupId>org.apache.maven.plugins</groupId>
to 2.3.2 - which does not release in the maven repo, and neither shows any <artifactId>maven-surefire-plugin</artifactId>
error message :-( --> <configuration>
<groupId>org.apache.maven.plugins</groupId> <runOrder>alphabetical</runOrder>
<artifactId>maven-release-plugin</artifactId> </configuration>
<version>2.5.3</version>
</plugin> </plugin>
</plugins> <!-- allow getImplementationVersion for the pom.xml -->
</pluginManagement> <plugin>
<plugins> <groupId>org.apache.maven.plugins</groupId>
<plugin> <artifactId>maven-jar-plugin</artifactId>
<groupId>org.apache.maven.plugins</groupId> <version>3.4.2</version>
<artifactId>maven-surefire-plugin</artifactId> <configuration>
<configuration> <archive>
<runOrder>alphabetical</runOrder> <manifest>
</configuration> <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</plugin> <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
<!-- allow getImplementationVersion for the pom.xml --> </manifest>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</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> </archive>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version> <version>2.3.2</version>
<configuration> <configuration>
<archive> <archive>
<manifest> <manifest>
<addClasspath>true</addClasspath> <addClasspath>true</addClasspath>
<mainClass>org.mustangproject.commandline.main</mainClass> <mainClass>org.mustangproject.commandline.main</mainClass>
</manifest> </manifest>
</archive> </archive>
<descriptorRefs> <descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef> <descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs> </descriptorRefs>
<!-- http://stackoverflow.com/questions/574594/how-can-i-create-an-executable-jar-with-dependencies-using-maven <!-- http://stackoverflow.com/questions/574594/how-can-i-create-an-executable-jar-with-dependencies-using-maven
mvn clean compile assembly:single --> mvn clean compile assembly:single -->
<!-- or whatever version you use --> <!-- or whatever version you use -->
<source>8</source> <source>11</source>
<target>8</target> <target>11</target>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId> <artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version> <version>3.5.3</version>
<configuration> <configuration>
<transformers> <transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.mustangproject.commandline.Main</mainClass> <mainClass>org.mustangproject.commandline.Main</mainClass>
</transformer> </transformer>
</transformers> </transformers>
<minimizeJar>false</minimizeJar> <minimizeJar>false</minimizeJar>
<filters> <filters>
<filter> <filter>
<artifact>*:*</artifact> <artifact>*:*</artifact>
<excludes> <excludes>
<exclude>LICENSE</exclude> <exclude>LICENSE</exclude>
<exclude>NOTICE</exclude> <exclude>NOTICE</exclude>
<exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude> <exclude>META-INF/*.RSA</exclude>
</excludes> </excludes>
</filter> </filter>
<filter> <filter>
<artifact>log4j:log4j</artifact> <artifact>log4j:log4j</artifact>
<includes> <includes>
<include>**</include> <include>**</include>
</includes> </includes>
</filter> </filter>
<filter> <filter>
<artifact>commons-logging:commons-logging</artifact> <artifact>commons-logging:commons-logging</artifact>
<includes> <includes>
<include>**</include> <include>**</include>
</includes> </includes>
</filter> </filter>
<filter> <filter>
<artifact>com.sun.xml.bind:jaxb-impl</artifact> <artifact>com.sun.xml.bind:jaxb-impl</artifact>
<includes> <includes>
<include>**</include> <include>**</include>
</includes> </includes>
</filter> </filter>
</filters> </filters>
</configuration> </configuration>
<executions> <executions>
<execution> <execution>
<phase>package</phase> <phase>package</phase>
<goals> <goals>
<goal>shade</goal> <goal>shade</goal>
</goals> </goals>
<configuration> <configuration>
<artifactSet> <artifactSet>
<excludes /> <excludes />
</artifactSet> </artifactSet>
</configuration> </configuration>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
<profiles> <profiles>
<profile> <!-- enforce building binaries with Java 1.8 for Maven Central, otherwise using them e.g. as jar will <profile> <!-- enforce building binaries with Java 1.8 for Maven Central, otherwise using them e.g. as jar will
throw a version exception, triggered automatically on mvn release:release (hopefully) and requires an according throw a version exception, triggered automatically on mvn release:release (hopefully) and requires an according
~/.m2/toolchains.xml file, @see doc/development_documentation.md --> ~/.m2/toolchains.xml file, @see doc/development_documentation.md -->
<id>release-sign-artifacts</id> <id>release-sign-artifacts</id>
<activation> <activation>
<property> <property>
<name>performRelease</name> <name>performRelease</name>
<value>true</value> <value>true</value>
</property> </property>
</activation> </activation>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-toolchains-plugin</artifactId> <artifactId>maven-toolchains-plugin</artifactId>
<version>3.1.0</version> <version>3.1.0</version>
<executions> <executions>
<execution> <execution>
<goals> <goals>
<goal>toolchain</goal> <goal>toolchain</goal>
</goals> </goals>
</execution> </execution>
</executions> </executions>
<configuration> <configuration>
<toolchains> <toolchains>
<jdk> <jdk>
<version>8</version> <version>11</version>
<vendor>adopt</vendor> <vendor>adopt</vendor>
</jdk> </jdk>
</toolchains> </toolchains>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
</profile> </profile>
</profiles> </profiles>
<licenses> <licenses>
<license> <license>
<name>Apache License, Version 2.0</name> <name>Apache License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution> <distribution>repo</distribution>
<comments>A business-friendly OSS license</comments> <comments>A business-friendly OSS license</comments>
</license> </license>
</licenses> </licenses>
<developers> <developers>
<developer> <developer>
<name>Jochen Stärk</name> <name>Jochen Stärk</name>
<email>jstaerk@usegroup.de</email> <email>jstaerk@usegroup.de</email>
<roles> <roles>
<role>architect</role> <role>architect</role>
<role>developer</role> <role>developer</role>
</roles> </roles>
</developer> </developer>
</developers> </developers>
</project> </project>

View File

@@ -36,64 +36,66 @@ import java.util.ArrayList;
public class Main { public class Main {
private static org.slf4j.Logger LOGGER; // log output private static org.slf4j.Logger LOGGER; // log output
private static void printUsage() { private static void printUsage() {
System.err.println(getUsage()); System.err.println(getUsage());
} }
private static String getUsage() { private static String getUsage() {
return "Usage: --action metrics|combine|extract|a3only|ubl|validate|validateExpectInvalid|validateExpectValid|visualize [-d,--directory] [-l,--listfromstdin] [-i,--ignore fileextension, PDF/A errors] [--disable-file-logging] | [-h,--help] \r\n" return "Usage: --action metrics|combine|extract|a3only|ubl|validate|validateExpectInvalid|validateExpectValid|visualize [-d,--directory] [-l,--listfromstdin] [-i,--ignore fileextension, PDF/A errors] [--disable-file-logging] | [-h,--help] \r\n"
+ " --action license display open source license and notice\n" + " --action license display open source license and notice\n"
+ " --action metrics\n" + " --action metrics\n"
+ " -d, --directory count ZUGFeRD files in directory to be scanned\n" + " -d, --directory count ZUGFeRD files in directory to be scanned\n"
+ " If it is a directory, it will recurse.\n" + " If it is a directory, it will recurse.\n"
+ " -l, --listfromstdin count ZUGFeRD files from a list of linefeed separated files on runtime.\n" + " -l, --listfromstdin count ZUGFeRD files from a list of linefeed separated files on runtime.\n"
+ " It will start once a blank line has been entered.\n" + "\n" + " It will start once a blank line has been entered.\n" + "\n"
+ " Additional parameter for both count operations\n" + " Additional parameter for both count operations\n"
+ " [-i, --ignorefileextension] Check for all files (*.*) instead of PDF files only (*.pdf) in metrics, ignore PDF/A input file errors in combine\n" + " [-i, --ignorefileextension] Check for all files (*.*) instead of PDF files only (*.pdf) in metrics, ignore PDF/A input file errors in combine\n"
+ " [--disable-file-logging] disable logging to file.\n" + " [--disable-file-logging] disable logging to file.\n"
+ " --action extract extract Factur-X PDF to XML file\n" + " --action extract extract Factur-X PDF to XML file\n"
+ " Additional parameters (optional - user will be prompted if not defined)\n" + " Additional parameters (optional - user will be prompted if not defined)\n"
+ " [--source <filename>]: set input PDF file\n" + " [--source <filename>]: set input PDF file\n"
+ " [--out <filename>]: set output XML file\n" + " [--out <filename>]: set output XML file\n"
+ " --action a3only upgrade from PDF/A1 to A3 only (no ZUGFeRD data attached)\n" + " --action a3only upgrade from PDF/A1 to A3 only (no ZUGFeRD data attached)\n"
+ " Additional parameters (optional - user will be prompted if not defined)\n" + " Additional parameters (optional - user will be prompted if not defined)\n"
+ " [--source <filename>]: set input PDF file\n" + " [--source <filename>]: set input PDF file\n"
+ " [--out <filename>]: set output PDF file\n" + " [--out <filename>]: set output PDF file\n"
+ " --action combine combine XML and PDF file to Factur-X PDF file\n" + " --action combine combine XML and PDF file to Factur-X PDF file\n"
+ " Additional parameters (optional - user will be prompted if not defined)\n" + " Additional parameters (optional - user will be prompted if not defined)\n"
+ " [--source <filename>]: set input PDF file\n" + " [--source <filename>]: set input PDF file\n"
+ " [--source-xml <filename>]: set input XML file\n" + " [--source-xml <filename>]: set input XML file\n"
+ " [--out <filename>]: set output PDF file\n" + " [--out <filename>]: set output PDF file\n"
+ " [--format <fx|zf|ox|da>]: set Factur-X, ZUGFeRD, Order-X or Cross Industry Despatch Advice\n" + " [--format <fx|zf|ox|da>]: set Factur-X, ZUGFeRD, Order-X or Cross Industry Despatch Advice\n"
+ " [--version <1|2>]: set ZUGFeRD version\n" + " [--version <1|2>]: set ZUGFeRD version\n"
+ " [--profile <...>]: set ZUGFeRD profile\n" + " [--profile <...>]: set ZUGFeRD profile\n"
+ " For ZUGFeRD v1 or Order-X: <B>ASIC, <C>OMFORT or EX<T>ENDED\n" + " For ZUGFeRD v1 or Order-X: <B>ASIC, <C>OMFORT or EX<T>ENDED\n"
+ " For ZUGFeRD v2: <M>INIMUM, BASIC <W>L, <B>ASIC, <C>IUS, <E>N16931, <X>Rechnung, EX<T>ENDED\n" + " For ZUGFeRD v2: <M>INIMUM, BASIC <W>L, <B>ASIC, <C>IUS, <E>N16931, <X>Rechnung, EX<T>ENDED\n"
+ " [--attachments <filenames>]: list of file attachments (passing a single empty file name prevents prompting)\n" + " [--attachments <filenames>]: list of file attachments (passing a single empty file name prevents prompting)\n"
+ " --action ubl convert UN/CEFACT 2016b CII XML to UBL XML\n" + " [--no-additional-attachments]: prevent prompting for attachments\n"
+ " [--source <filename>]: set input XML file\n" + " --action ubl convert UN/CEFACT 2016b CII XML to UBL XML\n"
+ " [--out <filename>]: set output XML file\n" + " [--source <filename>]: set input XML file\n"
+ " --action upgrade upgrade ZUGFeRD XML to ZUGFeRD 2 XML\n" + " [--out <filename>]: set output XML file\n"
+ " Additional parameters (optional - user will be prompted if not defined)\n" + " --action upgrade upgrade ZUGFeRD XML to ZUGFeRD 2 XML\n"
+ " [--source <filename>]: set input XML ZUGFeRD 1 file\n" + " Additional parameters (optional - user will be prompted if not defined)\n"
+ " [--out <filename>]: set output XML ZUGFeRD 2 file\n" + " [--source <filename>]: set input XML ZUGFeRD 1 file\n"
+ " --action validate validate XML or PDF file \n" + " [--out <filename>]: set output XML ZUGFeRD 2 file\n"
+ " [--no-notices]: refrain from reporting notices\n" + " --action validate validate XML or PDF file \n"
+ " [--logAppend <text>]: text to be added to log line\n" + " [--no-notices]: refrain from reporting notices\n"
+ " Additional parameters (optional - user will be prompted if not defined)\n" + " [--logAppend <text>]: text to be added to log line\n"
+ " [--source <filename>]: input PDF or XML file\n" + " Additional parameters (optional - user will be prompted if not defined)\n"
+ " --action validateExpectInvalid validate directory expecting negative results \n" + " [--source <filename>]: input PDF or XML file\n"
+ " [--no-notices]: refrain from reporting notices\n" + " --action validateExpectInvalid validate directory expecting negative results \n"
+ " Additional parameters (optional - user will be prompted if not defined)\n" + " [--no-notices]: refrain from reporting notices\n"
+ " -d, --directory to check recursively\n" + " Additional parameters (optional - user will be prompted if not defined)\n"
+ " --action validateExpectValid validate directory expecting positive results \n" + " -d, --directory to check recursively\n"
+ " [--no-notices]: refrain from reporting notices\n" + " --action validateExpectValid validate directory expecting positive results \n"
+ " Additional parameters (optional - user will be prompted if not defined)\n" + " [--no-notices]: refrain from reporting notices\n"
+ " -d, --directory to check recursively \n" + " Additional parameters (optional - user will be prompted if not defined)\n"
+ " --action visualize convert XML to HTML \n" + " -d, --directory to check recursively \n"
+ " [--language <lang>]: set output lang (en, fr or de)\n" + " --action visualize convert XML to HTML \n"
+ " [--source <filename>]: set input XML file\n" + " [--language <lang>]: set output lang (en, fr or de)\n"
+ " [--out <filename>]: set output HTML file\n" + " [--source <filename>]: set input XML file\n"
+ " [--out <filename>]: set output HTML file\n"
+ " --action pdf convert XML to PDF \n" + " --action pdf convert XML to PDF \n"
+ " [--source <filename>]: set input XML file\n" + " [--source <filename>]: set input XML file\n"
+ " [--out <filename>]: set output PDF file\n" + " [--out <filename>]: set output PDF file\n"
@@ -330,32 +332,36 @@ public class Main {
public static void main(String[] args) { public static void main(String[] args) {
try { try {
CommandLine cmd; CommandLine cmd;
CommandLineParser parser = new BasicParser(); CommandLineParser parser = new DefaultParser();
// create Options object // create Options object
Options options = new Options(); Options options = new Options();
options.addOption(new Option("h", "help", false, "display usage")); options.addOption(new Option("h", "help", false, "display usage"));
options.addOption(new Option("a", "action", true, "which action to perform")); 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("f", "format", true, "which format to output"));
options.addOption(new Option("", "version", true, "which version of the standard to use")); options.addOption(new Option("version", "version", true, "which version of the standard to use"));
options.addOption(new Option("", "profile", true, "which profile of the standard to use")); options.addOption(new Option("profile", "profile", true, "which profile of the standard to use"));
Option attachmentOpt = new Option("", "attachments", true, "File attachments"); options.addOption(new Option("no-additional-attachments", "no-additional-attachments", false, "prevent prompting for attachments"));
Option attachmentOpt = new Option("attachments", "attachments", true, "File attachments");
attachmentOpt.setValueSeparator(','); attachmentOpt.setValueSeparator(',');
attachmentOpt.setArgs(Option.UNLIMITED_VALUES); attachmentOpt.setArgs(Option.UNLIMITED_VALUES);
options.addOption(attachmentOpt); options.addOption(attachmentOpt);
options.addOption(new Option("", "source",true, "which source file to use")); options.addOption(new Option("source", "source", true, "which source file to use"));
options.addOption(new Option("", "source-xml",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",true, "output language (en, de or fr)")); options.addOption(new Option("language", "language", true, "output language (en, de or fr)"));
options.addOption(new Option("", "out",true, "which output file to write to")); options.addOption(new Option("out", "out", true, "which output file to write to"));
options.addOption(new Option("", "no-notices",false, "suppress non-fatal errors")); options.addOption(new Option("no-notices", "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("logAppend", "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("disable-file-logging", "disable-file-logging", false, "suppress logging to file"));
options.addOption(new Option("d", "directory",true, "which directory to operate on")); options.addOption(new Option("d", "directory", true, "which directory to operate on"));
options.addOption(new Option("i", "ignorefileextension",false, "ignore non-matching file extensions")); options.addOption(new Option("i", "ignorefileextension", false, "ignore non-matching file extensions"));
options.addOption(new Option("l", "listfromstdin",false, "take list of files from commandline")); options.addOption(new Option("l", "listfromstdin", false, "take list of files from commandline"));
boolean optionsRecognized=false;
boolean optionsRecognized = false;
String action = ""; String action = "";
Boolean disableFileLogging = false;
Boolean disableFileLogging = false;
try { try {
cmd = parser.parse(options, args); cmd = parser.parse(options, args);
@@ -364,7 +370,8 @@ public class Main {
String directoryName = cmd.getOptionValue("directory"); String directoryName = cmd.getOptionValue("directory");
Boolean filesFromStdIn = cmd.hasOption("listfromstdin");//((Number)cmdLine.getParsedOptionValue("integer-option")).intValue(); Boolean filesFromStdIn = cmd.hasOption("listfromstdin");//((Number)cmdLine.getParsedOptionValue("integer-option")).intValue();
Boolean ignoreFileExt = cmd.hasOption("ignorefileextension"); Boolean ignoreFileExt = cmd.hasOption("ignorefileextension");
Boolean helpRequested = cmd.hasOption("help") || ((action!=null)&&(action.equals("help"))); Boolean noAttachments = cmd.hasOption("no-additional-attachments");
Boolean helpRequested = cmd.hasOption("help") || ((action != null) && (action.equals("help")));
disableFileLogging = cmd.hasOption("disable-file-logging"); disableFileLogging = cmd.hasOption("disable-file-logging");
String sourceName = cmd.getOptionValue("source"); String sourceName = cmd.getOptionValue("source");
@@ -380,14 +387,12 @@ public class Main {
String[] attachmentFilenames = cmd.hasOption("attachments") ? cmd.getOptionValues("attachments") : null; String[] attachmentFilenames = cmd.hasOption("attachments") ? cmd.getOptionValues("attachments") : null;
ArrayList<FileAttachment> attachments = new ArrayList<>();
ArrayList<FileAttachment> attachments=new ArrayList <>();
/* /*
setting system property to disable FILE appender and setting system property to disable FILE appender and
suppress creation of files and folders. suppress creation of files and folders.
*/ */
System.setProperty("FILE_APPENDER_ENABLED", ((Boolean)(disableFileLogging==false)).toString()); System.setProperty("FILE_APPENDER_ENABLED", ((Boolean) (disableFileLogging == false)).toString());
LOGGER = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); LOGGER = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
@@ -401,7 +406,7 @@ public class Main {
performMetrics(directoryName, filesFromStdIn, ignoreFileExt); performMetrics(directoryName, filesFromStdIn, ignoreFileExt);
optionsRecognized = true; optionsRecognized = true;
} else if ((action != null) && (action.equals("combine"))) { } else if ((action != null) && (action.equals("combine"))) {
performCombine(sourceName, sourceXMLName, outName, format, zugferdVersion, zugferdProfile, ignoreFileExt, attachmentFilenames, attachments); performCombine(sourceName, sourceXMLName, outName, format, zugferdVersion, zugferdProfile, ignoreFileExt, attachmentFilenames, attachments, noAttachments);
optionsRecognized = true; optionsRecognized = true;
} else if ((action != null) && (action.equals("extract"))) { } else if ((action != null) && (action.equals("extract"))) {
performExtract(sourceName, outName); performExtract(sourceName, outName);
@@ -422,7 +427,7 @@ public class Main {
performUBL(sourceName, outName); performUBL(sourceName, outName);
optionsRecognized = true; optionsRecognized = true;
} else if ((action != null) && (action.equals("validate"))) { } else if ((action != null) && (action.equals("validate"))) {
optionsRecognized = performValidate(sourceName, noNotices != null && noNotices, cmd.getOptionValue("logAppend")); optionsRecognized = performValidate(sourceName, noNotices, cmd.getOptionValue("logAppend"));
} else if ((action != null) && (action.equals("validateExpectValid"))) { } else if ((action != null) && (action.equals("validateExpectValid"))) {
optionsRecognized = performValidateExpect(true, directoryName); optionsRecognized = performValidateExpect(true, directoryName);
} else if ((action != null) && (action.equals("validateExpectInvalid"))) { } else if ((action != null) && (action.equals("validateExpectInvalid"))) {
@@ -439,7 +444,11 @@ public class Main {
System.exit(2); System.exit(2);
} }
} catch (Exception e) { } catch (Exception e) {
LOGGER.error(e.getMessage(), e); if (LOGGER != null) {
LOGGER.error(e.getMessage(), e);
} else {
System.err.println(e.getMessage());
}
System.exit(-1); System.exit(-1);
} }
@@ -510,10 +519,11 @@ public class Main {
ensureFileNotExists(outName); ensureFileNotExists(outName);
// All params are good! continue... // All params are good! continue...
ZUGFeRDExporterFromA1 ze = new ZUGFeRDExporterFromA1().convertOnly().load(pdfName); try (ZUGFeRDExporterFromA1 ze = new ZUGFeRDExporterFromA1()) {
ze.convertOnly().load(pdfName);
ze.export(outName); ze.export(outName);
System.out.println("Written to " + outName); System.out.println("Written to " + outName);
}
} }
private static void performExtract(String pdfName, String xmlName) throws IOException { private static void performExtract(String pdfName, String xmlName) throws IOException {
@@ -546,7 +556,7 @@ public class Main {
} }
private static void performCombine(String pdfName, String xmlName, String outName, String format, String zfVersion, private static void performCombine(String pdfName, String xmlName, String outName, String format, String zfVersion,
String zfProfile, Boolean ignoreInputErrors, String[] attachmentFilenames, ArrayList<FileAttachment> attachments) throws Exception { String zfProfile, Boolean ignoreInputErrors, String[] attachmentFilenames, ArrayList<FileAttachment> attachments, Boolean noAttachments) throws Exception {
/* /*
* ZUGFeRDExporter ze= new ZUGFeRDExporterFromA1Factory() * ZUGFeRDExporter ze= new ZUGFeRDExporterFromA1Factory()
* .setProducer("toecount") .setCreator(System.getProperty("user.name")) * .setProducer("toecount") .setCreator(System.getProperty("user.name"))
@@ -576,12 +586,14 @@ public class Main {
if (attachmentFilenames == null) { if (attachmentFilenames == null) {
byte attachmentContents[] = null; byte attachmentContents[] = null;
String attachmentFilename, attachmentMime, attachmentDescription; String attachmentFilename, attachmentMime;
attachmentFilename = getFilenameFromUser("Additional file attachments filename (empty for none)", "", "pdf", true, false); if (!noAttachments) {
if (attachmentFilename.length() != 0) { attachmentFilename = getFilenameFromUser("Additional file attachments filename (empty for none)", "", "pdf", true, false);
attachmentContents = Files.readAllBytes(Paths.get(attachmentFilename)); if (attachmentFilename.length() != 0) {
attachmentMime = Files.probeContentType(Paths.get(attachmentFilename)); attachmentContents = Files.readAllBytes(Paths.get(attachmentFilename));
attachments.add(new FileAttachment(attachmentFilename, attachmentMime, "Data", attachmentContents)); attachmentMime = Files.probeContentType(Paths.get(attachmentFilename));
attachments.add(new FileAttachment(attachmentFilename, attachmentMime, "Data", attachmentContents));
}
} }
} else { } else {
for (int i = 0; i < attachmentFilenames.length; i++) { for (int i = 0; i < attachmentFilenames.length; i++) {
@@ -597,7 +609,7 @@ public class Main {
if (format == null) { if (format == null) {
try { 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) { } catch (Exception e) {
LOGGER.error(e.getMessage(), e); LOGGER.error(e.getMessage(), e);
} }
@@ -607,7 +619,7 @@ public class Main {
if (zfVersion == null) { if (zfVersion == null) {
try { 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) { } catch (Exception e) {
LOGGER.error(e.getMessage(), e); LOGGER.error(e.getMessage(), e);
} }
@@ -691,53 +703,49 @@ public class Main {
IZUGFeRDExporter ze = null; IZUGFeRDExporter ze = null;
// All params are good! continue... // All params are good! continue...
if (format.equals("ox")) { if (format.equals("ox")) {
ze = new OXExporterFromA1().setProducer("Mustang-cli") ze = new OXExporterFromA1();
.setZUGFeRDVersion(zfIntVersion)
.setCreator(System.getProperty("user.name")).setProfile(zfConformanceLevelProfile);
if (ignoreInputErrors) { if (ignoreInputErrors) {
((OXExporterFromA1) ze).ignorePDFAErrors(); ((OXExporterFromA1) ze).ignorePDFAErrors();
} }
} else if (format.equals("da")) { } else if (format.equals("da")) {
ze = new DXExporterFromA1().setProducer("Mustang-cli") ze = new DXExporterFromA1();
.setZUGFeRDVersion(zfIntVersion)
.setCreator(System.getProperty("user.name")).setProfile(zfConformanceLevelProfile);
if (ignoreInputErrors) { if (ignoreInputErrors) {
((DXExporterFromA1) ze).ignorePDFAErrors(); ((DXExporterFromA1) ze).ignorePDFAErrors();
} }
} else { } else {
if (format.equals("fx")) { if (format.equals("fx")) {
zfIntVersion=2;// actually we are talking of generation, not version zfIntVersion = 2;// actually we are talking of generation, not version
// so even if someone correctly requested factur-x 1 internally we call it zugferd 2 :-( // so even if someone correctly requested factur-x 1 internally we call it zugferd 2 :-(
} }
ze = new ZUGFeRDExporterFromPDFA().load(pdfName); ze = new ZUGFeRDExporterFromPDFA();
ze.setProducer("Mustang-cli")
.setZUGFeRDVersion(zfIntVersion)
.setCreator(System.getProperty("user.name")).setProfile(zfConformanceLevelProfile);
if (ignoreInputErrors) { if (ignoreInputErrors) {
((ZUGFeRDExporterFromA1) ze).ignorePDFAErrors(); ((ZUGFeRDExporterFromPDFA) ze).ignorePDFAErrors();
} }
} }
for (FileAttachment attachment : attachments) {
((ZUGFeRDExporterFromA3) ze).attachFile(attachment.getFilename(), attachment.getData(), attachment.getMimetype(), attachment.getRelation());
}
// ze = ze.load(pdfName); ze.load(pdfName);
ze.setProducer("Mustang-cli")
.setZUGFeRDVersion(zfIntVersion)
.setCreator(System.getProperty("user.name")).setProfile(zfConformanceLevelProfile);
if (format.equals("zf")) { if (format.equals("zf")) {
ze.disableFacturX(); ze.disableFacturX();
} }
for (FileAttachment attachment : attachments) {
ze.attachFile(attachment.getFilename(), attachment.getData(), attachment.getMimetype(), attachment.getRelation());
}
ze.setXML(Files.readAllBytes(Paths.get(xmlName))); ze.setXML(Files.readAllBytes(Paths.get(xmlName)));
ze.export(outName); ze.export(outName);
System.out.println("Written to " + outName); System.out.println("Written to " + outName);
} catch (Exception e) { } catch (Exception e) {
LOGGER.error(e.getMessage(), e); if (LOGGER != null) {
LOGGER.error(e.getMessage(), e);
}
System.err.println(e.getMessage()); System.err.println(e.getMessage());
} }
} }
@@ -837,14 +845,9 @@ public class Main {
} else { } else {
zvi.toPDF(sourceName, outName); zvi.toPDF(sourceName, outName);
} }
} catch (FileNotFoundException e) { } catch (Exception 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) {
LOGGER.error(e.getMessage(), e); LOGGER.error(e.getMessage(), e);
System.err.println(e.getMessage());
} }
System.out.println("Written to " + outName); 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 * @throws Exception e.g. if the specified resource does not exist at the specified location
*/ */
static public String ExportResource(String resourceName) throws Exception { static public String ExportResource(String resourceName) throws Exception {
InputStream stream = null;
OutputStream resStreamOut = null;
String jarFolder; String jarFolder;
try { 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
stream = Main.class.getResourceAsStream(resourceName);//note that each / is a directory down in the "jar tree" been the jar the root of the tree
if (stream == null) { if (stream == null) {
throw new Exception("Cannot get resource \"" + resourceName + "\" from Jar file."); throw new Exception("Cannot get resource \"" + resourceName + "\" from Jar file.");
} }
@@ -883,15 +883,11 @@ public class Main {
int readBytes; int readBytes;
byte[] buffer = new byte[4096]; byte[] buffer = new byte[4096];
jarFolder = System.getProperty("user.dir"); jarFolder = System.getProperty("user.dir");
resStreamOut = new FileOutputStream(jarFolder + resourceName); try (FileOutputStream resStreamOut = new FileOutputStream(jarFolder + resourceName)) {
while ((readBytes = stream.read(buffer)) > 0) { while ((readBytes = stream.read(buffer)) > 0) {
resStreamOut.write(buffer, 0, readBytes); resStreamOut.write(buffer, 0, readBytes);
}
} }
} catch (Exception ex) {
throw ex;
} finally {
stream.close();
resStreamOut.close();
} }
return jarFolder + resourceName; return jarFolder + resourceName;
@@ -913,7 +909,8 @@ public class Main {
if (fileName == null) if (fileName == null)
return false; return false;
File f = new File(fileName); File f = new File(fileName);
return f.exists(); // "exists" also returns true for directories
return f.isFile();
} }
} }

View File

@@ -14,9 +14,6 @@ import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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 org.mustangproject.validator.ZUGFeRDValidator;
import static org.xmlunit.assertj.XmlAssert.assertThat; import static org.xmlunit.assertj.XmlAssert.assertThat;
@@ -50,25 +47,23 @@ public class ValidatorFileWalker
Date date = new Date(); Date date = new Date();
String expectedString="valid"; String expectedString="valid";
if (!expectValid) { if (!expectValid) {
expectedString="invalid"; expectedString="invalid";
} }
if ((attr!=null)&&(attr.isRegularFile())) { if ((attr!=null)&&(attr.isRegularFile())) {
if (matcher.matches(file.getFileName())) { if (matcher.matches(file.getFileName())) {
boolean thisResultValid=true;
String thisResultString=" valid"; String thisResultString=" valid";
try { try {
assertThat(zul.validate(file.toAbsolutePath().toString())).valueByXPath("/validation/summary/@status") assertThat(zul.validate(file.toAbsolutePath().toString())).valueByXPath("/validation/summary/@status")
.asString() .asString()
.isEqualTo(expectedString); .isEqualTo(expectedString);
} catch (AssertionError ae) { } catch (AssertionError ae) {
thisResultValid=false; thisResultString="invalid";
thisResultString="invalid"; allValid=false;
allValid=false; }
} LOGGER.info(String.format("\n@%s Testing file %d: %s (%s)", dateFormat.format(date), fileCount++, thisResultString, file));
LOGGER.info(String.format("\n@%s Testing file %d: %s (%s)", dateFormat.format(date), fileCount++, thisResultString, file));
}
}
} }
return FileVisitResult.CONTINUE; return FileVisitResult.CONTINUE;
} }

View File

@@ -1,8 +1,6 @@
package org.mustangproject.commandline; package org.mustangproject.commandline;
import java.io.BufferedReader; import java.io.*;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@@ -10,18 +8,43 @@ import java.nio.file.Paths;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
public class CliIT { 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 @Test
public void testCii2Ubl() throws Exception { public void testCii2Ubl() throws Exception {
Path output = Paths.get("target/ubl.xml"); Path output = Paths.get("target/ubl.xml");
Files.deleteIfExists(output); Files.deleteIfExists(output);
Path jar = Files.newDirectoryStream(Paths.get("target"), "Mustang-CLI-*.jar").iterator().next(); Path jar = Files.newDirectoryStream(Paths.get("target"), "Mustang-CLI-*.jar").iterator().next();
ProcessBuilder pb = new ProcessBuilder("java", "-jar", jar.toString(), ProcessBuilder pb = new ProcessBuilder("java", "-jar", jar.toString(),
"--action", "ubl", "--source", "src/test/resources/cii.xml", "--out", "--action", "ubl", "--source", "src/test/resources/cii.xml", "--out",
output.toString()); output.toString());
pb.redirectErrorStream(true); pb.redirectErrorStream(true);
Process process = pb.start(); Process process = pb.start();
String result = getOutput(process); String result = getOutput(process);
@@ -43,4 +66,17 @@ public class CliIT {
return builder.toString(); 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());
}
} }

View File

@@ -0,0 +1,2 @@
%PDF-1.4
%ェォャュ

View File

@@ -34,14 +34,11 @@ If you set up a Maven project, you can reference the mustang artifact like this:
<dependency> <dependency>
<groupId>org.mustangproject</groupId> <groupId>org.mustangproject</groupId>
<artifactId>library</artifactId> <artifactId>library</artifactId>
<version>2.11.0</version> <version>2.14.0</version>
</dependency> </dependency>
``` ```
Further docs on how to develop **with** mustang: 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. - [Usage examples](https://www.mustangproject.org/use/): Read and write electronic invoices.
- [Mustang classes](https://www.mustangproject.org/invoice-class/): Using the mustang classes. - [Mustang classes](https://www.mustangproject.org/invoice-class/): Using the mustang classes.

View File

@@ -20,13 +20,13 @@ Additionally, the Mustang validator now also supports XRechnung 2, can now also
### Use on command line ### 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. to for non-interactive (i.e., batch) processing.
@@ -37,12 +37,13 @@ from `-f` (ZUV) to the usual `--source`.
We're now on maven central, please remove the old github repository. Additionally, the following changed We're now on maven central, please remove the old github repository. Additionally, the following changed
| What | old value | new value | | What | old value | new value |
|---|---|---| |---|---|--------------------|
| Group id | org.mustangproject.zugferd | org.mustangproject| | Group id | org.mustangproject.zugferd | org.mustangproject |
| Artifact ID | mustang | library | | 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 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 as artifact ID. "validator" includes the library functionality but is >20 MB
bigger due to its dependencies. bigger due to its dependencies.
@@ -200,7 +201,7 @@ read/write ZUGFeRD-invoices like the (smaller) library module.
<dependency> <dependency>
<groupId>org.mustangproject</groupId> <groupId>org.mustangproject</groupId>
<artifactId>validator</artifactId> <artifactId>validator</artifactId>
<version>2.0.0</version> <version>2.14.0</version>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -1 +0,0 @@
* xref:index.adoc[]

View File

@@ -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[]`.

View File

@@ -1 +0,0 @@
* xref:overview.adoc[]

View File

@@ -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[]`.

View File

@@ -1,11 +1,10 @@
## General approach ## Typical process
1. build 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 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. 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 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 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 ## Architecture
Mustang contains a library to read/write e-invoices, Mustang contains a library to read/write e-invoices,
@@ -48,7 +59,7 @@ to validate the XML part of the invoices.
## New build ## New build
Target platform is java 1.8 Target platform is java 1.17
## Build ## Build
@@ -115,14 +126,10 @@ The whole settings.xml then looks e.g. like this
<offline/> <offline/>
<pluginGroups/> <pluginGroups/>
<servers> <servers>
<server>
      <id>github</id>
      <password>TOKEN</password>
    </server>
    <server>     <server>
      <id>ossrh</id>       <id>ossrh</id>
      <username>jstaerk</username>       <username>SONATYPE TOKEN USER</username>
      <password>JIRA-PASSWORD</password>       <password>SONATYPE TOKEN PASSWORD</password>
    </server>     </server>
</servers> </servers>
@@ -150,21 +157,21 @@ Sign in in GitHub and click on the profile picture -> Settings. Now just generat
![screenshot](development_documentation_screenshot_github_settings.png "Screenshot Github Settings") ![screenshot](development_documentation_screenshot_github_settings.png "Screenshot Github Settings")
The Token-ID is the password. 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
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<toolchains> <toolchains>
<!-- JDK toolchains --> <!-- JDK toolchains -->
<toolchain> <toolchain>
<type>jdk</type> <type>jdk</type>
<provides> <provides>
<version>8</version> <version>11</version>
<vendor>adopt</vendor> <vendor>adopt</vendor>
</provides> </provides>
<configuration> <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> </configuration>
</toolchain> </toolchain>
</toolchains> </toolchains>
``` ```
@@ -177,11 +184,11 @@ maybe not yet even existing new release version:
``` ```
cd validator/target 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 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 * freshcode.club
* Submit on openpr.de/.com, https://www.einpresswire.com/ * Submit on openpr.de/.com, https://www.einpresswire.com/
## Tips&amp;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
View 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>

View File

@@ -1,345 +1,341 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent> <parent>
<groupId>org.mustangproject</groupId>
<artifactId>core</artifactId>
<version>2.15.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.mustangproject</groupId> <groupId>org.mustangproject</groupId>
<artifactId>core</artifactId> <artifactId>library</artifactId>
<version>2.10.1-SNAPSHOT</version> <version>2.15.0-SNAPSHOT</version>
</parent> <packaging>jar</packaging>
<name>Library to write, read and validate e-invoices (Factur-X, ZUGFeRD, Order-X, XRechnung/CII)</name>
<modelVersion>4.0.0</modelVersion> <description>FOSS Java library to read, write and validate european electronic invoices and orders in the UN/CEFACT
<groupId>org.mustangproject</groupId> Cross Industry Invoice based formats Factur-X/ZUGFeRD, XRechnung and Order-X in your invoice PDFs.
<artifactId>library</artifactId>
<version>2.10.1-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> </description>
<url>http://www.mustangproject.org/</url> <url>http://www.mustangproject.org/</url>
<scm> <scm>
<connection>scm:git:https://github.com/ZUGFeRD/mustangproject.git</connection> <connection>scm:git:https://github.com/ZUGFeRD/mustangproject.git</connection>
<developerConnection>scm:git:https://github.com/ZUGFeRD/mustangproject.git</developerConnection> <developerConnection>scm:git:https://github.com/ZUGFeRD/mustangproject.git</developerConnection>
<url>https://github.com/ZUGFeRD/mustangproject</url> <url>https://github.com/ZUGFeRD/mustangproject</url>
<tag>core-2.3.2</tag> <tag>core-2.3.2</tag>
</scm> </scm>
<repositories> <repositories>
<repository><!-- for jargs --> <repository><!-- for jargs -->
<id>sonatype-oss-public</id> <id>sonatype-oss-public</id>
<url>https://oss.sonatype.org/content/groups/public/</url> <url>https://oss.sonatype.org/content/groups/public/</url>
<releases> <releases>
<enabled>true</enabled> <enabled>true</enabled>
</releases> </releases>
<snapshots> <snapshots>
<enabled>true</enabled> <enabled>true</enabled>
</snapshots> </snapshots>
</repository> </repository>
</repositories> </repositories>
<distributionManagement> <distributionManagement>
<repository> <repository>
<id>internal.repo</id> <id>internal.repo</id>
<name>Temporary Staging Repository</name> <name>Temporary Staging Repository</name>
<url>file://${project.build.directory}/mvn-repo</url> <url>file://${project.build.directory}/mvn-repo</url>
</repository> </repository>
</distributionManagement> </distributionManagement>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<github.global.server>github</github.global.server> <github.global.server>github</github.global.server>
<additionalparam>-Xdoclint:none</additionalparam> <additionalparam>-Xdoclint:none</additionalparam>
<!-- Skip error check for javadoc --> <!-- Skip error check for javadoc -->
<maven.compiler.compilerVersion>8</maven.compiler.compilerVersion> <maven.compiler.compilerVersion>11</maven.compiler.compilerVersion>
<maven.compiler.source>8</maven.compiler.source> <maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target> <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 --> <maven.deploy.skip>true
</maven.deploy.skip><!-- do deploy to maven central, parent project does not and inherits -->
</properties> </properties>
<dependencies> <dependencies>
<!-- https://mvnrepository.com/artifact/net.sf.saxon/Saxon-HE --> <dependency>
<dependency> <groupId>org.slf4j</groupId>
<groupId>net.sf.saxon</groupId> <artifactId>slf4j-api</artifactId>
<artifactId>Saxon-HE</artifactId> <version>2.0.9</version>
<version>9.9.0-1</version> </dependency>
</dependency><!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind --> <!-- https://mvnrepository.com/artifact/net.sf.saxon/Saxon-HE -->
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>net.sf.saxon</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>Saxon-HE</artifactId>
<version>2.14.2</version> <version>12.4</version>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.apache.xmlgraphics/fop --> <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency> <dependency>
<groupId>org.apache.xmlgraphics</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>fop</artifactId> <artifactId>jackson-databind</artifactId>
<version>2.9</version> <version>2.17.1</version>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.apache.xmlgraphics/fop -->
<dependency> <dependency>
<groupId>javax.activation</groupId> <groupId>org.apache.xmlgraphics</groupId>
<artifactId>activation</artifactId> <artifactId>fop</artifactId>
<version>1.1.1</version> <version>2.10</version>
</dependency> <exclusions>
<exclusion>
<dependency> <groupId>xml-apis</groupId>
<groupId>org.apache.pdfbox</groupId> <artifactId>xml-apis</artifactId>
<artifactId>preflight</artifactId> </exclusion>
<version>2.0.27</version> </exclusions>
</dependency> </dependency>
<dependency> <!-- JAXB -->
<groupId>org.apache.pdfbox</groupId> <dependency>
<artifactId>pdfbox</artifactId> <groupId>jakarta.xml.bind</groupId>
<version>2.0.27</version> <artifactId>jakarta.xml.bind-api</artifactId>
</dependency> <version>4.0.2</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>
</dependency>
<!-- https://mvnrepository.com/artifact/org.glassfish.jaxb/jaxb-runtime --> <!-- https://mvnrepository.com/artifact/org.glassfish.jaxb/jaxb-runtime -->
<dependency> <dependency>
<groupId>org.glassfish.jaxb</groupId> <groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId> <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> </dependency>
<dependency> <!-- test dependencies -->
<groupId>org.xmlunit</groupId> <dependency>
<artifactId>xmlunit-core</artifactId> <groupId>org.junit.jupiter</groupId>
<version>2.10.0</version> <artifactId>junit-jupiter-api</artifactId>
<scope>test</scope> <version>5.10.2</version>
</dependency>
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-assertj</artifactId>
<version>2.9.1</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- API --> <dependency>
<dependency> <groupId>org.junit.vintage</groupId>
<groupId>jakarta.xml.bind</groupId> <artifactId>junit-vintage-engine</artifactId>
<artifactId>jakarta.xml.bind-api</artifactId> <version>5.10.2</version>
<version>2.3.3</version> <scope>test</scope>
</dependency> </dependency>
<dependency>
</dependencies> <groupId>org.xmlunit</groupId>
<build> <artifactId>xmlunit-core</artifactId>
<plugins> <version>2.10.0</version>
<plugin> <scope>test</scope>
<groupId>org.apache.maven.plugins</groupId> </dependency>
<artifactId>maven-surefire-plugin</artifactId> <dependency>
<configuration> <groupId>org.xmlunit</groupId>
<runOrder>alphabetical</runOrder> <artifactId>xmlunit-assertj</artifactId>
</configuration> <version>2.10.0</version>
</plugin> <scope>test</scope>
<plugin> </dependency>
<groupId>org.apache.maven.plugins</groupId> <dependency>
<artifactId>maven-compiler-plugin</artifactId> <groupId>org.slf4j</groupId>
<version>3.6.1</version> <artifactId>slf4j-simple</artifactId>
<configuration> <version>2.0.9</version>
<descriptorRefs> <scope>test</scope>
<descriptorRef>jar-with-dependencies</descriptorRef> </dependency>
</descriptorRefs> </dependencies>
<!-- http://stackoverflow.com/questions/574594/how-can-i-create-an-executable-jar-with-dependencies-using-maven <build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<runOrder>alphabetical</runOrder>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<!-- http://stackoverflow.com/questions/574594/how-can-i-create-an-executable-jar-with-dependencies-using-maven
mvn clean compile assembly:single --> mvn clean compile assembly:single -->
<!-- or whatever version you use --> <!-- or whatever version you use -->
<source>8</source> <source>11</source>
<target>8</target> <target>11</target>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId> <artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version> <version>3.2.0</version>
<configuration> <configuration>
<excludes> <excludes>
<exclude>**/logback.xml</exclude> <exclude>**/logback.xml</exclude>
</excludes> </excludes>
<archive> <archive>
<manifest> <manifest>
<addClasspath>true</addClasspath> <addClasspath>true</addClasspath>
</manifest> </manifest>
<manifestSections> <manifestSections>
<manifestSection> <manifestSection>
<name>FreeSans.ttf</name> <name>FreeSans.ttf</name>
<manifestEntries> <manifestEntries>
<Content-Type>font/ttf</Content-Type> <Content-Type>font/ttf</Content-Type>
</manifestEntries> </manifestEntries>
</manifestSection> </manifestSection>
<manifestSection> <manifestSection>
<name>FreeSerif.ttf</name> <name>FreeSerif.ttf</name>
<manifestEntries> <manifestEntries>
<Content-Type>application/x-font</Content-Type> <Content-Type>application/x-font</Content-Type>
</manifestEntries> </manifestEntries>
</manifestSection> </manifestSection>
<manifestSection> <manifestSection>
<name>Times-Bold.ttf</name> <name>Times-Bold.ttf</name>
<manifestEntries> <manifestEntries>
<Content-Type>application/x-font</Content-Type> <Content-Type>application/x-font</Content-Type>
</manifestEntries> </manifestEntries>
</manifestSection> </manifestSection>
</manifestSections> </manifestSections>
</archive> </archive>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId> <artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version> <version>3.3.1</version>
<executions> <executions>
<execution> <execution>
<id>attach-sources</id> <id>attach-sources</id>
<goals> <goals>
<goal>jar-no-fork</goal> <goal>jar-no-fork</goal>
</goals> </goals>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId> <artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version> <version>3.5.3</version>
<configuration> <configuration>
<shadedArtifactAttached>true</shadedArtifactAttached> <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> <filters>
<filter>
<filter> <artifact>*:*</artifact>
<artifact>*:*</artifact> <excludes>
<excludes> <exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude>
<exclude>META-INF/*.RSA</exclude> </excludes>
</excludes> </filter>
</filter> </filters>
<filter> </configuration>
<artifact>log4j:log4j</artifact> <executions>
<includes> <execution>
<include>**</include> <phase>package</phase>
</includes> <goals>
</filter> <goal>shade</goal>
<filter> </goals>
<artifact>commons-logging:commons-logging</artifact> </execution>
<includes> </executions>
<include>**</include> </plugin>
</includes> <plugin>
</filter> <groupId>org.codehaus.mojo</groupId>
</filters> <artifactId>templating-maven-plugin</artifactId>
</configuration> <version>1.0.0</version>
<executions> <executions>
<execution> <execution>
<phase>package</phase> <id>filtering-java-templates</id>
<goals> <goals>
<goal>shade</goal> <goal>filter-sources</goal>
</goals> </goals>
<configuration> </execution>
<artifactSet> </executions>
<excludes> </plugin>
<!--exclude>classworlds:classworlds</exclude> <exclude>junit:junit</exclude> </plugins>
<exclude>jmock:*</exclude> <exclude>*:xml-apis</exclude> <exclude>org.apache.maven:lib:tests</exclude> </build>
<exclude>log4j:log4j:jar:</exclude -->
</excludes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>templating-maven-plugin</artifactId>
<version>1.0.0</version>
<executions>
<execution>
<id>filtering-java-templates</id>
<goals>
<goal>filter-sources</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles> <profiles>
<profile> <!-- enforce building binaries with Java 1.8 for Maven Central, otherwise using them e.g. as jar will throw a version exception--> <profile> <!-- enforce building binaries with Java 1.8 for Maven Central, otherwise using them e.g. as jar will throw a version exception-->
<id>release-sign-artifacts</id> <id>release-sign-artifacts</id>
<activation> <activation>
<property> <property>
<name>performRelease</name> <name>performRelease</name>
<value>true</value> <value>true</value>
</property> </property>
</activation> </activation>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-toolchains-plugin</artifactId> <artifactId>maven-toolchains-plugin</artifactId>
<version>3.1.0</version> <version>3.1.0</version>
<executions> <executions>
<execution> <execution>
<goals> <goals>
<goal>toolchain</goal> <goal>toolchain</goal>
</goals> </goals>
</execution> </execution>
</executions> </executions>
<configuration> <configuration>
<toolchains> <toolchains>
<jdk> <jdk>
<version>8</version> <version>11</version>
<vendor>adopt</vendor> <vendor>adopt</vendor>
</jdk> </jdk>
</toolchains> </toolchains>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
</profile> </profile>
</profiles> </profiles>
<mailingLists> <mailingLists>
<mailingList> <mailingList>
<name>User List</name> <name>User List</name>
<archive>https://groups.google.com/forum/?hl=de#!forum/mustangproject</archive> <archive>https://groups.google.com/forum/?hl=de#!forum/mustangproject</archive>
</mailingList> </mailingList>
</mailingLists> </mailingLists>
<licenses> <licenses>
<license> <license>
<name>Apache License, Version 2.0</name> <name>Apache License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution> <distribution>repo</distribution>
<comments>A business-friendly OSS license</comments> <comments>A business-friendly OSS license</comments>
</license> </license>
</licenses> </licenses>
<developers> <developers>
<developer> <developer>
<name>Jochen Stärk</name> <name>Jochen Stärk</name>
<email>jstaerk@usegroup.de</email> <email>jstaerk@usegroup.de</email>
<roles> <roles>
<role>architect</role> <role>architect</role>
<role>developer</role> <role>developer</role>
</roles> </roles>
</developer> </developer>
<developer> <developer>
<name>Alexander Schmidt</name> <name>Alexander Schmidt</name>
<email>schmidt.alexander@mail.de</email> <email>schmidt.alexander@mail.de</email>
<roles> <roles>
<role>developer</role> <role>developer</role>
</roles> </roles>
</developer> </developer>
</developers> </developers>
</project> </project>

View File

@@ -1,14 +1,13 @@
package org.mustangproject; package org.mustangproject;
import org.mustangproject.ZUGFeRD.IExportableTransaction; import com.fasterxml.jackson.annotation.JsonIgnore;
import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge;
import java.math.BigDecimal; import java.math.BigDecimal;
/*** /***
* (absolute) allowances on item and document level * (absolute) allowances on item and document level
*/ */
public class Allowance extends Charge implements IZUGFeRDAllowanceCharge { public class Allowance extends Charge {
/*** /***
* bean constructor * bean constructor
@@ -31,6 +30,7 @@ public class Allowance extends Charge implements IZUGFeRDAllowanceCharge {
* @return false since its not supposed to be calculated negatively * @return false since its not supposed to be calculated negatively
*/ */
@Override @Override
@JsonIgnore
public boolean isCharge() { public boolean isCharge() {
return false; return false;
} }

View File

@@ -1,5 +1,7 @@
package org.mustangproject; package org.mustangproject;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.mustangproject.ZUGFeRD.IZUGFeRDTradeSettlementPayment; import org.mustangproject.ZUGFeRD.IZUGFeRDTradeSettlementPayment;
/** /**
@@ -18,6 +20,10 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment {
* the "name" of the bank account (holder) * the "name" of the bank account (holder)
*/ */
protected String accountName=null; protected String accountName=null;
/***
* bean constructor
*/
public BankDetails() { }
/*** /***
* constructor for IBAN only :-) * constructor for IBAN only :-)
@@ -80,12 +86,14 @@ public class BankDetails implements IZUGFeRDTradeSettlementPayment {
* */ * */
@Override @Override
@Deprecated @Deprecated
@JsonIgnore
public String getOwnBIC() { public String getOwnBIC() {
return getBIC(); return getBIC();
} }
@Override @Override
@Deprecated @Deprecated
@JsonIgnore
public String getOwnIBAN() { public String getOwnIBAN() {
return getIBAN(); return getIBAN();
} }

View File

@@ -5,9 +5,7 @@ import java.io.Serializable;
import com.helger.commons.error.list.ErrorList; import com.helger.commons.error.list.ErrorList;
import com.helger.en16931.cii2ubl.CIIToUBL23Converter; import com.helger.en16931.cii2ubl.CIIToUBL23Converter;
import com.helger.ubl21.UBL21Writer; import com.helger.ubl23.UBL23Marshaller;
import com.helger.ubl22.UBL22Writer;
import com.helger.ubl23.UBL23Writer;
/*** /***
* converts a Cross Industry Invoice XML file to a UBL XML file * 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 ErrorList occurred=new ErrorList();
final CIIToUBL23Converter cc=new CIIToUBL23Converter(); final CIIToUBL23Converter cc=new CIIToUBL23Converter();
final Serializable aUBL = cc.convertCIItoUBL(input, occurred); 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 () UBL23Marshaller.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 ()
.setFormattedOutput (true) .setFormattedOutput (true)
.write ((oasis.names.specification.ubl.schema.xsd.invoice_23.InvoiceType) aUBL, output); .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) else if (aUBL instanceof oasis.names.specification.ubl.schema.xsd.creditnote_23.CreditNoteType)
{ {
UBL23Writer.creditNote () UBL23Marshaller.creditNote ()
.setFormattedOutput (true) .setFormattedOutput (true)
.write ((oasis.names.specification.ubl.schema.xsd.creditnote_23.CreditNoteType) aUBL, output); .write ((oasis.names.specification.ubl.schema.xsd.creditnote_23.CreditNoteType) aUBL, output);
} }

View File

@@ -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;
}
}

View 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";
}
}

View File

@@ -1,9 +1,8 @@
package org.mustangproject; package org.mustangproject;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.mustangproject.ZUGFeRD.IAbsoluteValueProvider; import org.mustangproject.ZUGFeRD.IAbsoluteValueProvider;
import org.mustangproject.ZUGFeRD.IExportableTransaction;
import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge; import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge;
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableItem;
import java.math.BigDecimal; import java.math.BigDecimal;
@@ -28,6 +27,10 @@ public class Charge implements IZUGFeRDAllowanceCharge {
* a simple human readable description * a simple human readable description
*/ */
protected String reason; protected String reason;
/**
* Code from list UNTDID 5189
*/
protected String reasonCode;
/** /**
* the category ID why this charge has been applied * 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 @Override
public BigDecimal getTotalAmount(IAbsoluteValueProvider currentItem) { 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) { if (totalAmount!=null) {
return totalAmount; return totalAmount;
} else if (percent!=null) {
return currentItem.getValue().multiply(getPercent().divide(new BigDecimal(100)));
} else { } 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 * @return true since it is supposed to be calculated negatively
*/ */
@Override @Override
@JsonIgnore
public boolean isCharge() { public boolean isCharge() {
return true; 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 * @param categoryCode usually S
* @return fluid setter * @return fluid setter
*/ */

View 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;
}
}

View File

@@ -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;
}
}

View 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;
}
}

View File

@@ -1,6 +1,6 @@
package org.mustangproject; package org.mustangproject;
public enum EStandard { public enum EStandard {
facturx, orderx, despatchadvice, ubldespatchadvice, zugferd, cii, ubl facturx, orderx, despatchadvice, ubldespatchadvice, zugferd, cii, ubl, ubl_creditnote
} }

View File

@@ -26,12 +26,8 @@ import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import org.mustangproject.ZUGFeRD.IExportableTransaction; import com.fasterxml.jackson.annotation.JsonInclude;
import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge; import org.mustangproject.ZUGFeRD.*;
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableItem;
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableTradeParty;
import org.mustangproject.ZUGFeRD.IZUGFeRDPaymentTerms;
import org.mustangproject.ZUGFeRD.IZUGFeRDTradeSettlement;
import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants; import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@@ -42,34 +38,38 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
* @see IExportableTransaction if you want to implement an interface instead * @see IExportableTransaction if you want to implement an interface instead
*/ */
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Invoice implements IExportableTransaction { 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 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 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;
@JsonDeserialize(contentAs=Item.class) protected ArrayList<CashDiscount> cashDiscounts = null;
@JsonDeserialize(contentAs = Item.class)
protected ArrayList<IZUGFeRDExportableItem> ZFItems = null; protected ArrayList<IZUGFeRDExportableItem> ZFItems = null;
protected ArrayList<String> notes = null; protected ArrayList<String> notes = null;
private List<IncludedNote> includedNotes = null; private List<IncludedNote> includedNotes = null;
protected String sellerOrderReferencedDocumentID; protected String sellerOrderReferencedDocumentID;
protected String contractReferencedDocument = null; protected String contractReferencedDocument = null;
protected ArrayList<FileAttachment> xmlEmbeddedFiles=null; protected ArrayList<FileAttachment> xmlEmbeddedFiles = null;
protected BigDecimal totalPrepaidAmount = null; protected BigDecimal totalPrepaidAmount = null;
protected Date detailedDeliveryDateStart = null; protected Date detailedDeliveryDateStart = null;
protected Date detailedDeliveryPeriodEnd = null; protected Date detailedDeliveryPeriodEnd = null;
protected ArrayList<IZUGFeRDAllowanceCharge> Allowances = new ArrayList<>(), protected ArrayList<IZUGFeRDAllowanceCharge> Allowances = new ArrayList<>(),
Charges = new ArrayList<>(), LogisticsServiceCharges = new ArrayList<>(); Charges = new ArrayList<>(), LogisticsServiceCharges = new ArrayList<>();
protected IZUGFeRDPaymentTerms paymentTerms = null; protected IZUGFeRDPaymentTerms paymentTerms = null;
protected Date invoiceReferencedIssueDate; protected Date invoiceReferencedIssueDate;
protected String specifiedProcuringProjectID = null; protected String specifiedProcuringProjectID = null;
protected String specifiedProcuringProjectName = null; protected String specifiedProcuringProjectName = null;
protected String despatchAdviceReferencedDocumentID = null; protected String despatchAdviceReferencedDocumentID = null;
protected String vatDueDateTypeCode = null; protected String vatDueDateTypeCode = null;
protected String creditorReferenceID; // required when direct debit is used.
public Invoice() { public Invoice() {
ZFItems = new ArrayList<>(); ZFItems = new ArrayList<>();
cashDiscounts = new ArrayList<>();
setCurrency("EUR"); setCurrency("EUR");
} }
@@ -79,7 +79,7 @@ public class Invoice implements IExportableTransaction {
} }
@Override @Override
public String getContractReferencedDocument() { public String getContractReferencedDocument() {
return contractReferencedDocument; return contractReferencedDocument;
} }
@@ -100,14 +100,14 @@ public class Invoice implements IExportableTransaction {
public Invoice embedFileInXML(FileAttachment fa) { public Invoice embedFileInXML(FileAttachment fa) {
if (xmlEmbeddedFiles == null) { if (xmlEmbeddedFiles == null) {
xmlEmbeddedFiles= new ArrayList<>(); xmlEmbeddedFiles = new ArrayList<>();
} }
xmlEmbeddedFiles.add(fa); xmlEmbeddedFiles.add(fa);
return this; return this;
} }
@Override @Override
public FileAttachment[] getAdditionalReferencedDocuments() { public FileAttachment[] getAdditionalReferencedDocuments() {
if (xmlEmbeddedFiles == null) { if (xmlEmbeddedFiles == null) {
return null; return null;
} }
@@ -115,6 +115,10 @@ public class Invoice implements IExportableTransaction {
} }
@Override
public IZUGFeRDCashDiscount[] getCashDiscounts() {
return cashDiscounts.toArray(new IZUGFeRDCashDiscount[0]);
}
@Override @Override
public String getNumber() { public String getNumber() {
@@ -139,6 +143,7 @@ public class Invoice implements IExportableTransaction {
documentCode = DocumentCodeTypeConstants.CORRECTEDINVOICE; documentCode = DocumentCodeTypeConstants.CORRECTEDINVOICE;
return this; return this;
} }
public Invoice setCreditNote() { public Invoice setCreditNote() {
documentCode = DocumentCodeTypeConstants.CREDITNOTE; documentCode = DocumentCodeTypeConstants.CREDITNOTE;
return this; return this;
@@ -229,16 +234,18 @@ public class Invoice implements IExportableTransaction {
public String getBuyerOrderReferencedDocumentID() { public String getBuyerOrderReferencedDocumentID() {
return buyerOrderReferencedDocumentID; return buyerOrderReferencedDocumentID;
} }
@Override @Override
public String getSellerOrderReferencedDocumentID() { public String getSellerOrderReferencedDocumentID() {
return sellerOrderReferencedDocumentID; return sellerOrderReferencedDocumentID;
} }
public Invoice setSellerOrderReferencedDocumentID(String sellerOrderReferencedDocumentID) { public Invoice setSellerOrderReferencedDocumentID(String sellerOrderReferencedDocumentID) {
this.sellerOrderReferencedDocumentID = sellerOrderReferencedDocumentID; this.sellerOrderReferencedDocumentID = sellerOrderReferencedDocumentID;
return this; return this;
} }
/*** /***
* usually the order number * usually the order number
* @param buyerOrderReferencedDocumentID string with number * @param buyerOrderReferencedDocumentID string with number
@@ -258,22 +265,23 @@ public class Invoice implements IExportableTransaction {
this.invoiceReferencedDocumentID = invoiceReferencedDocumentID; this.invoiceReferencedDocumentID = invoiceReferencedDocumentID;
return this; return this;
} }
@Override @Override
public String getInvoiceReferencedDocumentID() { public String getInvoiceReferencedDocumentID() {
return invoiceReferencedDocumentID; return invoiceReferencedDocumentID;
} }
@Override @Override
public Date getInvoiceReferencedIssueDate() { public Date getInvoiceReferencedIssueDate() {
return invoiceReferencedIssueDate; return invoiceReferencedIssueDate;
} }
public Invoice setInvoiceReferencedIssueDate(Date issueDate) { public Invoice setInvoiceReferencedIssueDate(Date issueDate) {
this.invoiceReferencedIssueDate = issueDate; this.invoiceReferencedIssueDate = issueDate;
return this; return this;
} }
@Override @Override
public String getBuyerOrderReferencedDocumentIssueDateTime() { public String getBuyerOrderReferencedDocumentIssueDateTime() {
return buyerOrderReferencedDocumentIssueDateTime; return buyerOrderReferencedDocumentIssueDateTime;
} }
@@ -285,7 +293,7 @@ public class Invoice implements IExportableTransaction {
* @return fluent setter * @return fluent setter
*/ */
public Invoice setTotalPrepaidAmount(BigDecimal prepaid) { public Invoice setTotalPrepaidAmount(BigDecimal prepaid) {
totalPrepaidAmount=prepaid; totalPrepaidAmount = prepaid;
return this; return this;
} }
@@ -380,13 +388,13 @@ public class Invoice implements IExportableTransaction {
@Override @Override
public String getOwnLocation() { public String getOwnLocation() {
return sender.getLocation(); return sender.getLocation();
} }
@Override @Override
public String getOwnCountry() { public String getOwnCountry() {
return sender.getCountry(); return sender.getCountry();
} }
@@ -399,12 +407,12 @@ public class Invoice implements IExportableTransaction {
return notes.toArray(new String[0]); return notes.toArray(new String[0]);
} }
@Override @Override
public List<IncludedNote> getNotesWithSubjectCode() { public List<IncludedNote> getNotesWithSubjectCode() {
return includedNotes; return includedNotes;
} }
@Override @Override
public String getCurrency() { public String getCurrency() {
return currency; return currency;
} }
@@ -473,13 +481,14 @@ public class Invoice implements IExportableTransaction {
} }
@Override @Override
public TradeParty getRecipient() { public TradeParty getRecipient() {
return recipient; return recipient;
} }
/** /**
* required. * required.
* sets the invoice receiving institution = invoicee * sets the invoice receiving institution = invoicee
*
* @param recipient the invoicee organisation * @param recipient the invoicee organisation
* @return fluent setter * @return fluent setter
*/ */
@@ -491,6 +500,7 @@ public class Invoice implements IExportableTransaction {
/** /**
* required. * required.
* sets the invoicing institution = invoicer * sets the invoicing institution = invoicer
*
* @param sender the invoicer * @param sender the invoicer
* @return fluent setter * @return fluent setter
*/ */
@@ -508,8 +518,22 @@ public class Invoice implements IExportableTransaction {
if (Allowances.isEmpty()) { if (Allowances.isEmpty()) {
return null; return null;
} else { } else {
return Allowances.toArray(new IZUGFeRDAllowanceCharge[0]); return Allowances.toArray(new IZUGFeRDAllowanceCharge[0]);
} }
}
/***
* 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;
} }
@@ -518,18 +542,30 @@ public class Invoice implements IExportableTransaction {
if (Charges.isEmpty()) { if (Charges.isEmpty()) {
return null; return null;
} else { } else {
return Charges.toArray(new IZUGFeRDAllowanceCharge[0]); return Charges.toArray(new IZUGFeRDAllowanceCharge[0]);
} }
} }
/***
* 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 @Override
public IZUGFeRDAllowanceCharge[] getZFLogisticsServiceCharges() { public IZUGFeRDAllowanceCharge[] getZFLogisticsServiceCharges() {
if (LogisticsServiceCharges.isEmpty()) { if (LogisticsServiceCharges.isEmpty()) {
return null; return null;
} else { } else {
return LogisticsServiceCharges.toArray(new IZUGFeRDAllowanceCharge[0]); return LogisticsServiceCharges.toArray(new IZUGFeRDAllowanceCharge[0]);
} }
} }
@@ -540,7 +576,7 @@ public class Invoice implements IExportableTransaction {
return null; return null;
} }
return ((TradeParty) getSender()).getAsTradeSettlement(); return getSender().getAsTradeSettlement();
} }
@@ -570,21 +606,48 @@ public class Invoice implements IExportableTransaction {
return this; 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 @Override
public IZUGFeRDExportableItem[] getZFItems() { public IZUGFeRDExportableItem[] getZFItems() {
return ZFItems.toArray(new IZUGFeRDExportableItem[0]); return ZFItems.toArray(new IZUGFeRDExportableItem[0]);
} }
public void setZFItems(ArrayList<IZUGFeRDExportableItem> ims) { public void setZFItems(ArrayList<IZUGFeRDExportableItem> ims) {
ZFItems=ims; ZFItems = ims;
} }
/** /**
* required * required
* adds invoice "lines" :-) * adds invoice "lines" :-)
* @see Item *
* @param item the invoice line * @param item the invoice line
* @return fluent setter * @return fluent setter
* @see Item
*/ */
public Invoice addItem(IZUGFeRDExportableItem item) { public Invoice addItem(IZUGFeRDExportableItem item) {
ZFItems.add(item); ZFItems.add(item);
@@ -669,6 +732,7 @@ public class Invoice implements IExportableTransaction {
/** /**
* adds a free text paragraph, which will become an includedNote element * adds a free text paragraph, which will become an includedNote element
*
* @param text freeform UTF8 plain text * @param text freeform UTF8 plain text
* @return fluent setter * @return fluent setter
*/ */
@@ -679,129 +743,138 @@ public class Invoice implements IExportableTransaction {
notes.add(text); notes.add(text);
return this; return this;
} }
public Invoice addNotes(Collection<IncludedNote> notes) {
if (notes == null) {
return this;
}
if (includedNotes == null) {
includedNotes = new ArrayList<>();
}
includedNotes.addAll(notes);
return this;
}
/**
* 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
*/
public Invoice addGeneralNote(String content) {
if (includedNotes == null) {
includedNotes = new ArrayList<>();
}
includedNotes.add(IncludedNote.generalNote(content));
return this;
}
/** public Invoice addNotes(Collection<IncludedNote> notes) {
* adds a free text paragraph, which will become an includedNote element with explicit if (notes == null) {
* subjectCode {@link SubjectCode#REG} return this;
* @param content freeform UTF8 plain text }
* @return fluent setter if (includedNotes == null) {
*/ includedNotes = new ArrayList<>();
public Invoice addRegulatoryNote(String content) { }
if (includedNotes == null) { includedNotes.addAll(notes);
includedNotes = new ArrayList<>(); return this;
} }
includedNotes.add(IncludedNote.regulatoryNote(content));
return this;
}
/** /**
* adds a free text paragraph, which will become an includedNote element with explicit * adds a free text paragraph, which will become an includedNote element with explicit
* subjectCode {@link SubjectCode#ABL} * subjectCode {@link SubjectCode#AAI}
* @param content freeform UTF8 plain text *
* @return fluent setter * @param content freeform UTF8 plain text
*/ * @return fluent setter
public Invoice addLegalNote(String content) { */
if (includedNotes == null) { public Invoice addGeneralNote(String content) {
includedNotes = new ArrayList<>(); if (includedNotes == null) {
} includedNotes = new ArrayList<>();
includedNotes.add(IncludedNote.legalNote(content)); }
return this; includedNotes.add(IncludedNote.generalNote(content));
} return this;
}
/** /**
* adds a free text paragraph, which will become an includedNote element with explicit * adds a free text paragraph, which will become an includedNote element with explicit
* subjectCode {@link SubjectCode#CUS} * subjectCode {@link SubjectCode#REG}
* @param content freeform UTF8 plain text *
* @return fluent setter * @param content freeform UTF8 plain text
*/ * @return fluent setter
public Invoice addCustomsNote(String content) { */
if (includedNotes == null) { public Invoice addRegulatoryNote(String content) {
includedNotes = new ArrayList<>(); if (includedNotes == null) {
} includedNotes = new ArrayList<>();
includedNotes.add(IncludedNote.customsNote(content)); }
return this; includedNotes.add(IncludedNote.regulatoryNote(content));
} return this;
}
/** /**
* adds a free text paragraph, which will become an includedNote element with explicit * adds a free text paragraph, which will become an includedNote element with explicit
* subjectCode {@link SubjectCode#SUR} * subjectCode {@link SubjectCode#ABL}
* @param content freeform UTF8 plain text *
* @return fluent setter * @param content freeform UTF8 plain text
*/ * @return fluent setter
public Invoice addSellerNote(String content) { */
if (includedNotes == null) { public Invoice addLegalNote(String content) {
includedNotes = new ArrayList<>(); if (includedNotes == null) {
} includedNotes = new ArrayList<>();
includedNotes.add(IncludedNote.sellerNote(content)); }
return this; includedNotes.add(IncludedNote.legalNote(content));
} return this;
}
/**
* 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
*/
public Invoice addCustomsNote(String content) {
if (includedNotes == null) {
includedNotes = new ArrayList<>();
}
includedNotes.add(IncludedNote.customsNote(content));
return this;
}
/**
* 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
*/
public Invoice addSellerNote(String content) {
if (includedNotes == null) {
includedNotes = new ArrayList<>();
}
includedNotes.add(IncludedNote.sellerNote(content));
return this;
}
/**
* 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
*/
public Invoice addTaxNote(String content) {
if (includedNotes == null) {
includedNotes = new ArrayList<>();
}
includedNotes.add(IncludedNote.taxNote(content));
return this;
}
/**
* 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
*/
public Invoice addIntroductionNote(String content) {
if (includedNotes == null) {
includedNotes = new ArrayList<>();
}
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
*/
public Invoice addDiscountBonusNote(String content) {
if (includedNotes == null) {
includedNotes = new ArrayList<>();
}
includedNotes.add(IncludedNote.discountBonusNote(content));
return this;
}
/**
* 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
*/
public Invoice addTaxNote(String content) {
if (includedNotes == null) {
includedNotes = new ArrayList<>();
}
includedNotes.add(IncludedNote.taxNote(content));
return this;
}
/**
* 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
*/
public Invoice addIntroductionNote(String content) {
if (includedNotes == null) {
includedNotes = new ArrayList<>();
}
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
*/
public Invoice addDiscountBonusNote(String content) {
if (includedNotes == null) {
includedNotes = new ArrayList<>();
}
includedNotes.add(IncludedNote.discountBonusNote(content));
return this;
}
@Override @Override
public String getSpecifiedProcuringProjectID() { public String getSpecifiedProcuringProjectID() {
return specifiedProcuringProjectID; return specifiedProcuringProjectID;
@@ -841,6 +914,7 @@ public class Invoice implements IExportableTransaction {
/** /**
* Decide when the VAT should be collected. * Decide when the VAT should be collected.
*
* @param vatDueDateTypeCode use EventTimeCodeTypeConstants * @param vatDueDateTypeCode use EventTimeCodeTypeConstants
* @return fluent setter * @return fluent setter
*/ */
@@ -849,4 +923,13 @@ public class Invoice implements IExportableTransaction {
return this; return this;
} }
public String getCreditorReferenceID() {
return creditorReferenceID;
}
public Invoice setCreditorReferenceID(String creditorReferenceID) {
this.creditorReferenceID = creditorReferenceID;
return this;
}
} }

View File

@@ -1,10 +1,11 @@
package org.mustangproject; package org.mustangproject;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 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.IReferencedDocument;
import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge; import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge;
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableItem; import org.mustangproject.ZUGFeRD.IZUGFeRDExportableItem;
import org.mustangproject.util.NodeMap;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
@@ -12,24 +13,30 @@ import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List;
/*** /***
* describes any invoice line * describes any invoice line
*/ */
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Item implements IZUGFeRDExportableItem { 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 BigDecimal basisQuantity = BigDecimal.ONE;
protected Date detailedDeliveryPeriodFrom = null, detailedDeliveryPeriodTo = null; protected Date detailedDeliveryPeriodFrom = null;
protected Date detailedDeliveryPeriodTo = null;
protected String id; protected String id;
protected String referencedLineID = null; protected String referencedLineID = null;
protected Product product; protected Product product;
protected ArrayList<String> notes = null; protected ArrayList<String> notes = null;
protected ArrayList<ReferencedDocument> referencedDocuments = null; protected ArrayList<ReferencedDocument> referencedDocuments = null;
protected ArrayList<IZUGFeRDAllowanceCharge> Allowances = new ArrayList<IZUGFeRDAllowanceCharge>(), protected ArrayList<ReferencedDocument> additionalReference = null;
Charges = new ArrayList<IZUGFeRDAllowanceCharge>(); protected ArrayList<IZUGFeRDAllowanceCharge> Allowances = new ArrayList<>();
protected ArrayList<IZUGFeRDAllowanceCharge> Charges = new ArrayList<>();
/*** /***
* default constructor * default constructor
@@ -43,7 +50,6 @@ public class Item implements IZUGFeRDExportableItem {
this.product = product; this.product = product;
} }
/*** /***
* empty constructor * empty constructor
* do not use, but might be used e.g. by jackson * do not use, but might be used e.g. by jackson
@@ -52,222 +58,75 @@ public class Item implements IZUGFeRDExportableItem {
} }
public Item(NodeList itemChilds, boolean recalcPrice) { public Item(NodeList itemChilds, boolean recalcPrice) {
String price = "0"; NodeMap itemMap = new NodeMap(itemChilds);
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";
ArrayList<ReferencedDocument> rdocs = null; itemMap.getAsNodeMap("Item").ifPresent(icnm -> {
// ubl
//we need: name description unitcode
//and we additionally have vat%
setProduct(new Product());
icnm.getAsString("Name").ifPresent(product::setName);
icnm.getAsNodeMap("ClassifiedTaxCategory").flatMap(m -> m.getAsBigDecimal("Percent"))
.ifPresent(product::setVATPercent);
});
// nodes.item(i).getTextContent())) { itemMap.getAsNodeMap("Price").ifPresent(icnm -> {
// ubl
// PriceAmount with currencyID and BaseQuantity with unitCode
icnm.getAsBigDecimal("PriceAmount").ifPresent(this::setPrice);
icnm.getAsBigDecimal("BaseQuantity").ifPresent(this::setBasisQuantity);
});
for (int itemChildIndex = 0; itemChildIndex < itemChilds.getLength(); itemChildIndex++) { itemMap.getNode("InvoicedQuantity").ifPresent(icn -> {
String lineTrade = itemChilds.item(itemChildIndex).getLocalName(); // ubl
if ((lineTrade != null) && (lineTrade.equals("Item"))) { setQuantity(new BigDecimal(icn.getTextContent().trim()));
// ubl product.setUnit(icn.getAttributes().getNamedItem("unitCode").getNodeValue());
//we need: name description unitcode });
//and we additionally have vat%
NodeList UBLitemChilds = itemChilds.item(itemChildIndex).getChildNodes();
for (Node currentUBLItemChildNode : XMLTools.asList(UBLitemChilds)) {
if ((currentUBLItemChildNode.getLocalName() != null) && (currentUBLItemChildNode.getLocalName().equals("Name"))) { itemMap.getAsNodeMap("SpecifiedLineTradeAgreement", "SpecifiedSupplyChainTradeAgreement").ifPresent(icnm -> {
name = currentUBLItemChildNode.getTextContent(); icnm.getAsNodeMap("BuyerOrderReferencedDocument")
} .flatMap(bordNodes -> bordNodes.getAsString("LineID"))
if ((currentUBLItemChildNode.getLocalName() != null) && (currentUBLItemChildNode.getLocalName().equals("ClassifiedTaxCategory"))) { .ifPresent(this::addReferencedLineID);
for (Node currentUBLTaxChildNode : XMLTools.asList(currentUBLItemChildNode.getChildNodes())) {
if ((currentUBLTaxChildNode.getLocalName() != null) && (currentUBLTaxChildNode.getLocalName().equals("Percent"))) { icnm.getAsNodeMap("NetPriceProductTradePrice").ifPresent(npptpNodes -> {
vatPercent = currentUBLTaxChildNode.getTextContent(); npptpNodes.getAsBigDecimal("ChargeAmount").ifPresent(this::setPrice);
} npptpNodes.getAsBigDecimal("BasisQuantity").ifPresent(this::setBasisQuantity);
} });
icnm.getAllNodes("AdditionalReferencedDocument").map(ReferencedDocument::fromNode)
.forEach(this::addReferencedDocument);
});
itemMap.getNode("SpecifiedTradeProduct").map(Product::new).ifPresent(this::setProduct);
// RequestedQuantity is for Order-X, BilledQuantity for FX and ZF
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());
} }
} }
});
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)));
} }
if ((lineTrade != null) && (lineTrade.equals("Price"))) {
// ubl
// PriceAmount with currencyID and BaseQuantity with unitCode
NodeList UBLpriceChilds = itemChilds.item(itemChildIndex).getChildNodes();
for (Node currentUBLPriceChildNode : XMLTools.asList(UBLpriceChilds)) {
if ((currentUBLPriceChildNode.getLocalName() != null) && (currentUBLPriceChildNode.getLocalName().equals("PriceAmount"))) { icnm.getAllNodes("AdditionalReferencedDocument").map(ReferencedDocument::fromNode).forEach(this::addAdditionalReference);
price = currentUBLPriceChildNode.getTextContent(); });
}
if ((currentUBLPriceChildNode.getLocalName() != null) && (currentUBLPriceChildNode.getLocalName().equals("BaseQuantity"))) {
basisQuantity = currentUBLPriceChildNode.getTextContent();
}
}
}
if ((lineTrade != null) && (lineTrade.equals("InvoicedQuantity"))) {
// 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++) {
if ((tradeLineChilds.item(tradeLineChildIndex).getLocalName() != null) && tradeLineChilds
.item(tradeLineChildIndex).getLocalName().equals("AdditionalReferencedDocument")) {
String IssuerAssignedID = "";
String TypeCode = "";
String ReferenceTypeCode = "";
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();
}
}
ReferencedDocument rd = new ReferencedDocument(IssuerAssignedID, TypeCode,
ReferenceTypeCode);
if (rdocs == null) {
rdocs = new ArrayList<ReferencedDocument>();
}
rdocs.add(rd);
}
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();
}
}
}
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());
}
}
}
}
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) { public Item addReferencedLineID(String s) {
referencedLineID = s; referencedLineID = s;
return this; return this;
@@ -428,7 +287,7 @@ public class Item implements IZUGFeRDExportableItem {
*/ */
public Item addNote(String text) { public Item addNote(String text) {
if (notes == null) { if (notes == null) {
notes = new ArrayList<String>(); notes = new ArrayList<>();
} }
notes.add(text); notes.add(text);
return this; return this;
@@ -441,7 +300,7 @@ public class Item implements IZUGFeRDExportableItem {
*/ */
public Item addReferencedDocument(ReferencedDocument doc) { public Item addReferencedDocument(ReferencedDocument doc) {
if (referencedDocuments == null) { if (referencedDocuments == null) {
referencedDocuments = new ArrayList<ReferencedDocument>(); referencedDocuments = new ArrayList<>();
} }
referencedDocuments.add(doc); referencedDocuments.add(doc);
return this; return this;
@@ -455,6 +314,29 @@ public class Item implements IZUGFeRDExportableItem {
return referencedDocuments.toArray(new IReferencedDocument[0]); 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 * specify a item level delivery period
* (apart from the document level delivery period, and the document level * (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 * this will be included in a BillingSpecifiedPeriod element
* @return the beginning of the delivery period * @return the beginning of the delivery period
*/ */
@Override
public Date getDetailedDeliveryPeriodFrom() { public Date getDetailedDeliveryPeriodFrom() {
return detailedDeliveryPeriodFrom; return detailedDeliveryPeriodFrom;
} }
@@ -484,6 +367,7 @@ public class Item implements IZUGFeRDExportableItem {
* this will be included in a BillingSpecifiedPeriod element * this will be included in a BillingSpecifiedPeriod element
* @return the end of the delivery period * @return the end of the delivery period
*/ */
@Override
public Date getDetailedDeliveryPeriodTo() { public Date getDetailedDeliveryPeriodTo() {
return detailedDeliveryPeriodTo; return detailedDeliveryPeriodTo;
} }

View File

@@ -5,46 +5,74 @@ import org.mustangproject.ZUGFeRD.*;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import org.w3c.dom.NodeList; 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 * A organisation, i.e. usually a company
*/ */
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class LegalOrganisation implements IZUGFeRDLegalOrganisation { public class LegalOrganisation implements IZUGFeRDLegalOrganisation {
protected String ID = null; protected SchemedID schemedID = null;
protected String SchemeID = null; protected String tradingBusinessName = null;
public LegalOrganisation() { public LegalOrganisation() {
} }
public LegalOrganisation(String ID, String scheme) { public LegalOrganisation(String ID, String scheme) {
this.ID=ID; this.schemedID = new SchemedID(scheme, ID);
this.SchemeID=scheme; }
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 &amp; 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 @Override
public String getID() { public SchemedID getSchemedID() {
return ID; return this.schemedID;
} }
@Override @Override
public String getSchemeID() { public String getTradingBusinessName() {
return SchemeID; return this.tradingBusinessName;
} }
public LegalOrganisation setID(String id) { public LegalOrganisation setSchemedID(SchemedID schemedID) {
this.ID=id; this.schemedID = schemedID;
return this; return this;
} }
public LegalOrganisation setSchemeID(String scheme) { public LegalOrganisation setTradingBusinessName(String tradingBusinessName) {
SchemeID=scheme; this.tradingBusinessName = tradingBusinessName;
return this; return this;
} }
} }

View File

@@ -1,20 +1,37 @@
package org.mustangproject; package org.mustangproject;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 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.ZUGFeRD.IZUGFeRDExportableProduct;
import org.mustangproject.util.NodeMap;
import org.w3c.dom.Node;
import java.math.BigDecimal; 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 * describes a product, good or service used in an invoice item line
*/ */
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Product implements IZUGFeRDExportableProduct { 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 BigDecimal VATPercent;
protected boolean isReverseCharge = false; protected boolean isReverseCharge = false;
protected boolean isIntraCommunitySupply = false; protected boolean isIntraCommunitySupply = false;
protected SchemedID globalId = null; protected SchemedID globalId = null;
protected String countryOfOrigin = null;
protected HashMap<String, String> attributes = new HashMap<>();
protected List<IDesignatedProductClassification> classifications = new ArrayList<>();
/*** /***
* default constructor * default constructor
@@ -30,6 +47,42 @@ public class Product implements IZUGFeRDExportableProduct {
this.VATPercent = VATPercent; 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 * empty constructor
@@ -62,7 +115,49 @@ public class Product implements IZUGFeRDExportableProduct {
return this; 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() { public String getSellerAssignedID() {
return sellerAssignedID; return sellerAssignedID;
} }
@@ -77,6 +172,7 @@ public class Product implements IZUGFeRDExportableProduct {
return this; return this;
} }
@Override
public String getBuyerAssignedID() { public String getBuyerAssignedID() {
return buyerAssignedID; return buyerAssignedID;
} }
@@ -119,6 +215,8 @@ public class Product implements IZUGFeRDExportableProduct {
public Product setIntraCommunitySupply() { public Product setIntraCommunitySupply() {
isIntraCommunitySupply = true; isIntraCommunitySupply = true;
setVATPercent(BigDecimal.ZERO); setVATPercent(BigDecimal.ZERO);
setTaxExemptionReason("Intra-community supply");
setTaxCategoryCode("K");
return this; return this;
} }
@@ -183,4 +281,70 @@ public class Product implements IZUGFeRDExportableProduct {
this.VATPercent = VATPercent; this.VATPercent = VATPercent;
return this; 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;
}
} }

View File

@@ -1,6 +1,8 @@
package org.mustangproject; package org.mustangproject;
import org.mustangproject.ZUGFeRD.IReferencedDocument; import org.mustangproject.ZUGFeRD.IReferencedDocument;
import org.mustangproject.util.NodeMap;
import org.w3c.dom.Node;
public class ReferencedDocument implements IReferencedDocument { public class ReferencedDocument implements IReferencedDocument {
@@ -58,4 +60,14 @@ public class ReferencedDocument implements IReferencedDocument {
public String getReferenceTypeCode() { public String getReferenceTypeCode() {
return referenceTypeCode; 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"));
}
} }

View File

@@ -31,4 +31,9 @@ public class SchemedID {
setId(id); setId(id);
} }
@Override
public String toString() {
return "SchemedID{scheme='" + scheme + "', id='" + id + "'}";
}
} }

View File

@@ -5,29 +5,32 @@ import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableContact; import org.mustangproject.ZUGFeRD.IZUGFeRDExportableContact;
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableTradeParty; import org.mustangproject.ZUGFeRD.IZUGFeRDExportableTradeParty;
import org.mustangproject.ZUGFeRD.IZUGFeRDLegalOrganisation; import org.mustangproject.ZUGFeRD.IZUGFeRDLegalOrganisation;
import org.mustangproject.ZUGFeRD.IZUGFeRDTradeSettlement; import org.mustangproject.ZUGFeRD.IZUGFeRDTradeSettlement;
import org.mustangproject.ZUGFeRD.IZUGFeRDTradeSettlementDebit;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
/*** /***
* A organisation, i.e. usually a company * A organisation, i.e. usually a company
*/ */
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class TradeParty implements IZUGFeRDExportableTradeParty { public class TradeParty implements IZUGFeRDExportableTradeParty {
protected String name, zip, street, location, country; protected String name, zip, street, location, country;
protected String taxID = null, vatID = null; protected String taxID = null, vatID = null;
protected String ID = null; protected String ID = null;
protected String description = null;
protected String additionalAddress = null; protected String additionalAddress = null;
protected String additionalAddressExtension = null; protected String additionalAddressExtension = null;
protected List<BankDetails> bankDetails = new ArrayList<>(); protected List<BankDetails> bankDetails = new ArrayList<>();
protected List<IZUGFeRDTradeSettlementDebit> debitDetails = new ArrayList<>(); protected List<DirectDebit> debitDetails = new ArrayList<>();
protected Contact contact = null; protected Contact contact = null;
protected LegalOrganisation legalOrg = null; protected LegalOrganisation legalOrg = null;
protected SchemedID globalId = null; protected SchemedID globalId = null;
@@ -66,15 +69,15 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
//nodes.item(i).getTextContent())) { //nodes.item(i).getTextContent())) {
Node currentItemNode = nodes.item(nodeIndex); Node currentItemNode = nodes.item(nodeIndex);
if (nodes.item(nodeIndex).getLocalName() != null) { if (currentItemNode.getLocalName() != null) {
String debcurrentChild = nodes.item(nodeIndex).getLocalName(); String currentUBLChild = currentItemNode.getLocalName();
if (nodes.item(nodeIndex).getLocalName().equals("Party")) { if (currentUBLChild.equals("Party")) {
NodeList party = nodes.item(nodeIndex).getChildNodes(); NodeList party = currentItemNode.getChildNodes();
for (int partyIndex = 0; partyIndex < party.getLength(); partyIndex++) { for (int partyIndex = 0; partyIndex < party.getLength(); partyIndex++) {
if (party.item(partyIndex).getLocalName() != null) { if (party.item(partyIndex).getLocalName() != null) {
String debCN = party.item(partyIndex).getLocalName(); String currentTopElementName = party.item(partyIndex).getLocalName();
if (party.item(partyIndex).getLocalName().equals("PartyName")) { if (currentTopElementName.equals("PartyName")) {
NodeList partyName = party.item(partyIndex).getChildNodes(); NodeList partyName = party.item(partyIndex).getChildNodes();
for (int partyNameIndex = 0; partyNameIndex < partyName.getLength(); partyNameIndex++) { 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(); NodeList postal = party.item(partyIndex).getChildNodes();
for (int postalChildIndex = 0; postalChildIndex < postal.getLength(); postalChildIndex++) { 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(); NodeList contact = party.item(partyIndex).getChildNodes();
setContact(new Contact(contact)); 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) { 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()); SchemedID gid = new SchemedID().setScheme(nodes.item(nodeIndex).getAttributes().getNamedItem("schemeID").getNodeValue()).setId(nodes.item(nodeIndex).getTextContent());
addGlobalID(gid); addGlobalID(gid);
} }
} }
if (nodes.item(nodeIndex).getLocalName().equals("DefinedTradeContact")) { if (currentUBLChild.equals("DefinedTradeContact")) {
NodeList contact = nodes.item(nodeIndex).getChildNodes(); NodeList contact = nodes.item(nodeIndex).getChildNodes();
setContact(new Contact(contact)); setContact(new Contact(contact));
} }
if (nodes.item(nodeIndex).getLocalName().equals("PostalTradeAddress")) { if (currentUBLChild.equals("PostalTradeAddress")) {
NodeList postal = nodes.item(nodeIndex).getChildNodes(); NodeList postal = nodes.item(nodeIndex).getChildNodes();
for (int postalChildIndex = 0; postalChildIndex < postal.getLength(); postalChildIndex++) { for (int postalChildIndex = 0; postalChildIndex < postal.getLength(); postalChildIndex++) {
if (postal.item(postalChildIndex).getLocalName() != null) { 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(); NodeList taxChilds = nodes.item(nodeIndex).getChildNodes();
for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) { for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) {
if (taxChilds.item(taxChildIndex).getLocalName() != null) { if (taxChilds.item(taxChildIndex).getLocalName() != null) {
@@ -204,12 +220,10 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
if (taxChilds.item(taxChildIndex).getAttributes().getNamedItem("schemeID") != null) { if (taxChilds.item(taxChildIndex).getAttributes().getNamedItem("schemeID") != null) {
Node firstChild = taxChilds.item(taxChildIndex).getFirstChild(); Node firstChild = taxChilds.item(taxChildIndex).getFirstChild();
if (firstChild != null) { if (firstChild != null) {
if (taxChilds.item(taxChildIndex).getAttributes() if (taxChilds.item(taxChildIndex).getAttributes().getNamedItem("schemeID").getNodeValue().equals("VA")) {
.getNamedItem("schemeID").getNodeValue().equals("VA")) {
setVATID(firstChild.getNodeValue()); setVATID(firstChild.getNodeValue());
} }
if (taxChilds.item(taxChildIndex).getAttributes() if (taxChilds.item(taxChildIndex).getAttributes().getNamedItem("schemeID").getNodeValue().equals("FC")) {
.getNamedItem("schemeID").getNodeValue().equals("FC")) {
setTaxID(firstChild.getNodeValue()); setTaxID(firstChild.getNodeValue());
} }
} }
@@ -289,8 +303,8 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
for (int nodeIndex = 0; nodeIndex < nodes.getLength(); nodeIndex++) { for (int nodeIndex = 0; nodeIndex < nodes.getLength(); nodeIndex++) {
//nodes.item(i).getTextContent())) { //nodes.item(i).getTextContent())) {
String debLN = nodes.item(nodeIndex).getLocalName(); String topElementName = nodes.item(nodeIndex).getLocalName();
if (nodes.item(nodeIndex).getLocalName().equals("Party")) { if (topElementName.equals("Party")) {
// take one step back and parse from top // take one step back and parse from top
parseFromUBL(nodes); parseFromUBL(nodes);
return; return;
@@ -299,15 +313,26 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
NodeList itemChilds = currentItemNode.getChildNodes(); NodeList itemChilds = currentItemNode.getChildNodes();
for (int itemChildIndex = 0; itemChildIndex < itemChilds.getLength(); itemChildIndex++) { for (int itemChildIndex = 0; itemChildIndex < itemChilds.getLength(); itemChildIndex++) {
if (itemChilds.item(itemChildIndex).getLocalName() != null) { 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")) { if (itemChilds.item(itemChildIndex).getLocalName().equals("Name")) {
setName(itemChilds.item(itemChildIndex).getTextContent()); 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).getLocalName().equals("GlobalID")) {
if (itemChilds.item(itemChildIndex).getAttributes().getNamedItem("schemeID") != null) { 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()); SchemedID gid = new SchemedID().setScheme(itemChilds.item(itemChildIndex).getAttributes().getNamedItem("schemeID").getNodeValue()).setId(itemChilds.item(itemChildIndex).getTextContent());
addGlobalID(gid); 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")) { if (itemChilds.item(itemChildIndex).getLocalName().equals("DefinedTradeContact")) {
NodeList contact = itemChilds.item(itemChildIndex).getChildNodes(); NodeList contact = itemChilds.item(itemChildIndex).getChildNodes();
setContact(new Contact(contact)); setContact(new Contact(contact));
@@ -359,12 +384,10 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
if (taxChilds.item(taxChildIndex).getAttributes().getNamedItem("schemeID") != null) { if (taxChilds.item(taxChildIndex).getAttributes().getNamedItem("schemeID") != null) {
Node firstChild = taxChilds.item(taxChildIndex).getFirstChild(); Node firstChild = taxChilds.item(taxChildIndex).getFirstChild();
if (firstChild != null) { if (firstChild != null) {
if (taxChilds.item(taxChildIndex).getAttributes() if (taxChilds.item(taxChildIndex).getAttributes().getNamedItem("schemeID").getNodeValue().equals("VA")) {
.getNamedItem("schemeID").getNodeValue().equals("VA")) {
setVATID(firstChild.getNodeValue()); setVATID(firstChild.getNodeValue());
} }
if (taxChilds.item(taxChildIndex).getAttributes() if (taxChilds.item(taxChildIndex).getAttributes().getNamedItem("schemeID").getNodeValue().equals("FC")) {
.getNamedItem("schemeID").getNodeValue().equals("FC")) {
setTaxID(firstChild.getNodeValue()); setTaxID(firstChild.getNodeValue());
} }
} }
@@ -404,6 +427,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
@Override @Override
@JsonIgnore
public String getUriUniversalCommunicationID() { public String getUriUniversalCommunicationID() {
if (uriUniversalCommunicationId != null) { if (uriUniversalCommunicationId != null) {
return uriUniversalCommunicationId.getID(); return uriUniversalCommunicationId.getID();
@@ -413,6 +437,7 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
@Override @Override
@JsonIgnore
public String getUriUniversalCommunicationIDScheme() { public String getUriUniversalCommunicationIDScheme() {
if (uriUniversalCommunicationId != null) { if (uriUniversalCommunicationId != null) {
return uriUniversalCommunicationId.getScheme(); return uriUniversalCommunicationId.getScheme();
@@ -423,8 +448,9 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
/*** /***
* sets the email of the organization (not the one of the contact person) * sets the email of the organization (not the one of the contact person)
* (while setEmail has to be defined here getEmail comes from IZUGFeRDExportableTradeParty) * (while setEmail has to be defined here getEmail comes from IZUGFeRDExportableTradeParty)
* @param eMail address of institution (not contact)
* @return fluent setter * @return fluent setter
*/ */
public TradeParty setEmail(String eMail) { public TradeParty setEmail(String eMail) {
@@ -476,17 +502,26 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
return this; return this;
} }
/** /**
* (optional) * (optional)
* *
* @param debitDetail e.g. containing IBAN and mandate * @param debitDetail e.g. containing IBAN and mandate
* @return fluent setter * @return fluent setter
*/ */
public TradeParty addDebitDetails(IZUGFeRDTradeSettlementDebit debitDetail) { public TradeParty addDebitDetails(DirectDebit debitDetail) {
debitDetails.add(debitDetail); debitDetails.add(debitDetail);
return this; return this;
} }
/**
* primarily for invoiceimporter and JSON
* @return the list of sepa mandates
*/
public List<DirectDebit> getDebitDetails() {
return debitDetails;
}
@Override @Override
public IZUGFeRDLegalOrganisation getLegalOrganisation() { public IZUGFeRDLegalOrganisation getLegalOrganisation() {
return legalOrg; 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() { public String getZIP() {
return zip; return zip;
} }
@@ -626,13 +681,12 @@ public class TradeParty implements IZUGFeRDExportableTradeParty {
return contact; return contact;
} }
@JsonIgnore
public IZUGFeRDTradeSettlement[] getAsTradeSettlement() { public IZUGFeRDTradeSettlement[] getAsTradeSettlement() {
if (bankDetails.isEmpty() && debitDetails.isEmpty()) { if (bankDetails.isEmpty() && debitDetails.isEmpty()) {
return null; return null;
} }
List<IZUGFeRDTradeSettlement> tradeSettlements = Stream.concat(bankDetails.stream(), debitDetails.stream()) List<IZUGFeRDTradeSettlement> tradeSettlements = Stream.concat(bankDetails.stream(), debitDetails.stream()).map(IZUGFeRDTradeSettlement.class::cast).collect(Collectors.toList());
.map(IZUGFeRDTradeSettlement.class::cast)
.collect(Collectors.toList());
IZUGFeRDTradeSettlement[] result = new IZUGFeRDTradeSettlement[tradeSettlements.size()]; IZUGFeRDTradeSettlement[] result = new IZUGFeRDTradeSettlement[tradeSettlements.size()];
for (int i = 0; i < tradeSettlements.size(); i++) { for (int i = 0; i < tradeSettlements.size(); i++) {

View File

@@ -1,19 +1,16 @@
package org.mustangproject; package org.mustangproject;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.AbstractList; import java.text.SimpleDateFormat;
import java.util.Collections; import java.util.Date;
import java.util.List;
import java.util.RandomAccess;
import org.apache.commons.io.IOUtils;
import org.dom4j.io.XMLWriter; import org.dom4j.io.XMLWriter;
import org.mustangproject.ZUGFeRD.ZUGFeRDDateFormat;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class XMLTools extends XMLWriter { public class XMLTools extends XMLWriter {
@Override @Override
@@ -24,34 +21,11 @@ public class XMLTools extends XMLWriter {
@Override @Override
public String escapeElementEntities(String s) { public String escapeElementEntities(String s) {
return super.escapeElementEntities(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) { 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 * NumberFormat.getCurrencyInstance().format( 12345.6789 ); but that is locale
* specific.I also tried DecimalFormat df = new DecimalFormat( "0,00" ); * specific.I also tried DecimalFormat df = new DecimalFormat( "0,00" );
* df.setDecimalSeparatorAlwaysShown(true); df.setGroupingUsed(false); * 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 &lt;
* @param s the string
* @return the "safe" string
*/
public static String encodeXML(CharSequence s) { public static String encodeXML(CharSequence s) {
if (s == null) { if (s == null) {
return ""; return "";
@@ -120,36 +192,6 @@ public class XMLTools extends XMLWriter {
return sb.toString(); 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 * removes utf8 byte order marks from byte arrays, in case one is there
* @param zugferdRaw the CII XML * @param zugferdRaw the CII XML
@@ -157,6 +199,7 @@ public class XMLTools extends XMLWriter {
*/ */
public static byte[] removeBOM(byte[] zugferdRaw) { public static byte[] removeBOM(byte[] zugferdRaw) {
final byte[] zugferdData; final byte[] zugferdData;
// This handles the UTF-8 BOM
if ((zugferdRaw[0] == (byte) 0xEF) && (zugferdRaw[1] == (byte) 0xBB) && (zugferdRaw[2] == (byte) 0xBF)) { if ((zugferdRaw[0] == (byte) 0xEF) && (zugferdRaw[1] == (byte) 0xBB) && (zugferdRaw[2] == (byte) 0xBF)) {
// I don't like BOMs, lets remove it // I don't like BOMs, lets remove it
zugferdData = new byte[zugferdRaw.length - 3]; zugferdData = new byte[zugferdRaw.length - 3];
@@ -168,20 +211,19 @@ public class XMLTools extends XMLWriter {
} }
public static byte[] getBytesFromStream(InputStream fileinput) throws IOException { public static byte[] getBytesFromStream(InputStream fileinput) throws IOException {
return IOUtils.toByteArray (fileinput);
// 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
} }
public static String trimOrNull(Node node) {
if (node != null) {
String textContent = node.getTextContent();
if (textContent != null) {
return textContent.trim();
}
}
return null;
}
} }

View File

@@ -22,18 +22,18 @@ package org.mustangproject.ZUGFeRD;
import static org.mustangproject.ZUGFeRD.ZUGFeRDDateFormat.DATE; import static org.mustangproject.ZUGFeRD.ZUGFeRDDateFormat.DATE;
import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets;
import java.util.Base64; import java.util.Base64;
import java.util.Optional; 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; protected IExportableTransaction trans;
private String paymentTermsDescription;
protected Profile profile = Profiles.getByName(EStandard.despatchadvice,"pilot", 1); protected Profile profile = Profiles.getByName(EStandard.despatchadvice,"pilot", 1);
@@ -249,13 +249,9 @@ public class DAPullProvider extends ZUGFeRD2PullProvider implements IXMLProvider
+ "</SCRDMCCBDACIDAMessageStructure>"; + "</SCRDMCCBDACIDAMessageStructure>";
final byte[] zugferdRaw; final byte[] zugferdRaw;
try { zugferdRaw = xml.getBytes(StandardCharsets.UTF_8);
zugferdRaw = xml.getBytes("UTF-8");
zugferdData = XMLTools.removeBOM(zugferdRaw); zugferdData = XMLTools.removeBOM(zugferdRaw);
} catch (final UnsupportedEncodingException e) {
Logger.getLogger(OXPullProvider.class.getName()).log(Level.SEVERE, null, e);
}
} }

View File

@@ -19,31 +19,35 @@
package org.mustangproject.ZUGFeRD; 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.IOException;
import java.io.InputStream; 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; protected boolean ignorePDFAErrors = false;
public DXExporterFromA1 ignorePDFAErrors() { @Override
public DXExporterFromA1 ignorePDFAErrors() {
this.ignorePDFAErrors = true; this.ignorePDFAErrors = true;
return this; return this;
} }
private static boolean isValidA1(DataSource dataSource) throws IOException { 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 * internal helper function: get namespace for order-x
* @param ver the delivery-x version * @param ver the delivery-x version
* @return the URN of the namespace * @return the URN of the namespace
*/ */
public String getNamespaceForVersion(int ver) { @Override
public String getNamespaceForVersion(int ver) {
// As of late 2022 the Delivery-X standard is not yet published. See specification: // 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 // Die digitale Ablösung des Papier-Lieferscheins, Version 1.1, April 2022
// Chapter 7.1 XMP-Erweiterungsschema für PDF/A-3 // Chapter 7.1 XMP-Erweiterungsschema für PDF/A-3
@@ -55,7 +59,8 @@ public class DXExporterFromA1 extends DXExporterFromA3 implements IZUGFeRDExport
* @param ver the ox version * @param ver the ox version
* @return the namespace prefix as string, without colon * @return the namespace prefix as string, without colon
*/ */
public String getPrefixForVersion(int ver) { @Override
public String getPrefixForVersion(int ver) {
return "fx"; return "fx";
} }
@@ -65,19 +70,19 @@ public class DXExporterFromA1 extends DXExporterFromA3 implements IZUGFeRDExport
* NonSequentialParser. Some additional controls are present to check a set of * NonSequentialParser. Some additional controls are present to check a set of
* PDF/A requirements. (Stream length consistency, EOL after some Keyword...) * 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 * Once the syntax validation is done, the parser can provide a
* PreflightDocument (that inherits from PDDocument) This document process the * PreflightDocument (that inherits from PDDocument) This document process the
* end of PDF/A validation. * end of PDF/A validation.
*/ */
document.validate(); ValidationResult res = document.validate();
// Get validation result // Get validation result
return document.getResult().isValid(); return res.isValid();
} catch (ValidationException e) { } catch (ValidationException e) {
/* /*
* the parse method can throw a SyntaxValidationException if the PDF file can't * the parse method can throw a SyntaxValidationException if the PDF file can't
@@ -89,14 +94,17 @@ public class DXExporterFromA1 extends DXExporterFromA3 implements IZUGFeRDExport
} }
public DXExporterFromA1 setProfile(Profile p) { @Override
public DXExporterFromA1 setProfile(Profile p) {
return (DXExporterFromA1)super.setProfile(p); return (DXExporterFromA1)super.setProfile(p);
} }
public DXExporterFromA1 setProfile(String profileName) { @Override
public DXExporterFromA1 setProfile(String profileName) {
return (DXExporterFromA1)super.setProfile(profileName); return (DXExporterFromA1)super.setProfile(profileName);
} }
public boolean ensurePDFIsValid(final DataSource dataSource) throws IOException { @Override
public boolean ensurePDFIsValid(final DataSource dataSource) throws IOException {
if (!ignorePDFAErrors && !isValidA1(dataSource)) { if (!ignorePDFAErrors && !isValidA1(dataSource)) {
throw new IOException("File is not a valid PDF/A input file"); throw new IOException("File is not a valid PDF/A input file");
} }
@@ -108,32 +116,41 @@ public class DXExporterFromA1 extends DXExporterFromA3 implements IZUGFeRDExport
} }
public DXExporterFromA1 load(String pdfFilename) throws IOException { @Override
public DXExporterFromA1 load(String pdfFilename) throws IOException {
return (DXExporterFromA1) super.load(pdfFilename); return (DXExporterFromA1) super.load(pdfFilename);
} }
public DXExporterFromA1 load(byte[] pdfBinary) throws IOException { @Override
public DXExporterFromA1 load(byte[] pdfBinary) throws IOException {
return (DXExporterFromA1) super.load(pdfBinary); return (DXExporterFromA1) super.load(pdfBinary);
} }
public DXExporterFromA1 load(InputStream pdfSource) throws IOException{ @Override
public DXExporterFromA1 load(InputStream pdfSource) throws IOException{
return (DXExporterFromA1) super.load(pdfSource); return (DXExporterFromA1) super.load(pdfSource);
} }
public DXExporterFromA1 setCreator(String creator) { @Override
public DXExporterFromA1 setCreator(String creator) {
return (DXExporterFromA1) super.setCreator(creator); return (DXExporterFromA1) super.setCreator(creator);
} }
public DXExporterFromA1 setConformanceLevel(PDFAConformanceLevel newLevel) { @Override
public DXExporterFromA1 setConformanceLevel(PDFAConformanceLevel newLevel) {
return (DXExporterFromA1) super.setConformanceLevel(newLevel); return (DXExporterFromA1) super.setConformanceLevel(newLevel);
} }
public DXExporterFromA1 setProducer(String producer){ @Override
public DXExporterFromA1 setProducer(String producer){
return (DXExporterFromA1) super.setProducer(producer); return (DXExporterFromA1) super.setProducer(producer);
} }
public DXExporterFromA1 setZUGFeRDVersion(int version){ @Override
public DXExporterFromA1 setZUGFeRDVersion(int version){
return (DXExporterFromA1) super.setZUGFeRDVersion(version); return (DXExporterFromA1) super.setZUGFeRDVersion(version);
} }
public DXExporterFromA1 setXML(byte[] zugferdData) throws IOException{ @Override
public DXExporterFromA1 setXML(byte[] zugferdData) throws IOException{
return (DXExporterFromA1) super.setXML(zugferdData); return (DXExporterFromA1) super.setXML(zugferdData);
} }
public DXExporterFromA1 disableAutoClose(boolean disableAutoClose){ @Override
public DXExporterFromA1 disableAutoClose(boolean disableAutoClose){
return (DXExporterFromA1) super.disableAutoClose(disableAutoClose); return (DXExporterFromA1) super.disableAutoClose(disableAutoClose);
} }
public DXExporterFromA1 convertOnly() { public DXExporterFromA1 convertOnly() {

View File

@@ -20,16 +20,38 @@
*/ */
package org.mustangproject.ZUGFeRD; 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.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.PDMetadata;
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDMarkInfo; import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDMarkInfo;
import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot; import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot;
import org.apache.pdfbox.pdmodel.graphics.color.PDOutputIntent; import org.apache.pdfbox.pdmodel.graphics.color.PDOutputIntent;
import org.apache.pdfbox.preflight.utils.ByteArrayDataSource;
import org.apache.xmpbox.XMPMetadata; import org.apache.xmpbox.XMPMetadata;
import org.apache.xmpbox.schema.AdobePDFSchema; import org.apache.xmpbox.schema.AdobePDFSchema;
import org.apache.xmpbox.schema.DublinCoreSchema; import org.apache.xmpbox.schema.DublinCoreSchema;
@@ -43,16 +65,13 @@ import org.apache.xmpbox.xml.XmpSerializer;
import org.mustangproject.EStandard; import org.mustangproject.EStandard;
import org.mustangproject.FileAttachment; import org.mustangproject.FileAttachment;
import javax.activation.DataSource; import jakarta.activation.DataSource;
import javax.activation.FileDataSource; import jakarta.activation.FileDataSource;
import javax.xml.transform.TransformerException;
import java.io.*;
import java.util.*;
public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 { public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
protected PDFAConformanceLevel conformanceLevel = PDFAConformanceLevel.UNICODE; 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. * 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"; protected String despatchAdviceDocumentType = "DESPATCHADVICE";
private HashMap<String, byte[]> additionalXMLs = new HashMap<String, byte[]>();
private boolean attachZUGFeRDHeaders = true; private boolean attachZUGFeRDHeaders = true;
/** /**
@@ -119,7 +135,8 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* *
* @param pdfFilename filename of an PDF/A1 compliant document * @param pdfFilename filename of an PDF/A1 compliant document
*/ */
public DXExporterFromA3 load(String pdfFilename) throws IOException { @Override
public DXExporterFromA3 load(String pdfFilename) throws IOException {
ensurePDFIsValid(new FileDataSource(pdfFilename)); ensurePDFIsValid(new FileDataSource(pdfFilename));
try (FileInputStream pdf = new FileInputStream(pdfFilename)) { try (FileInputStream pdf = new FileInputStream(pdfFilename)) {
@@ -127,11 +144,13 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
} }
} }
public IXMLProvider getProvider() { @Override
public IXMLProvider getProvider() {
return xmlProvider; return xmlProvider;
} }
public DXExporterFromA3 setProfile(Profile p) { @Override
public DXExporterFromA3 setProfile(Profile p) {
this.profile = p; this.profile = p;
if (xmlProvider != null) { if (xmlProvider != null) {
xmlProvider.setProfile(p); xmlProvider.setProfile(p);
@@ -139,7 +158,8 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
return this; return this;
} }
public DXExporterFromA3 setProfile(String profilename) { @Override
public DXExporterFromA3 setProfile(String profilename) {
this.profile = Profiles.getByName(profilename); this.profile = Profiles.getByName(profilename);
if (xmlProvider != null) { if (xmlProvider != null) {
@@ -148,7 +168,8 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
return this; return this;
} }
public DXExporterFromA3 addAdditionalFile(String name, byte[] content) { @Override
public DXExporterFromA3 addAdditionalFile(String name, byte[] content) {
fileAttachments.add(new FileAttachment(name, "text/xml", "Supplement", content).setDescription("ZUGFeRD extension/additional data")); fileAttachments.add(new FileAttachment(name, "text/xml", "Supplement", content).setDescription("ZUGFeRD extension/additional data"));
return this; return this;
} }
@@ -161,9 +182,10 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* *
* @param pdfBinary binary of a PDF/A1 compliant document * @param pdfBinary binary of a PDF/A1 compliant document
*/ */
public DXExporterFromA3 load(byte[] pdfBinary) throws IOException { @Override
public DXExporterFromA3 load(byte[] pdfBinary) throws IOException {
ensurePDFIsValid(new ByteArrayDataSource(new ByteArrayInputStream(pdfBinary))); ensurePDFIsValid(new ByteArrayDataSource(new ByteArrayInputStream(pdfBinary)));
doc = PDDocument.load(pdfBinary); doc = Loader.loadPDF(pdfBinary);
return this; return this;
} }
@@ -171,11 +193,13 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
super(); super();
} }
public void attachFile(FileAttachment file) { @Override
public void attachFile(FileAttachment file) {
fileAttachments.add(file); fileAttachments.add(file);
} }
public void attachFile(String filename, byte[] data, String mimetype, String relation) { @Override
public void attachFile(String filename, byte[] data, String mimetype, String relation) {
FileAttachment fa = new FileAttachment(filename, mimetype, relation, data); FileAttachment fa = new FileAttachment(filename, mimetype, relation, data);
fileAttachments.add(fa); fileAttachments.add(fa);
} }
@@ -185,7 +209,8 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* @param ZUGFeRDfilename the pdf file name * @param ZUGFeRDfilename the pdf file name
* @throws IOException if anything is wrong in the target location * @throws IOException if anything is wrong in the target location
*/ */
public void export(String ZUGFeRDfilename) throws IOException { @Override
public void export(String ZUGFeRDfilename) throws IOException {
if (!documentPrepared) { if (!documentPrepared) {
prepareDocument(); prepareDocument();
} }
@@ -212,7 +237,8 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* @param output the OutputStream * @param output the OutputStream
* @throws IOException if anything is wrong in the OutputStream * @throws IOException if anything is wrong in the OutputStream
*/ */
public void export(OutputStream output) throws IOException { @Override
public void export(OutputStream output) throws IOException {
if (!documentPrepared) { if (!documentPrepared) {
prepareDocument(); prepareDocument();
} }
@@ -238,7 +264,8 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* @param data the binary data of the file/attachment * @param data the binary data of the file/attachment
* @throws IOException if anything is wrong with filename * @throws IOException if anything is wrong with filename
*/ */
public void PDFAttachGenericFile(String filename, String relationship, String description, @Override
public void PDFAttachGenericFile(String filename, String relationship, String description,
String subType, byte[] data) throws IOException { String subType, byte[] data) throws IOException {
PDFAttachGenericFile(this.doc, filename, relationship, description, subType, data); PDFAttachGenericFile(this.doc, filename, relationship, description, subType, data);
} }
@@ -255,7 +282,8 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* @param data the binary data of the file/attachment * @param data the binary data of the file/attachment
* @throws IOException if anything is wrong with filename * @throws IOException if anything is wrong with filename
*/ */
public void PDFAttachGenericFile(PDDocument doc, String filename, String relationship, String description, @Override
public void PDFAttachGenericFile(PDDocument doc, String filename, String relationship, String description,
String subType, byte[] data) throws IOException { String subType, byte[] data) throws IOException {
fileAttached = true; fileAttached = true;
@@ -274,7 +302,7 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
ef.setSize(data.length); ef.setSize(data.length);
ef.setCreationDate(new GregorianCalendar()); ef.setCreationDate(new GregorianCalendar());
ef.setModDate(GregorianCalendar.getInstance()); ef.setModDate(Calendar.getInstance());
fs.setEmbeddedFile(ef); fs.setEmbeddedFile(ef);
@@ -306,7 +334,7 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
doc.getDocumentCatalog().setNames(names); doc.getDocumentCatalog().setNames(names);
// AF entry (Array) in catalog with the FileSpec // 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)) { if ((AFEntry == null)) {
COSArray cosArray = new COSArray(); COSArray cosArray = new COSArray();
cosArray.add(fs); cosArray.add(fs);
@@ -332,7 +360,8 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* @param zugferdData XML data to be set as a byte array (XML file in raw form). * @param zugferdData XML data to be set as a byte array (XML file in raw form).
* @throws IOException (should not happen) * @throws IOException (should not happen)
*/ */
public DXExporterFromA3 setXML(byte[] zugferdData) throws IOException { @Override
public DXExporterFromA3 setXML(byte[] zugferdData) throws IOException {
CustomXMLProvider cus = new CustomXMLProvider(); CustomXMLProvider cus = new CustomXMLProvider();
// As of late 2022 the Delivery-X standard is not yet published. See specification: // 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 // Die digitale Ablösung des Papier-Lieferscheins, Version 1.1, April 2022
@@ -353,11 +382,13 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* *
* @param pdfSource source to read a PDF/A1 compliant document from * @param pdfSource source to read a PDF/A1 compliant document from
*/ */
public DXExporterFromA3 load(InputStream pdfSource) throws IOException { @Override
public DXExporterFromA3 load(InputStream pdfSource) throws IOException {
return load(readAllBytes(pdfSource)); return load(readAllBytes(pdfSource));
} }
public boolean ensurePDFIsValid(final DataSource dataSource) throws IOException { @Override
public boolean ensurePDFIsValid(final DataSource dataSource) throws IOException {
return true; return true;
} }
@@ -380,23 +411,27 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* <p> * <p>
* Feel free to pass "A" as new level if you know what you are doing :-) * Feel free to pass "A" as new level if you know what you are doing :-)
*/ */
public DXExporterFromA3 setConformanceLevel(PDFAConformanceLevel newLevel) { @Override
public DXExporterFromA3 setConformanceLevel(PDFAConformanceLevel newLevel) {
conformanceLevel = newLevel; conformanceLevel = newLevel;
return this; return this;
} }
public DXExporterFromA3 setCreator(String creator) { @Override
public DXExporterFromA3 setCreator(String creator) {
this.creator = creator; this.creator = creator;
return this; return this;
} }
public DXExporterFromA3 setCreatorTool(String creatorTool) { @Override
public DXExporterFromA3 setCreatorTool(String creatorTool) {
this.creatorTool = creatorTool; this.creatorTool = creatorTool;
return this; return this;
} }
public DXExporterFromA3 setProducer(String producer) { @Override
public DXExporterFromA3 setProducer(String producer) {
this.producer = producer; this.producer = producer;
return this; return this;
} }
@@ -415,7 +450,8 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
return this; return this;
} }
protected DXExporterFromA3 setAttachZUGFeRDHeaders(boolean attachHeaders) { @Override
protected DXExporterFromA3 setAttachZUGFeRDHeaders(boolean attachHeaders) {
this.attachZUGFeRDHeaders = attachHeaders; this.attachZUGFeRDHeaders = attachHeaders;
return this; return this;
} }
@@ -428,7 +464,8 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* *
* @param metadata the PDFbox XMPMetadata object * @param metadata the PDFbox XMPMetadata object
*/ */
protected void addXMP(XMPMetadata metadata) { @Override
protected void addXMP(XMPMetadata metadata) {
if (attachZUGFeRDHeaders) { if (attachZUGFeRDHeaders) {
// As of late 2022 the Delivery-X standard is not yet published. See specification: // As of late 2022 the Delivery-X standard is not yet published. See specification:
@@ -459,12 +496,14 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* <code>setZUGFeRDXMLData(byte[] zugferdData)</code> * <code>setZUGFeRDXMLData(byte[] zugferdData)</code>
* @throws IOException if anything is wrong with already loaded PDF * @throws IOException if anything is wrong with already loaded PDF
*/ */
public IExporter setTransaction(IExportableTransaction trans) throws IOException { @Override
public IExporter setTransaction(IExportableTransaction trans) throws IOException {
this.trans = trans; this.trans = trans;
return prepare(); return prepare();
} }
public IExporter prepare() throws IOException { @Override
public IExporter prepare() throws IOException {
prepareDocument(); prepareDocument();
xmlProvider.generateXML(trans); xmlProvider.generateXML(trans);
String filename = "cida.xml"; String filename = "cida.xml";
@@ -483,7 +522,8 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* Reads the XMPMetadata from the PDDocument, if it exists. * Reads the XMPMetadata from the PDDocument, if it exists.
* Otherwise creates XMPMetadata. * Otherwise creates XMPMetadata.
*/ */
protected XMPMetadata getXmpMetadata() throws IOException { @Override
protected XMPMetadata getXmpMetadata() throws IOException {
PDMetadata meta = doc.getDocumentCatalog().getMetadata(); PDMetadata meta = doc.getDocumentCatalog().getMetadata();
if ((meta != null) && (meta.getLength() > 0)) { if ((meta != null) && (meta.getLength() > 0)) {
try { try {
@@ -496,7 +536,8 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
return XMPMetadata.createXMPMetadata(); return XMPMetadata.createXMPMetadata();
} }
protected byte[] serializeXmpMetadata(XMPMetadata xmpMetadata) throws TransformerException { @Override
protected byte[] serializeXmpMetadata(XMPMetadata xmpMetadata) throws TransformerException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream(); ByteArrayOutputStream buffer = new ByteArrayOutputStream();
new XmpSerializer().serialize(xmpMetadata, buffer, true); // see https://github.com/ZUGFeRD/mustangproject/issues/44 new XmpSerializer().serialize(xmpMetadata, buffer, true); // see https://github.com/ZUGFeRD/mustangproject/issues/44
return buffer.toByteArray(); return buffer.toByteArray();
@@ -506,7 +547,8 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* Sets the producer if the overwrite flag is set or the producer is not already set. * 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. * Sets the PDFVersion to 1.4 if the field is empty.
*/ */
protected void writeAdobePDFSchema(XMPMetadata xmp) { @Override
protected void writeAdobePDFSchema(XMPMetadata xmp) {
AdobePDFSchema pdf = getAdobePDFSchema(xmp); AdobePDFSchema pdf = getAdobePDFSchema(xmp);
if (overwrite || isEmpty(pdf.getProducer())) if (overwrite || isEmpty(pdf.getProducer()))
pdf.setProducer(producer); pdf.setProducer(producer);
@@ -516,7 +558,8 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* Returns the AdobePDFSchema from the XMPMetadata if it exists. * 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. * If the overwrite flag is set or no AdobePDFSchema exists in the XMPMetadata, it is created, added and returned.
*/ */
protected AdobePDFSchema getAdobePDFSchema(XMPMetadata xmp) { @Override
protected AdobePDFSchema getAdobePDFSchema(XMPMetadata xmp) {
AdobePDFSchema pdf = xmp.getAdobePDFSchema(); AdobePDFSchema pdf = xmp.getAdobePDFSchema();
if (pdf != null) if (pdf != null)
if (overwrite) if (overwrite)
@@ -526,7 +569,8 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
return xmp.createAndAddAdobePDFSchema(); return xmp.createAndAddAdobePDFSchema();
} }
protected void writePDFAIdentificationSchema(XMPMetadata xmp) { @Override
protected void writePDFAIdentificationSchema(XMPMetadata xmp) {
PDFAIdentificationSchema pdfaid = getPDFAIdentificationSchema(xmp); PDFAIdentificationSchema pdfaid = getPDFAIdentificationSchema(xmp);
if (overwrite || isEmpty(pdfaid.getConformance())) { if (overwrite || isEmpty(pdfaid.getConformance())) {
try { try {
@@ -541,17 +585,19 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
pdfaid.setPart(3); pdfaid.setPart(3);
} }
protected PDFAIdentificationSchema getPDFAIdentificationSchema(XMPMetadata xmp) { @Override
PDFAIdentificationSchema pdfaid = xmp.getPDFIdentificationSchema(); protected PDFAIdentificationSchema getPDFAIdentificationSchema(XMPMetadata xmp) {
PDFAIdentificationSchema pdfaid = xmp.getPDFAIdentificationSchema();
if (pdfaid != null) if (pdfaid != null)
if (overwrite) if (overwrite)
xmp.removeSchema(pdfaid); xmp.removeSchema(pdfaid);
else else
return pdfaid; return pdfaid;
return xmp.createAndAddPFAIdentificationSchema(); return xmp.createAndAddPDFAIdentificationSchema();
} }
protected void writeDublinCoreSchema(XMPMetadata xmp) { @Override
protected void writeDublinCoreSchema(XMPMetadata xmp) {
DublinCoreSchema dc = getDublinCoreSchema(xmp); DublinCoreSchema dc = getDublinCoreSchema(xmp);
if (dc.getFormat() == null) if (dc.getFormat() == null)
dc.setFormat("application/pdf"); dc.setFormat("application/pdf");
@@ -574,7 +620,8 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
} }
} }
protected DublinCoreSchema getDublinCoreSchema(XMPMetadata xmp) { @Override
protected DublinCoreSchema getDublinCoreSchema(XMPMetadata xmp) {
DublinCoreSchema dc = xmp.getDublinCoreSchema(); DublinCoreSchema dc = xmp.getDublinCoreSchema();
if (dc != null) if (dc != null)
if (overwrite) if (overwrite)
@@ -584,15 +631,17 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
return xmp.createAndAddDublinCoreSchema(); return xmp.createAndAddDublinCoreSchema();
} }
protected void writeXMLBasicSchema(XMPMetadata xmp) { @Override
protected void writeXMLBasicSchema(XMPMetadata xmp) {
XMPBasicSchema xsb = getXmpBasicSchema(xmp); XMPBasicSchema xsb = getXmpBasicSchema(xmp);
if (overwrite || isEmpty(xsb.getCreatorTool()) || "UnknownApplication".equals(xsb.getCreatorTool())) if (overwrite || isEmpty(xsb.getCreatorTool()) || "UnknownApplication".equals(xsb.getCreatorTool()))
xsb.setCreatorTool(creatorTool); xsb.setCreatorTool(creatorTool);
if (overwrite || xsb.getCreateDate() == null) if (overwrite || xsb.getCreateDate() == null)
xsb.setCreateDate(GregorianCalendar.getInstance()); xsb.setCreateDate(Calendar.getInstance());
} }
protected XMPBasicSchema getXmpBasicSchema(XMPMetadata xmp) { @Override
protected XMPBasicSchema getXmpBasicSchema(XMPMetadata xmp) {
XMPBasicSchema xsb = xmp.getXMPBasicSchema(); XMPBasicSchema xsb = xmp.getXMPBasicSchema();
if (xsb != null) if (xsb != null)
if (overwrite) if (overwrite)
@@ -602,7 +651,8 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
return xmp.createAndAddXMPBasicSchema(); return xmp.createAndAddXMPBasicSchema();
} }
protected void writeDocumentInformation() { @Override
protected void writeDocumentInformation() {
String fullProducer = producer + " (via mustangproject.org " + Version.VERSION + ")"; String fullProducer = producer + " (via mustangproject.org " + Version.VERSION + ")";
PDDocumentInformation info = doc.getDocumentInformation(); PDDocumentInformation info = doc.getDocumentInformation();
if (overwrite || info.getCreationDate() == null) if (overwrite || info.getCreationDate() == null)
@@ -624,7 +674,8 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
/** /**
* Adds an OutputIntent and the sRGB color profile if no OutputIntent exist * Adds an OutputIntent and the sRGB color profile if no OutputIntent exist
*/ */
protected void addSRGBOutputIntend() throws IOException { @Override
protected void addSRGBOutputIntend() throws IOException {
if (!doc.getDocumentCatalog().getOutputIntents().isEmpty()) { if (!doc.getDocumentCatalog().getOutputIntents().isEmpty()) {
return; return;
} }
@@ -647,7 +698,8 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
/** /**
* Adds a MarkInfo element to the PDF if it doesn't already exist and sets it as marked. * Adds a MarkInfo element to the PDF if it doesn't already exist and sets it as marked.
*/ */
protected void setMarked() { @Override
protected void setMarked() {
PDDocumentCatalog catalog = doc.getDocumentCatalog(); PDDocumentCatalog catalog = doc.getDocumentCatalog();
if (catalog.getMarkInfo() == null) { if (catalog.getMarkInfo() == null) {
catalog.setMarkInfo(new PDMarkInfo(doc.getPages().getCOSObject())); catalog.setMarkInfo(new PDMarkInfo(doc.getPages().getCOSObject()));
@@ -658,7 +710,8 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
/** /**
* Adds a StructureTreeRoot element to the PDF if it doesn't already exist. * Adds a StructureTreeRoot element to the PDF if it doesn't already exist.
*/ */
protected void addStructureTreeRoot() { @Override
protected void addStructureTreeRoot() {
if (doc.getDocumentCatalog().getStructureTreeRoot() == null) { if (doc.getDocumentCatalog().getStructureTreeRoot() == null) {
doc.getDocumentCatalog().setStructureTreeRoot(new PDStructureTreeRoot()); doc.getDocumentCatalog().setStructureTreeRoot(new PDStructureTreeRoot());
} }
@@ -668,19 +721,22 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
/** /**
* @return if pdf file will be automatically closed after adding ZF * @return if pdf file will be automatically closed after adding ZF
*/ */
public boolean isAutoCloseDisabled() { @Override
public boolean isAutoCloseDisabled() {
return disableAutoClose; return disableAutoClose;
} }
/** /**
* @param disableAutoClose prevent PDF file from being closed after adding ZF * @param disableAutoClose prevent PDF file from being closed after adding ZF
*/ */
public DXExporterFromA3 disableAutoClose(boolean disableAutoClose) { @Override
public DXExporterFromA3 disableAutoClose(boolean disableAutoClose) {
this.disableAutoClose = disableAutoClose; this.disableAutoClose = disableAutoClose;
return this; return this;
} }
protected void setXMLProvider(IXMLProvider p) { @Override
protected void setXMLProvider(IXMLProvider p) {
this.xmlProvider = p; this.xmlProvider = p;
if (profile != null) { if (profile != null) {
xmlProvider.setProfile(profile); xmlProvider.setProfile(profile);

View File

@@ -20,31 +20,10 @@
*/ */
package org.mustangproject.ZUGFeRD; package org.mustangproject.ZUGFeRD;
import org.apache.pdfbox.cos.*; import java.io.File;
import org.apache.pdfbox.io.IOUtils; import java.io.FileOutputStream;
import org.apache.pdfbox.pdmodel.*; import java.io.IOException;
import org.apache.pdfbox.pdmodel.common.PDMetadata; import java.io.OutputStream;
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;
public class EinLieferscheinExporter implements IExporter { public class EinLieferscheinExporter implements IExporter {
IXMLProvider xmlProvider; IXMLProvider xmlProvider;

View File

@@ -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; }
}

View File

@@ -140,6 +140,11 @@ public interface IExportableTransaction {
return null; return null;
} }
default IZUGFeRDCashDiscount[] getCashDiscounts() { return null; }
/***
* @return the invoice line items with the positions
*/
IZUGFeRDExportableItem[] getZFItems(); IZUGFeRDExportableItem[] getZFItems();
/** /**
@@ -445,11 +450,19 @@ public interface IExportableTransaction {
* *
* @return the IZUGFeRDExportableTradeParty delivery address * @return the IZUGFeRDExportableTradeParty delivery address
*/ */
default IZUGFeRDExportableTradeParty getDeliveryAddress() { default IZUGFeRDExportableTradeParty getDeliveryAddress() {
return null; 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 * specifies the document level delivery period, will be included in a
* BillingSpecifiedPeriod element * BillingSpecifiedPeriod element
@@ -517,4 +530,7 @@ public interface IExportableTransaction {
return null; return null;
} }
default String getCreditorReferenceID() {
return null;
}
} }

View File

@@ -18,7 +18,6 @@
*********************************************************************** */ *********************************************************************** */
package org.mustangproject.ZUGFeRD; package org.mustangproject.ZUGFeRD;
import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;

View File

@@ -15,10 +15,10 @@
*/ */
package org.mustangproject.ZUGFeRD; package org.mustangproject.ZUGFeRD;
import org.mustangproject.ZUGFeRD.model.TaxCategoryCodeTypeConstants;
import java.math.BigDecimal; import java.math.BigDecimal;
import org.mustangproject.ZUGFeRD.model.TaxCategoryCodeTypeConstants;
/** /**
* The interface for allowances or charges, to be used by the pullprovider * The interface for allowances or charges, to be used by the pullprovider
* @author AlexanderSchmidt * @author AlexanderSchmidt
@@ -44,6 +44,12 @@ public interface IZUGFeRDAllowanceCharge {
*/ */
String getReason(); String getReason();
/***
* get the code for the allowance/charge
* @return the code
*/
String getReasonCode();
/*** /***
* get the applicable tax percentage for the allowance/charge * get the applicable tax percentage for the allowance/charge
* @return the percentage * @return the percentage

View File

@@ -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();
}

View File

@@ -26,12 +26,13 @@ package org.mustangproject.ZUGFeRD;
* @author jstaerk * @author jstaerk
* */ * */
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.mustangproject.Item;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Date; import java.util.Date;
import org.mustangproject.Item;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
@JsonDeserialize(as = Item.class) @JsonDeserialize(as = Item.class)
public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
IZUGFeRDExportableProduct getProduct(); IZUGFeRDExportableProduct getProduct();
@@ -91,12 +92,22 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
/*** /***
* the ID of an additionally referenced document for this item * the ID of an additionally referenced document for this item
* @deprecated use {@link #getAdditionalReferences()} instead.
* @return the id as string * @return the id as string
*/ */
@Deprecated
default String getAdditionalReferencedDocumentID() { default String getAdditionalReferencedDocumentID() {
return null; 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 * allows to specify multiple(!) referenced documents along with e.g. their typecodes
@@ -140,6 +151,6 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
*/ */
default IZUGFeRDAllowanceCharge[] getItemTotalAllowances() { default IZUGFeRDAllowanceCharge[] getItemTotalAllowances() {
return null; return null;
}; }
} }

View File

@@ -21,6 +21,7 @@
package org.mustangproject.ZUGFeRD; package org.mustangproject.ZUGFeRD;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.HashMap;
import org.mustangproject.ZUGFeRD.model.TaxCategoryCodeTypeConstants; import org.mustangproject.ZUGFeRD.model.TaxCategoryCodeTypeConstants;
@@ -156,4 +157,19 @@ public interface IZUGFeRDExportableProduct {
} }
return null; 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; }
} }

View File

@@ -20,8 +20,6 @@
*/ */
package org.mustangproject.ZUGFeRD; package org.mustangproject.ZUGFeRD;
import org.mustangproject.TradeParty;
/** /**
* Mustangproject's ZUGFeRD implementation neccessary interface for ZUGFeRD exporter Licensed under the APLv2 * 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(); String getName();
/**
* @return description, e.g. if it's a small company
*/
default String getDescription() { return null; }
/** /**
* Postal code of the recipient * Postal code of the recipient
* *

View File

@@ -18,11 +18,13 @@
*********************************************************************** */ *********************************************************************** */
package org.mustangproject.ZUGFeRD; package org.mustangproject.ZUGFeRD;
import javax.activation.DataSource;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import jakarta.activation.DataSource;
import org.mustangproject.FileAttachment;
public interface IZUGFeRDExporter extends Closeable, IExporter { public interface IZUGFeRDExporter extends Closeable, IExporter {
/** /**
* factory: loads a PDF file and returns an appropriate exporter * 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 getNamespaceForVersion(int ver);
public String getPrefixForVersion(int ver) ; public String getPrefixForVersion(int ver) ;
public IZUGFeRDExporter disableAutoClose(boolean disableAutoClose); public IZUGFeRDExporter disableAutoClose(boolean disableAutoClose);
public void attachFile(FileAttachment file);
public void attachFile(String filename, byte[] data, String mimetype, String relation);
public IXMLProvider getProvider(); public IXMLProvider getProvider();
} }

View File

@@ -18,22 +18,19 @@
*********************************************************************** */ *********************************************************************** */
package org.mustangproject.ZUGFeRD; package org.mustangproject.ZUGFeRD;
import javax.activation.DataSource; import org.mustangproject.SchemedID;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
public interface IZUGFeRDLegalOrganisation { 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();
} }

View File

@@ -18,12 +18,15 @@
*********************************************************************** */ *********************************************************************** */
package org.mustangproject.ZUGFeRD; package org.mustangproject.ZUGFeRD;
import com.fasterxml.jackson.annotation.JsonIgnore;
public interface IZUGFeRDTradeSettlement { public interface IZUGFeRDTradeSettlement {
/*** /***
* gets the applicableHeaderTradeSettlement * gets the applicableHeaderTradeSettlement
* @return zf2 xml * @return zf2 xml
*/ */
@JsonIgnore
String getSettlementXML(); String getSettlementXML();
@@ -31,6 +34,7 @@ public interface IZUGFeRDTradeSettlement {
* gets the applicableHeaderTradePayment * gets the applicableHeaderTradePayment
* @return zf2 xml * @return zf2 xml
*/ */
@JsonIgnore
default String getPaymentXML() { default String getPaymentXML() {
return null; return null;
} }

View File

@@ -22,35 +22,30 @@ import org.mustangproject.XMLTools;
public interface IZUGFeRDTradeSettlementDebit extends IZUGFeRDTradeSettlement { public interface IZUGFeRDTradeSettlementDebit extends IZUGFeRDTradeSettlement {
@Override @Override
default String getSettlementXML() { default String getSettlementXML() {
String xml = "<ram:SpecifiedTradeSettlementPaymentMeans>" String xml = "<ram:SpecifiedTradeSettlementPaymentMeans>"
+ "<ram:TypeCode>59</ram:TypeCode>" + "<ram:TypeCode>59</ram:TypeCode>"
+ "<ram:Information>SEPA direct debit</ram:Information>"
+ "<ram:PayerPartyDebtorFinancialAccount>" + "<ram:PayerPartyDebtorFinancialAccount>"
+ "<ram:IBANID>"+XMLTools.encodeXML(getIBAN())+"</ram:IBANID>" + "<ram:IBANID>" + XMLTools.encodeXML(getIBAN()) + "</ram:IBANID>"
+ "</ram:PayerPartyDebtorFinancialAccount>"; + "</ram:PayerPartyDebtorFinancialAccount>";
xml += "</ram:SpecifiedTradeSettlementPaymentMeans>"; xml += "</ram:SpecifiedTradeSettlementPaymentMeans>";
return xml; return xml;
} }
@Override @Override
default String getPaymentXML() { default String getPaymentXML() {
return "<ram:DirectDebitMandateID>"+XMLTools.encodeXML(getMandate())+"</ram:DirectDebitMandateID>"; return "<ram:DirectDebitMandateID>" + XMLTools.encodeXML(getMandate()) + "</ram:DirectDebitMandateID>";
} }
/*** /***
* @return IBAN of the debtor (optional) * @return IBAN of the debtor (optional)
*/ */
String getIBAN(); String getIBAN();
/*** /***
* @return sepa direct debit mandate reference * @return sepa direct debit mandate reference
*/ */

View File

@@ -18,6 +18,7 @@
*********************************************************************** */ *********************************************************************** */
package org.mustangproject.ZUGFeRD; package org.mustangproject.ZUGFeRD;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.mustangproject.XMLTools; import org.mustangproject.XMLTools;
public interface IZUGFeRDTradeSettlementPayment extends IZUGFeRDTradeSettlement { public interface IZUGFeRDTradeSettlementPayment extends IZUGFeRDTradeSettlement {
@@ -27,6 +28,7 @@ public interface IZUGFeRDTradeSettlementPayment extends IZUGFeRDTradeSettlement
* *
* @return payment information text * @return payment information text
*/ */
@JsonIgnore
default String getOwnPaymentInfoText() { default String getOwnPaymentInfoText() {
return null; return null;
} }
@@ -59,6 +61,7 @@ public interface IZUGFeRDTradeSettlementPayment extends IZUGFeRDTradeSettlement
@Override @Override
@JsonIgnore
default String getSettlementXML() { default String getSettlementXML() {
String accountNameStr=""; String accountNameStr="";
if (getAccountName()!=null) { if (getAccountName()!=null) {

View File

@@ -1,6 +1,7 @@
package org.mustangproject.ZUGFeRD; package org.mustangproject.ZUGFeRD;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode;
/*** /***
* the linecalculator does the math within an item line, and e.g. calculates quantity*price. * 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(); BigDecimal vatPercent = null;
if (vatPercent == null) if (currentItem.getProduct()!=null) {
vatPercent = currentItem.getProduct().getVATPercent();
}
if (vatPercent == null) {
vatPercent = BigDecimal.ZERO; vatPercent = BigDecimal.ZERO;
}
BigDecimal multiplicator = vatPercent.divide(BigDecimal.valueOf(100)); BigDecimal multiplicator = vatPercent.divide(BigDecimal.valueOf(100));
priceGross = currentItem.getPrice(); // see https://github.com/ZUGFeRD/mustangproject/issues/159 priceGross = currentItem.getPrice(); // see https://github.com/ZUGFeRD/mustangproject/issues/159
price = priceGross.subtract(allowance).add(charge); price = priceGross.subtract(allowance).add(charge);
itemTotalNetAmount = currentItem.getQuantity().multiply(getPrice()).divide(currentItem.getBasisQuantity()) itemTotalNetAmount = currentItem.getQuantity().multiply(getPrice()).divide(currentItem.getBasisQuantity(), 18, RoundingMode.HALF_UP)
.subtract(allowanceItemTotal).setScale(2, BigDecimal.ROUND_HALF_UP); .subtract(allowanceItemTotal).setScale(2, RoundingMode.HALF_UP);
itemTotalVATAmount = itemTotalNetAmount.multiply(multiplicator); itemTotalVATAmount = itemTotalNetAmount.multiply(multiplicator);
} }

View File

@@ -19,31 +19,35 @@
package org.mustangproject.ZUGFeRD; 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.IOException;
import java.io.InputStream; 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; protected boolean ignorePDFAErrors = false;
public OXExporterFromA1 ignorePDFAErrors() { @Override
public OXExporterFromA1 ignorePDFAErrors() {
this.ignorePDFAErrors = true; this.ignorePDFAErrors = true;
return this; return this;
} }
private static boolean isValidA1(DataSource dataSource) throws IOException { 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 * internal helper function: get namespace for order-x
* @param ver the order-x version * @param ver the order-x version
* @return the URN of the namespace * @return the URN of the namespace
*/ */
public String getNamespaceForVersion(int ver) { @Override
public String getNamespaceForVersion(int ver) {
return "urn:factur-x:pdfa:CrossIndustryDocument:1p0#"; return "urn:factur-x:pdfa:CrossIndustryDocument:1p0#";
} }
/*** /***
@@ -51,7 +55,8 @@ public class OXExporterFromA1 extends OXExporterFromA3 implements IZUGFeRDExport
* @param ver the ox version * @param ver the ox version
* @return the namespace prefix as string, without colon * @return the namespace prefix as string, without colon
*/ */
public String getPrefixForVersion(int ver) { @Override
public String getPrefixForVersion(int ver) {
return "fx"; return "fx";
} }
@@ -61,19 +66,19 @@ public class OXExporterFromA1 extends OXExporterFromA3 implements IZUGFeRDExport
* NonSequentialParser. Some additional controls are present to check a set of * NonSequentialParser. Some additional controls are present to check a set of
* PDF/A requirements. (Stream length consistency, EOL after some Keyword...) * 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 * Once the syntax validation is done, the parser can provide a
* PreflightDocument (that inherits from PDDocument) This document process the * PreflightDocument (that inherits from PDDocument) This document process the
* end of PDF/A validation. * end of PDF/A validation.
*/ */
document.validate(); ValidationResult res = document.validate();
// Get validation result // Get validation result
return document.getResult().isValid(); return res.isValid();
} catch (ValidationException e) { } catch (ValidationException e) {
/* /*
* the parse method can throw a SyntaxValidationException if the PDF file can't * the parse method can throw a SyntaxValidationException if the PDF file can't
@@ -85,14 +90,17 @@ public class OXExporterFromA1 extends OXExporterFromA3 implements IZUGFeRDExport
} }
public OXExporterFromA1 setProfile(Profile p) { @Override
public OXExporterFromA1 setProfile(Profile p) {
return (OXExporterFromA1)super.setProfile(p); return (OXExporterFromA1)super.setProfile(p);
} }
public OXExporterFromA1 setProfile(String profileName) { @Override
public OXExporterFromA1 setProfile(String profileName) {
return (OXExporterFromA1)super.setProfile(profileName); return (OXExporterFromA1)super.setProfile(profileName);
} }
public boolean ensurePDFIsValid(final DataSource dataSource) throws IOException { @Override
public boolean ensurePDFIsValid(final DataSource dataSource) throws IOException {
if (!ignorePDFAErrors && !isValidA1(dataSource)) { if (!ignorePDFAErrors && !isValidA1(dataSource)) {
throw new IOException("File is not a valid PDF/A input file"); throw new IOException("File is not a valid PDF/A input file");
} }
@@ -104,32 +112,41 @@ public class OXExporterFromA1 extends OXExporterFromA3 implements IZUGFeRDExport
} }
public OXExporterFromA1 load(String pdfFilename) throws IOException { @Override
public OXExporterFromA1 load(String pdfFilename) throws IOException {
return (OXExporterFromA1) super.load(pdfFilename); return (OXExporterFromA1) super.load(pdfFilename);
} }
public OXExporterFromA1 load(byte[] pdfBinary) throws IOException { @Override
public OXExporterFromA1 load(byte[] pdfBinary) throws IOException {
return (OXExporterFromA1) super.load(pdfBinary); return (OXExporterFromA1) super.load(pdfBinary);
} }
public OXExporterFromA1 load(InputStream pdfSource) throws IOException{ @Override
public OXExporterFromA1 load(InputStream pdfSource) throws IOException{
return (OXExporterFromA1) super.load(pdfSource); return (OXExporterFromA1) super.load(pdfSource);
} }
public OXExporterFromA1 setCreator(String creator) { @Override
public OXExporterFromA1 setCreator(String creator) {
return (OXExporterFromA1) super.setCreator(creator); return (OXExporterFromA1) super.setCreator(creator);
} }
public OXExporterFromA1 setConformanceLevel(PDFAConformanceLevel newLevel) { @Override
public OXExporterFromA1 setConformanceLevel(PDFAConformanceLevel newLevel) {
return (OXExporterFromA1) super.setConformanceLevel(newLevel); return (OXExporterFromA1) super.setConformanceLevel(newLevel);
} }
public OXExporterFromA1 setProducer(String producer){ @Override
public OXExporterFromA1 setProducer(String producer){
return (OXExporterFromA1) super.setProducer(producer); return (OXExporterFromA1) super.setProducer(producer);
} }
public OXExporterFromA1 setZUGFeRDVersion(int version){ @Override
public OXExporterFromA1 setZUGFeRDVersion(int version){
return (OXExporterFromA1) super.setZUGFeRDVersion(version); return (OXExporterFromA1) super.setZUGFeRDVersion(version);
} }
public OXExporterFromA1 setXML(byte[] zugferdData) throws IOException{ @Override
public OXExporterFromA1 setXML(byte[] zugferdData) throws IOException{
return (OXExporterFromA1) super.setXML(zugferdData); return (OXExporterFromA1) super.setXML(zugferdData);
} }
public OXExporterFromA1 disableAutoClose(boolean disableAutoClose){ @Override
public OXExporterFromA1 disableAutoClose(boolean disableAutoClose){
return (OXExporterFromA1) super.disableAutoClose(disableAutoClose); return (OXExporterFromA1) super.disableAutoClose(disableAutoClose);
} }
public OXExporterFromA1 convertOnly() { public OXExporterFromA1 convertOnly() {

View File

@@ -20,20 +20,38 @@
*/ */
package org.mustangproject.ZUGFeRD; 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.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.PDMetadata;
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDMarkInfo; import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDMarkInfo;
import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot; 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.pdmodel.graphics.color.PDOutputIntent;
import org.apache.pdfbox.preflight.utils.ByteArrayDataSource;
import org.apache.xmpbox.XMPMetadata; import org.apache.xmpbox.XMPMetadata;
import org.apache.xmpbox.schema.AdobePDFSchema; import org.apache.xmpbox.schema.AdobePDFSchema;
import org.apache.xmpbox.schema.DublinCoreSchema; import org.apache.xmpbox.schema.DublinCoreSchema;
@@ -47,16 +65,13 @@ import org.apache.xmpbox.xml.XmpSerializer;
import org.mustangproject.EStandard; import org.mustangproject.EStandard;
import org.mustangproject.FileAttachment; import org.mustangproject.FileAttachment;
import javax.activation.DataSource; import jakarta.activation.DataSource;
import javax.activation.FileDataSource; import jakarta.activation.FileDataSource;
import javax.xml.transform.TransformerException;
import java.io.*;
import java.util.*;
public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 { public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
protected PDFAConformanceLevel conformanceLevel = PDFAConformanceLevel.UNICODE; 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. * 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"; protected String orderXDocumentType = "ORDER";
private HashMap<String, byte[]> additionalXMLs = new HashMap<String, byte[]>();
private boolean attachZUGFeRDHeaders = true; private boolean attachZUGFeRDHeaders = true;
/** /**
@@ -123,7 +135,8 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* *
* @param pdfFilename filename of an PDF/A1 compliant document * @param pdfFilename filename of an PDF/A1 compliant document
*/ */
public OXExporterFromA3 load(String pdfFilename) throws IOException { @Override
public OXExporterFromA3 load(String pdfFilename) throws IOException {
ensurePDFIsValid(new FileDataSource(pdfFilename)); ensurePDFIsValid(new FileDataSource(pdfFilename));
try (FileInputStream pdf = new FileInputStream(pdfFilename)) { try (FileInputStream pdf = new FileInputStream(pdfFilename)) {
@@ -131,11 +144,13 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
} }
} }
public IXMLProvider getProvider() { @Override
public IXMLProvider getProvider() {
return xmlProvider; return xmlProvider;
} }
public OXExporterFromA3 setProfile(Profile p) { @Override
public OXExporterFromA3 setProfile(Profile p) {
this.profile = p; this.profile = p;
if (xmlProvider != null) { if (xmlProvider != null) {
xmlProvider.setProfile(p); xmlProvider.setProfile(p);
@@ -143,7 +158,8 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
return this; return this;
} }
public OXExporterFromA3 setProfile(String profilename) { @Override
public OXExporterFromA3 setProfile(String profilename) {
this.profile = Profiles.getByName(profilename); this.profile = Profiles.getByName(profilename);
if (xmlProvider != null) { if (xmlProvider != null) {
@@ -152,7 +168,8 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
return this; return this;
} }
public OXExporterFromA3 addAdditionalFile(String name, byte[] content) { @Override
public OXExporterFromA3 addAdditionalFile(String name, byte[] content) {
fileAttachments.add(new FileAttachment(name, "text/xml", "Supplement", content).setDescription("ZUGFeRD extension/additional data")); fileAttachments.add(new FileAttachment(name, "text/xml", "Supplement", content).setDescription("ZUGFeRD extension/additional data"));
return this; return this;
} }
@@ -165,9 +182,10 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* *
* @param pdfBinary binary of a PDF/A1 compliant document * @param pdfBinary binary of a PDF/A1 compliant document
*/ */
public OXExporterFromA3 load(byte[] pdfBinary) throws IOException { @Override
public OXExporterFromA3 load(byte[] pdfBinary) throws IOException {
ensurePDFIsValid(new ByteArrayDataSource(new ByteArrayInputStream(pdfBinary))); ensurePDFIsValid(new ByteArrayDataSource(new ByteArrayInputStream(pdfBinary)));
doc = PDDocument.load(pdfBinary); doc = Loader.loadPDF(pdfBinary);
return this; return this;
} }
@@ -175,11 +193,13 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
super(); super();
} }
public void attachFile(FileAttachment file) { @Override
public void attachFile(FileAttachment file) {
fileAttachments.add(file); fileAttachments.add(file);
} }
public void attachFile(String filename, byte[] data, String mimetype, String relation) { @Override
public void attachFile(String filename, byte[] data, String mimetype, String relation) {
FileAttachment fa = new FileAttachment(filename, mimetype, relation, data); FileAttachment fa = new FileAttachment(filename, mimetype, relation, data);
fileAttachments.add(fa); fileAttachments.add(fa);
} }
@@ -189,7 +209,8 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* @param ZUGFeRDfilename the pdf file name * @param ZUGFeRDfilename the pdf file name
* @throws IOException if anything is wrong in the target location * @throws IOException if anything is wrong in the target location
*/ */
public void export(String ZUGFeRDfilename) throws IOException { @Override
public void export(String ZUGFeRDfilename) throws IOException {
if (!documentPrepared) { if (!documentPrepared) {
prepareDocument(); prepareDocument();
} }
@@ -216,7 +237,8 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* @param output the OutputStream * @param output the OutputStream
* @throws IOException if anything is wrong in the OutputStream * @throws IOException if anything is wrong in the OutputStream
*/ */
public void export(OutputStream output) throws IOException { @Override
public void export(OutputStream output) throws IOException {
if (!documentPrepared) { if (!documentPrepared) {
prepareDocument(); prepareDocument();
} }
@@ -242,7 +264,8 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* @param data the binary data of the file/attachment * @param data the binary data of the file/attachment
* @throws java.io.IOException if anything is wrong with filename * @throws java.io.IOException if anything is wrong with filename
*/ */
public void PDFAttachGenericFile(String filename, String relationship, String description, @Override
public void PDFAttachGenericFile(String filename, String relationship, String description,
String subType, byte[] data) throws IOException { String subType, byte[] data) throws IOException {
PDFAttachGenericFile(this.doc, filename, relationship, description, subType, data); PDFAttachGenericFile(this.doc, filename, relationship, description, subType, data);
} }
@@ -259,7 +282,8 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* @param data the binary data of the file/attachment * @param data the binary data of the file/attachment
* @throws IOException if anything is wrong with filename * @throws IOException if anything is wrong with filename
*/ */
public void PDFAttachGenericFile(PDDocument doc, String filename, String relationship, String description, @Override
public void PDFAttachGenericFile(PDDocument doc, String filename, String relationship, String description,
String subType, byte[] data) throws IOException { String subType, byte[] data) throws IOException {
fileAttached = true; fileAttached = true;
@@ -278,7 +302,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
ef.setSize(data.length); ef.setSize(data.length);
ef.setCreationDate(new GregorianCalendar()); ef.setCreationDate(new GregorianCalendar());
ef.setModDate(GregorianCalendar.getInstance()); ef.setModDate(Calendar.getInstance());
fs.setEmbeddedFile(ef); fs.setEmbeddedFile(ef);
@@ -310,7 +334,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
doc.getDocumentCatalog().setNames(names); doc.getDocumentCatalog().setNames(names);
// AF entry (Array) in catalog with the FileSpec // 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)) { if ((AFEntry == null)) {
COSArray cosArray = new COSArray(); COSArray cosArray = new COSArray();
cosArray.add(fs); cosArray.add(fs);
@@ -336,7 +360,8 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* @param zugferdData XML data to be set as a byte array (XML file in raw form). * @param zugferdData XML data to be set as a byte array (XML file in raw form).
* @throws IOException (should not happen) * @throws IOException (should not happen)
*/ */
public OXExporterFromA3 setXML(byte[] zugferdData) throws IOException { @Override
public OXExporterFromA3 setXML(byte[] zugferdData) throws IOException {
CustomXMLProvider cus = new CustomXMLProvider(); CustomXMLProvider cus = new CustomXMLProvider();
cus.setXML(zugferdData); cus.setXML(zugferdData);
this.setXMLProvider(cus); this.setXMLProvider(cus);
@@ -351,11 +376,13 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* *
* @param pdfSource source to read a PDF/A1 compliant document from * @param pdfSource source to read a PDF/A1 compliant document from
*/ */
public OXExporterFromA3 load(InputStream pdfSource) throws IOException { @Override
public OXExporterFromA3 load(InputStream pdfSource) throws IOException {
return load(readAllBytes(pdfSource)); return load(readAllBytes(pdfSource));
} }
public boolean ensurePDFIsValid(final DataSource dataSource) throws IOException { @Override
public boolean ensurePDFIsValid(final DataSource dataSource) throws IOException {
return true; return true;
} }
@@ -378,23 +405,27 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* <p> * <p>
* Feel free to pass "A" as new level if you know what you are doing :-) * Feel free to pass "A" as new level if you know what you are doing :-)
*/ */
public OXExporterFromA3 setConformanceLevel(PDFAConformanceLevel newLevel) { @Override
public OXExporterFromA3 setConformanceLevel(PDFAConformanceLevel newLevel) {
conformanceLevel = newLevel; conformanceLevel = newLevel;
return this; return this;
} }
public OXExporterFromA3 setCreator(String creator) { @Override
public OXExporterFromA3 setCreator(String creator) {
this.creator = creator; this.creator = creator;
return this; return this;
} }
public OXExporterFromA3 setCreatorTool(String creatorTool) { @Override
public OXExporterFromA3 setCreatorTool(String creatorTool) {
this.creatorTool = creatorTool; this.creatorTool = creatorTool;
return this; return this;
} }
public OXExporterFromA3 setProducer(String producer) { @Override
public OXExporterFromA3 setProducer(String producer) {
this.producer = producer; this.producer = producer;
return this; return this;
} }
@@ -413,7 +444,8 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
return this; return this;
} }
protected OXExporterFromA3 setAttachZUGFeRDHeaders(boolean attachHeaders) { @Override
protected OXExporterFromA3 setAttachZUGFeRDHeaders(boolean attachHeaders) {
this.attachZUGFeRDHeaders = attachHeaders; this.attachZUGFeRDHeaders = attachHeaders;
return this; return this;
} }
@@ -426,7 +458,8 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* *
* @param metadata the PDFbox XMPMetadata object * @param metadata the PDFbox XMPMetadata object
*/ */
protected void addXMP(XMPMetadata metadata) { @Override
protected void addXMP(XMPMetadata metadata) {
if (attachZUGFeRDHeaders) { if (attachZUGFeRDHeaders) {
XMPSchemaZugferd zf = new XMPSchemaZugferd(metadata, 1, true, xmlProvider.getProfile(), XMPSchemaZugferd zf = new XMPSchemaZugferd(metadata, 1, true, xmlProvider.getProfile(),
@@ -453,12 +486,14 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* <code>setZUGFeRDXMLData(byte[] zugferdData)</code> * <code>setZUGFeRDXMLData(byte[] zugferdData)</code>
* @throws IOException if anything is wrong with already loaded PDF * @throws IOException if anything is wrong with already loaded PDF
*/ */
public IExporter setTransaction(IExportableTransaction trans) throws IOException { @Override
public IExporter setTransaction(IExportableTransaction trans) throws IOException {
this.trans = trans; this.trans = trans;
return prepare(); return prepare();
} }
public IExporter prepare() throws IOException { @Override
public IExporter prepare() throws IOException {
prepareDocument(); prepareDocument();
xmlProvider.generateXML(trans); xmlProvider.generateXML(trans);
String filename = "order-x.xml"; String filename = "order-x.xml";
@@ -477,7 +512,8 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* Reads the XMPMetadata from the PDDocument, if it exists. * Reads the XMPMetadata from the PDDocument, if it exists.
* Otherwise creates XMPMetadata. * Otherwise creates XMPMetadata.
*/ */
protected XMPMetadata getXmpMetadata() throws IOException { @Override
protected XMPMetadata getXmpMetadata() throws IOException {
PDMetadata meta = doc.getDocumentCatalog().getMetadata(); PDMetadata meta = doc.getDocumentCatalog().getMetadata();
if ((meta != null) && (meta.getLength() > 0)) { if ((meta != null) && (meta.getLength() > 0)) {
try { try {
@@ -490,7 +526,8 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
return XMPMetadata.createXMPMetadata(); return XMPMetadata.createXMPMetadata();
} }
protected byte[] serializeXmpMetadata(XMPMetadata xmpMetadata) throws TransformerException { @Override
protected byte[] serializeXmpMetadata(XMPMetadata xmpMetadata) throws TransformerException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream(); ByteArrayOutputStream buffer = new ByteArrayOutputStream();
new XmpSerializer().serialize(xmpMetadata, buffer, true); // see https://github.com/ZUGFeRD/mustangproject/issues/44 new XmpSerializer().serialize(xmpMetadata, buffer, true); // see https://github.com/ZUGFeRD/mustangproject/issues/44
return buffer.toByteArray(); return buffer.toByteArray();
@@ -500,7 +537,8 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* Sets the producer if the overwrite flag is set or the producer is not already set. * 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. * Sets the PDFVersion to 1.4 if the field is empty.
*/ */
protected void writeAdobePDFSchema(XMPMetadata xmp) { @Override
protected void writeAdobePDFSchema(XMPMetadata xmp) {
AdobePDFSchema pdf = getAdobePDFSchema(xmp); AdobePDFSchema pdf = getAdobePDFSchema(xmp);
if (overwrite || isEmpty(pdf.getProducer())) if (overwrite || isEmpty(pdf.getProducer()))
pdf.setProducer(producer); pdf.setProducer(producer);
@@ -510,7 +548,8 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* Returns the AdobePDFSchema from the XMPMetadata if it exists. * 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. * If the overwrite flag is set or no AdobePDFSchema exists in the XMPMetadata, it is created, added and returned.
*/ */
protected AdobePDFSchema getAdobePDFSchema(XMPMetadata xmp) { @Override
protected AdobePDFSchema getAdobePDFSchema(XMPMetadata xmp) {
AdobePDFSchema pdf = xmp.getAdobePDFSchema(); AdobePDFSchema pdf = xmp.getAdobePDFSchema();
if (pdf != null) if (pdf != null)
if (overwrite) if (overwrite)
@@ -520,7 +559,8 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
return xmp.createAndAddAdobePDFSchema(); return xmp.createAndAddAdobePDFSchema();
} }
protected void writePDFAIdentificationSchema(XMPMetadata xmp) { @Override
protected void writePDFAIdentificationSchema(XMPMetadata xmp) {
PDFAIdentificationSchema pdfaid = getPDFAIdentificationSchema(xmp); PDFAIdentificationSchema pdfaid = getPDFAIdentificationSchema(xmp);
if (overwrite || isEmpty(pdfaid.getConformance())) { if (overwrite || isEmpty(pdfaid.getConformance())) {
try { try {
@@ -535,17 +575,19 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
pdfaid.setPart(3); pdfaid.setPart(3);
} }
protected PDFAIdentificationSchema getPDFAIdentificationSchema(XMPMetadata xmp) { @Override
PDFAIdentificationSchema pdfaid = xmp.getPDFIdentificationSchema(); protected PDFAIdentificationSchema getPDFAIdentificationSchema(XMPMetadata xmp) {
PDFAIdentificationSchema pdfaid = xmp.getPDFAIdentificationSchema();
if (pdfaid != null) if (pdfaid != null)
if (overwrite) if (overwrite)
xmp.removeSchema(pdfaid); xmp.removeSchema(pdfaid);
else else
return pdfaid; return pdfaid;
return xmp.createAndAddPFAIdentificationSchema(); return xmp.createAndAddPDFAIdentificationSchema();
} }
protected void writeDublinCoreSchema(XMPMetadata xmp) { @Override
protected void writeDublinCoreSchema(XMPMetadata xmp) {
DublinCoreSchema dc = getDublinCoreSchema(xmp); DublinCoreSchema dc = getDublinCoreSchema(xmp);
if (dc.getFormat() == null) if (dc.getFormat() == null)
dc.setFormat("application/pdf"); dc.setFormat("application/pdf");
@@ -568,7 +610,8 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
} }
} }
protected DublinCoreSchema getDublinCoreSchema(XMPMetadata xmp) { @Override
protected DublinCoreSchema getDublinCoreSchema(XMPMetadata xmp) {
DublinCoreSchema dc = xmp.getDublinCoreSchema(); DublinCoreSchema dc = xmp.getDublinCoreSchema();
if (dc != null) if (dc != null)
if (overwrite) if (overwrite)
@@ -578,15 +621,17 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
return xmp.createAndAddDublinCoreSchema(); return xmp.createAndAddDublinCoreSchema();
} }
protected void writeXMLBasicSchema(XMPMetadata xmp) { @Override
protected void writeXMLBasicSchema(XMPMetadata xmp) {
XMPBasicSchema xsb = getXmpBasicSchema(xmp); XMPBasicSchema xsb = getXmpBasicSchema(xmp);
if (overwrite || isEmpty(xsb.getCreatorTool()) || "UnknownApplication".equals(xsb.getCreatorTool())) if (overwrite || isEmpty(xsb.getCreatorTool()) || "UnknownApplication".equals(xsb.getCreatorTool()))
xsb.setCreatorTool(creatorTool); xsb.setCreatorTool(creatorTool);
if (overwrite || xsb.getCreateDate() == null) if (overwrite || xsb.getCreateDate() == null)
xsb.setCreateDate(GregorianCalendar.getInstance()); xsb.setCreateDate(Calendar.getInstance());
} }
protected XMPBasicSchema getXmpBasicSchema(XMPMetadata xmp) { @Override
protected XMPBasicSchema getXmpBasicSchema(XMPMetadata xmp) {
XMPBasicSchema xsb = xmp.getXMPBasicSchema(); XMPBasicSchema xsb = xmp.getXMPBasicSchema();
if (xsb != null) if (xsb != null)
if (overwrite) if (overwrite)
@@ -596,7 +641,8 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
return xmp.createAndAddXMPBasicSchema(); return xmp.createAndAddXMPBasicSchema();
} }
protected void writeDocumentInformation() { @Override
protected void writeDocumentInformation() {
String fullProducer = producer + " (via mustangproject.org " + Version.VERSION + ")"; String fullProducer = producer + " (via mustangproject.org " + Version.VERSION + ")";
PDDocumentInformation info = doc.getDocumentInformation(); PDDocumentInformation info = doc.getDocumentInformation();
if (overwrite || info.getCreationDate() == null) if (overwrite || info.getCreationDate() == null)
@@ -618,7 +664,8 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
/** /**
* Adds an OutputIntent and the sRGB color profile if no OutputIntent exist * Adds an OutputIntent and the sRGB color profile if no OutputIntent exist
*/ */
protected void addSRGBOutputIntend() throws IOException { @Override
protected void addSRGBOutputIntend() throws IOException {
if (!doc.getDocumentCatalog().getOutputIntents().isEmpty()) { if (!doc.getDocumentCatalog().getOutputIntents().isEmpty()) {
return; return;
} }
@@ -641,7 +688,8 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
/** /**
* Adds a MarkInfo element to the PDF if it doesn't already exist and sets it as marked. * Adds a MarkInfo element to the PDF if it doesn't already exist and sets it as marked.
*/ */
protected void setMarked() { @Override
protected void setMarked() {
PDDocumentCatalog catalog = doc.getDocumentCatalog(); PDDocumentCatalog catalog = doc.getDocumentCatalog();
if (catalog.getMarkInfo() == null) { if (catalog.getMarkInfo() == null) {
catalog.setMarkInfo(new PDMarkInfo(doc.getPages().getCOSObject())); catalog.setMarkInfo(new PDMarkInfo(doc.getPages().getCOSObject()));
@@ -652,7 +700,8 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
/** /**
* Adds a StructureTreeRoot element to the PDF if it doesn't already exist. * Adds a StructureTreeRoot element to the PDF if it doesn't already exist.
*/ */
protected void addStructureTreeRoot() { @Override
protected void addStructureTreeRoot() {
if (doc.getDocumentCatalog().getStructureTreeRoot() == null) { if (doc.getDocumentCatalog().getStructureTreeRoot() == null) {
doc.getDocumentCatalog().setStructureTreeRoot(new PDStructureTreeRoot()); doc.getDocumentCatalog().setStructureTreeRoot(new PDStructureTreeRoot());
} }
@@ -662,19 +711,22 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
/** /**
* @return if pdf file will be automatically closed after adding ZF * @return if pdf file will be automatically closed after adding ZF
*/ */
public boolean isAutoCloseDisabled() { @Override
public boolean isAutoCloseDisabled() {
return disableAutoClose; return disableAutoClose;
} }
/** /**
* @param disableAutoClose prevent PDF file from being closed after adding ZF * @param disableAutoClose prevent PDF file from being closed after adding ZF
*/ */
public OXExporterFromA3 disableAutoClose(boolean disableAutoClose) { @Override
public OXExporterFromA3 disableAutoClose(boolean disableAutoClose) {
this.disableAutoClose = disableAutoClose; this.disableAutoClose = disableAutoClose;
return this; return this;
} }
protected void setXMLProvider(IXMLProvider p) { @Override
protected void setXMLProvider(IXMLProvider p) {
this.xmlProvider = p; this.xmlProvider = p;
if (profile != null) { if (profile != null) {
xmlProvider.setProfile(profile); xmlProvider.setProfile(profile);

View File

@@ -22,26 +22,19 @@ package org.mustangproject.ZUGFeRD;
import static org.mustangproject.ZUGFeRD.ZUGFeRDDateFormat.DATE; import static org.mustangproject.ZUGFeRD.ZUGFeRDDateFormat.DATE;
import static org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants.CORRECTEDINVOICE; 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.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Base64; import java.util.Base64;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.Map; 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.EStandard;
import org.mustangproject.FileAttachment; import org.mustangproject.FileAttachment;
import org.mustangproject.IncludedNote;
import org.mustangproject.XMLTools; import org.mustangproject.XMLTools;
public class OXPullProvider extends ZUGFeRD2PullProvider implements IXMLProvider { public class OXPullProvider extends ZUGFeRD2PullProvider {
protected IExportableTransaction trans; protected IExportableTransaction trans;
protected TransactionCalculator calc; 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) {
hasDueDate = false; if ((trans.getDocumentCode().equals(CORRECTEDINVOICE))/*||(trans.getDocumentCode().equals (DocumentCodeTypeConstants.CREDITNOTE))*/) {
hasDueDate = false;
}
} }
final Map<BigDecimal, VATAmount> VATPercentAmountMap = calc.getVATPercentAmountMap(); final Map<BigDecimal, VATAmount> VATPercentAmountMap = calc.getVATPercentAmountMap();
for (final BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) { for (final BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) {
final VATAmount amount = VATPercentAmountMap.get(currentTaxPercent); final VATAmount amount = VATPercentAmountMap.get(currentTaxPercent);
if (amount != null) { if (amount != null) {
final String amountCategoryCode = amount.getCategoryCode(); /*final String amountCategoryCode = amount.getCategoryCode();
final boolean displayExemptionReason = CATEGORY_CODES_WITH_EXEMPTION_REASON.contains(amountCategoryCode); 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>" + currencyFormat(amount.getCalculated())
+ "</ram:CalculatedAmount>\n" //currencyID=\"EUR\" + "</ram:CalculatedAmount>\n" //currencyID=\"EUR\"
+ "<ram:TypeCode>VAT</ram:TypeCode>\n" + "<ram:TypeCode>VAT</ram:TypeCode>\n"
@@ -479,13 +474,9 @@ public class OXPullProvider extends ZUGFeRD2PullProvider implements IXMLProvider
+ "</rsm:SCRDMCCBDACIOMessageStructure>"; + "</rsm:SCRDMCCBDACIOMessageStructure>";
final byte[] zugferdRaw; final byte[] zugferdRaw;
try { zugferdRaw = xml.getBytes(StandardCharsets.UTF_8);
zugferdRaw = xml.getBytes("UTF-8");
zugferdData = XMLTools.removeBOM(zugferdRaw); zugferdData = XMLTools.removeBOM(zugferdRaw);
} catch (final UnsupportedEncodingException e) {
Logger.getLogger(OXPullProvider.class.getName()).log(Level.SEVERE, null, e);
}
} }

View File

@@ -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
}

View File

@@ -20,12 +20,12 @@
*/ */
package org.mustangproject.ZUGFeRD; package org.mustangproject.ZUGFeRD;
import org.mustangproject.EStandard;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.mustangproject.EStandard;
public class Profiles { public class Profiles {
static Map<String, Profile> zf2Map = Stream.of(new Object[][]{ static Map<String, Profile> zf2Map = Stream.of(new Object[][]{
{"MINIMUM", new Profile("MINIMUM", "urn:factur-x.eu:1p0:minimum")}, {"MINIMUM", new Profile("MINIMUM", "urn:factur-x.eu:1p0:minimum")},

View File

@@ -39,8 +39,8 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
} }
/*** /***
* the invoice total with VAT, corrected by prepaid amount, allowances and * the invoice total with VAT, allowances and
* charges * charges, WITHOUT considering prepaid amount
* *
* @return the invoice total including taxes * @return the invoice total including taxes
*/ */
@@ -172,6 +172,10 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
LineCalculator lc = new LineCalculator(currentItem); LineCalculator lc = new LineCalculator(currentItem);
VATAmount itemVATAmount = new VATAmount(lc.getItemTotalNetAmount(), lc.getItemTotalVATAmount(), VATAmount itemVATAmount = new VATAmount(lc.getItemTotalNetAmount(), lc.getItemTotalVATAmount(),
currentItem.getProduct().getTaxCategoryCode(), vatDueDateTypeCode); currentItem.getProduct().getTaxCategoryCode(), vatDueDateTypeCode);
String reasonText=currentItem.getProduct().getTaxExemptionReason();
if (reasonText!=null) {
itemVATAmount.setVatExemptionReasonText(reasonText);
}
VATAmount current = hm.get(percent.stripTrailingZeros()); VATAmount current = hm.get(percent.stripTrailingZeros());
if (current == null) { if (current == null) {
hm.put(percent.stripTrailingZeros(), itemVATAmount); hm.put(percent.stripTrailingZeros(), itemVATAmount);

View File

@@ -20,33 +20,23 @@
*/ */
package org.mustangproject.ZUGFeRD; 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.Document;
import org.dom4j.DocumentException; import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper; import org.dom4j.DocumentHelper;
import org.dom4j.io.OutputFormat; import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter; import org.dom4j.io.XMLWriter;
import org.mustangproject.EStandard; import org.mustangproject.EStandard;
import org.mustangproject.FileAttachment;
import org.mustangproject.TradeParty;
import org.mustangproject.XMLTools; import org.mustangproject.XMLTools;
import org.slf4j.Logger;
import java.io.IOException; import org.slf4j.LoggerFactory;
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;
public class UBLDAPullProvider implements IXMLProvider { public class UBLDAPullProvider implements IXMLProvider {
private static final Logger LOGGER = LoggerFactory.getLogger (UBLDAPullProvider.class);
protected IExportableTransaction trans; protected IExportableTransaction trans;
protected TransactionCalculator calc; protected TransactionCalculator calc;
@@ -96,13 +86,9 @@ public class UBLDAPullProvider implements IXMLProvider {
} }
xml += "</DespatchAdvice>\n"; xml += "</DespatchAdvice>\n";
final byte[] ublRaw; final byte[] ublRaw;
try { ublRaw = xml.getBytes(StandardCharsets.UTF_8);
ublRaw = xml.getBytes("UTF-8");
ublData = XMLTools.removeBOM(ublRaw); ublData = XMLTools.removeBOM(ublRaw);
} catch (final UnsupportedEncodingException e) {
Logger.getLogger(UBLDAPullProvider.class.getName()).log(Level.SEVERE, null, e);
}
} }
public String getPartyXML(IZUGFeRDExportableTradeParty tp) { public String getPartyXML(IZUGFeRDExportableTradeParty tp) {
@@ -133,7 +119,7 @@ public class UBLDAPullProvider implements IXMLProvider {
try { try {
document = DocumentHelper.parseText(new String(ublData)); document = DocumentHelper.parseText(new String(ublData));
} catch (final DocumentException e1) { } catch (final DocumentException e1) {
Logger.getLogger(ZUGFeRD2PullProvider.class.getName()).log(Level.SEVERE, null, e1); LOGGER.error ("Failed to parse UBL", e1);
} }
try { try {
final OutputFormat format = OutputFormat.createPrettyPrint(); final OutputFormat format = OutputFormat.createPrettyPrint();
@@ -143,7 +129,7 @@ public class UBLDAPullProvider implements IXMLProvider {
res = sw.toString().getBytes(StandardCharsets.UTF_8); res = sw.toString().getBytes(StandardCharsets.UTF_8);
} catch (final IOException e) { } catch (final IOException e) {
Logger.getLogger(ZUGFeRD2PullProvider.class.getName()).log(Level.SEVERE, null, e); LOGGER.error ("Failed to write XML", e);
} }
return res; return res;

View File

@@ -32,6 +32,16 @@ import java.math.RoundingMode;
*/ */
public class VATAmount { 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) { public VATAmount(BigDecimal basis, BigDecimal calculated, String categoryCode) {
super(); super();
this.basis = basis; this.basis = basis;
@@ -48,35 +58,41 @@ public class VATAmount {
this.dueDateTypeCode = dueDateTypeCode; this.dueDateTypeCode = dueDateTypeCode;
} }
BigDecimal basis, calculated, applicablePercent;
String categoryCode;
String dueDateTypeCode;
public BigDecimal getApplicablePercent() { public BigDecimal getApplicablePercent() {
return applicablePercent; return applicablePercent;
} }
public void setApplicablePercent(BigDecimal applicablePercent) { public VATAmount setApplicablePercent(BigDecimal applicablePercent) {
this.applicablePercent = applicablePercent; this.applicablePercent = applicablePercent;
return this;
} }
public BigDecimal getBasis() { public BigDecimal getBasis() {
return basis; return basis;
} }
public void setBasis(BigDecimal basis) { public VATAmount setBasis(BigDecimal basis) {
this.basis = basis.setScale(2, RoundingMode.HALF_UP); this.basis = basis.setScale(2, RoundingMode.HALF_UP);
return this;
} }
public BigDecimal getCalculated() { public BigDecimal getCalculated() {
return calculated; return calculated;
} }
public void setCalculated(BigDecimal calculated) { public VATAmount setCalculated(BigDecimal calculated) {
this.calculated = 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 * @param documentCode as String
* @deprecated Use {@link #setCategoryCode(String)} instead * @deprecated Use {@link #setCategoryCode(String)} instead
* @return fluent setter
*/ */
@Deprecated @Deprecated
public void setDocumentCode(String documentCode) { public VATAmount setDocumentCode(String documentCode) {
this.categoryCode = documentCode; this.categoryCode = documentCode;
return this;
} }
public String getCategoryCode() { public String getCategoryCode() {
return categoryCode; return categoryCode;
} }
public void setCategoryCode(String categoryCode) { public VATAmount setCategoryCode(String categoryCode) {
this.categoryCode = categoryCode; this.categoryCode = categoryCode;
return this;
} }
public String getDueDateTypeCode() { public String getDueDateTypeCode() {
return dueDateTypeCode; return dueDateTypeCode;
} }
public void setDueDateTypeCode(String dueDateTypeCode) { public VATAmount setDueDateTypeCode(String dueDateTypeCode) {
this.dueDateTypeCode = dueDateTypeCode; this.dueDateTypeCode = dueDateTypeCode;
return this;
} }
public VATAmount add(VATAmount v) { 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) { 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());
} }
} }

View File

@@ -1,9 +1,20 @@
package org.mustangproject.ZUGFeRD; 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.StreamResult;
import javax.xml.transform.stream.StreamSource; import javax.xml.transform.stream.StreamSource;
import java.io.*;
/*** /***
@@ -31,9 +42,8 @@ public class XMLUpgrader {
* @return String the updated XML * @return String the updated XML
* @throws FileNotFoundException if the source could not be found * @throws FileNotFoundException if the source could not be found
* @throws TransformerException if the source could not be transformed * @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 * 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(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
applySchematronXsl(new FileInputStream(xmlFilename), baos); applySchematronXsl(new FileInputStream(xmlFilename), baos);
String res = null; String res = null;
res = baos.toString("UTF-8"); res = baos.toString(StandardCharsets.UTF_8);
return res; return res;
} }

View File

@@ -30,7 +30,15 @@ package org.mustangproject.ZUGFeRD;
import org.apache.xmpbox.XMPMetadata; import org.apache.xmpbox.XMPMetadata;
import org.apache.xmpbox.XmpConstants; import org.apache.xmpbox.XmpConstants;
import org.apache.xmpbox.schema.PDFAExtensionSchema; 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; import org.mustangproject.EStandard;
/** /**

View File

@@ -20,31 +20,10 @@
*/ */
package org.mustangproject.ZUGFeRD; package org.mustangproject.ZUGFeRD;
import org.apache.pdfbox.cos.*; import java.io.File;
import org.apache.pdfbox.io.IOUtils; import java.io.FileOutputStream;
import org.apache.pdfbox.pdmodel.*; import java.io.IOException;
import org.apache.pdfbox.pdmodel.common.PDMetadata; import java.io.OutputStream;
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;
public class XRExporter implements IExporter { public class XRExporter implements IExporter {
IXMLProvider xmlProvider; IXMLProvider xmlProvider;

View File

@@ -1,16 +1,16 @@
package org.mustangproject.ZUGFeRD; package org.mustangproject.ZUGFeRD;
import org.mustangproject.XMLTools;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; 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 { public class XRechnungImporter extends ZUGFeRDImporter {
private static final Logger LOGGER = LoggerFactory.getLogger (XRechnungImporter.class);
public XRechnungImporter(byte[] rawXml) { public XRechnungImporter(byte[] rawXml) {
super(); super();
@@ -19,7 +19,7 @@ public class XRechnungImporter extends ZUGFeRDImporter {
setRawXML(rawXml); setRawXML(rawXml);
containsMeta = true; containsMeta = true;
} catch (final IOException e) { } 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); throw new ZUGFeRDExportException(e);
} }
} }
@@ -31,7 +31,7 @@ public class XRechnungImporter extends ZUGFeRDImporter {
setRawXML(Files.readAllBytes(Paths.get(filename))); setRawXML(Files.readAllBytes(Paths.get(filename)));
containsMeta = true; containsMeta = true;
} catch (final IOException e) { } 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); throw new ZUGFeRDExportException(e);
} }
@@ -43,7 +43,7 @@ public class XRechnungImporter extends ZUGFeRDImporter {
setRawXML(XMLTools.getBytesFromStream(fileinput)); setRawXML(XMLTools.getBytesFromStream(fileinput));
containsMeta = true; containsMeta = true;
} catch (final IOException e) { } 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); throw new ZUGFeRDExportException(e);
} }

View File

@@ -30,8 +30,6 @@ import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.dom4j.Document; import org.dom4j.Document;
import org.dom4j.DocumentException; import org.dom4j.DocumentException;
@@ -39,9 +37,11 @@ import org.dom4j.DocumentHelper;
import org.dom4j.io.OutputFormat; import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter; import org.dom4j.io.XMLWriter;
import org.mustangproject.XMLTools; 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 //// MAIN CLASS
@@ -93,17 +93,17 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
try { try {
document = DocumentHelper.parseText(new String(zugferdData)); document = DocumentHelper.parseText(new String(zugferdData));
} catch (final DocumentException e1) { } catch (final DocumentException e1) {
Logger.getLogger(ZUGFeRD1PullProvider.class.getName()).log(Level.SEVERE, null, e1); LOGGER.error ("Failed to parse ZUGFeRD data", e1);
} }
try { try {
final OutputFormat format = OutputFormat.createPrettyPrint(); final OutputFormat format = OutputFormat.createPrettyPrint();
format.setTrimText(false); format.setTrimText(false);
final XMLWriter writer = new XMLWriter(sw, format); final XMLWriter writer = new XMLWriter(sw, format);
writer.write(document); writer.write(document);
res = sw.toString().getBytes("UTF-8"); res = sw.toString().getBytes(StandardCharsets.UTF_8);
} catch (final IOException e) { } catch (final IOException e) {
Logger.getLogger(ZUGFeRD1PullProvider.class.getName()).log(Level.SEVERE, null, e); LOGGER.error ("Failed to write ZUGFeRD data", e);
} }
return res; return res;
@@ -387,9 +387,10 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
+ "<ram:LineTotalAmount currencyID=\"" + trans.getCurrency() + "\">" + currencyFormat(lc.getItemTotalNetAmount()) + "<ram:LineTotalAmount currencyID=\"" + trans.getCurrency() + "\">" + currencyFormat(lc.getItemTotalNetAmount())
+ "</ram:LineTotalAmount>" + "</ram:LineTotalAmount>"
+ "</ram:SpecifiedTradeSettlementMonetarySummation>"; + "</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:AdditionalReferencedDocument><ram:ID>" + currentItem.getAdditionalReferencedDocumentID() + "</ram:ID><ram:TypeCode>130</ram:TypeCode></ram:AdditionalReferencedDocument>";
} }
xml += "</ram:SpecifiedSupplyChainTradeSettlement>" xml += "</ram:SpecifiedSupplyChainTradeSettlement>"
+ "<ram:SpecifiedTradeProduct>"; + "<ram:SpecifiedTradeProduct>";

View File

@@ -25,7 +25,6 @@ import static org.mustangproject.ZUGFeRD.model.TaxCategoryCodeTypeConstants.CATE
import java.io.IOException; import java.io.IOException;
import java.io.StringWriter; import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@@ -35,9 +34,8 @@ import java.util.Base64;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional; import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.dom4j.Document; import org.dom4j.Document;
@@ -49,8 +47,11 @@ import org.mustangproject.FileAttachment;
import org.mustangproject.IncludedNote; import org.mustangproject.IncludedNote;
import org.mustangproject.XMLTools; import org.mustangproject.XMLTools;
import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants; import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ZUGFeRD2PullProvider implements IXMLProvider { public class ZUGFeRD2PullProvider implements IXMLProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(ZUGFeRD2PullProvider.class);
protected byte[] zugferdData; protected byte[] zugferdData;
protected IExportableTransaction trans; protected IExportableTransaction trans;
@@ -75,11 +76,13 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
} }
protected String priceFormat(BigDecimal value) { 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) { protected String quantityFormat(BigDecimal value) {
return XMLTools.nDigitFormat(value, 4); return XMLTools.nDigitFormatDecimalRange(value, 18, 4);
} }
@Override @Override
@@ -92,7 +95,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
try { try {
document = DocumentHelper.parseText(new String(zugferdData)); document = DocumentHelper.parseText(new String(zugferdData));
} catch (final DocumentException e1) { } catch (final DocumentException e1) {
Logger.getLogger(ZUGFeRD2PullProvider.class.getName()).log(Level.SEVERE, null, e1); LOGGER.error("Failed to parse ZUGFeRD data", e1);
} }
try { try {
final OutputFormat format = OutputFormat.createPrettyPrint(); final OutputFormat format = OutputFormat.createPrettyPrint();
@@ -102,7 +105,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
res = sw.toString().getBytes(StandardCharsets.UTF_8); res = sw.toString().getBytes(StandardCharsets.UTF_8);
} catch (final IOException e) { } catch (final IOException e) {
Logger.getLogger(ZUGFeRD2PullProvider.class.getName()).log(Level.SEVERE, null, e); LOGGER.error("Failed to write ZUGFeRD data", e);
} }
return res; return res;
@@ -137,21 +140,32 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
+ XMLTools.encodeXML(party.getGlobalID()) + "</ram:GlobalID>"; + XMLTools.encodeXML(party.getGlobalID()) + "</ram:GlobalID>";
} }
xml += "<ram:Name>" + XMLTools.encodeXML(party.getName()) + "</ram:Name>"; 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) { if (party.getLegalOrganisation() != null) {
xml += "<ram:SpecifiedLegalOrganization> "; 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>"; 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>"; xml += "<ram:DefinedTradeContact>";
if (party.getContact().getName() != null) { if (party.getContact().getName() != null) {
xml += "<ram:PersonName>" + XMLTools.encodeXML(party.getContact().getName()) xml += "<ram:PersonName>"
+ XMLTools.encodeXML(party.getContact().getName())
+ "</ram:PersonName>"; + "</ram:PersonName>";
} }
if (party.getContact().getPhone() != null) { if (party.getContact().getPhone() != null) {
xml += "<ram:TelephoneUniversalCommunication><ram:CompleteNumber>" xml += "<ram:TelephoneUniversalCommunication><ram:CompleteNumber>"
+ XMLTools.encodeXML(party.getContact().getPhone()) + "</ram:CompleteNumber>" + XMLTools.encodeXML(party.getContact().getPhone()) + "</ram:CompleteNumber>"
+ "</ram:TelephoneUniversalCommunication>"; + "</ram:TelephoneUniversalCommunication>";
@@ -192,7 +206,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
+ "</ram:CityName>"; + "</ram:CityName>";
} }
//country IS mandatory //country IS mandatory
xml += "<ram:CountryID>" + XMLTools.encodeXML(party.getCountry()) xml += "<ram:CountryID>" + XMLTools.encodeXML(party.getCountry())
+ "</ram:CountryID>" + "</ram:CountryID>"
+ "</ram:PostalTradeAddress>"; + "</ram:PostalTradeAddress>";
@@ -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 * returns the XML for a charge or allowance on item level
@@ -239,14 +277,19 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
} }
String reason = ""; String reason = "";
if ((allowance.getReason() != null) && (profile == Profiles.getByName("Extended"))) { if ((allowance.getReason() != null) && (profile == Profiles.getByName("Extended") || profile == Profiles.getByName("XRechnung"))) {
// only in extended profile
reason = "<ram:Reason>" + XMLTools.encodeXML(allowance.getReason()) + "</ram:Reason>"; 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>" + final String allowanceChargeStr = "<ram:AppliedTradeAllowanceCharge><ram:ChargeIndicator><udt:Indicator>" +
chargeIndicator + "</udt:Indicator></ram:ChargeIndicator>" + percentage + chargeIndicator + "</udt:Indicator></ram:ChargeIndicator>" + percentage +
"<ram:ActualAmount>" + priceFormat(allowance.getTotalAmount(item)) + "</ram:ActualAmount>" + "<ram:ActualAmount>" + priceFormat(allowance.getTotalAmount(item)) + "</ram:ActualAmount>" +
reason + reason +
reasonCode +
"</ram:AppliedTradeAllowanceCharge>"; "</ram:AppliedTradeAllowanceCharge>";
return allowanceChargeStr; return allowanceChargeStr;
} }
@@ -268,10 +311,20 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
chargeIndicator = "true"; 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>" + final String itemTotalAllowanceChargeStr = "<ram:SpecifiedTradeAllowanceCharge><ram:ChargeIndicator><udt:Indicator>" +
chargeIndicator + "</udt:Indicator></ram:ChargeIndicator>" + percentage + chargeIndicator + "</udt:Indicator></ram:ChargeIndicator>" + percentage +
"<ram:ActualAmount>" + currencyFormat(allowance.getTotalAmount(item)) + "</ram:ActualAmount>" + "<ram:ActualAmount>" + currencyFormat(allowance.getTotalAmount(item)) + "</ram:ActualAmount>" +
"<ram:Reason>" + XMLTools.encodeXML(allowance.getReason()) + "</ram:Reason>" + reason +
reasonCode +
"</ram:SpecifiedTradeAllowanceCharge>"; "</ram:SpecifiedTradeAllowanceCharge>";
return itemTotalAllowanceChargeStr; return itemTotalAllowanceChargeStr;
} }
@@ -290,11 +343,22 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
paymentTermsDescription = XMLTools.encodeXML(trans.getPaymentTermDescription()); 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"; String typecode = "380";
if (trans.getDocumentCode() != null) { if (trans.getDocumentCode() != null) {
typecode = trans.getDocumentCode(); typecode = trans.getDocumentCode();
@@ -314,9 +378,9 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
// //
if (getProfile() == Profiles.getByName("XRechnung")) { if (getProfile() == Profiles.getByName("XRechnung")) {
xml += " <ram:BusinessProcessSpecifiedDocumentContextParameter>\n" xml += "<ram:BusinessProcessSpecifiedDocumentContextParameter>\n"
+ " <ram:ID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</ram:ID>\n" + "<ram:ID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</ram:ID>\n"
+ " </ram:BusinessProcessSpecifiedDocumentContextParameter>\n"; + "</ram:BusinessProcessSpecifiedDocumentContextParameter>\n";
} }
xml += xml +=
"<ram:GuidelineSpecifiedDocumentContextParameter>" "<ram:GuidelineSpecifiedDocumentContextParameter>"
@@ -325,7 +389,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
+ "</rsm:ExchangedDocumentContext>" + "</rsm:ExchangedDocumentContext>"
+ "<rsm:ExchangedDocument>" + "<rsm:ExchangedDocument>"
+ "<ram:ID>" + XMLTools.encodeXML(trans.getNumber()) + "</ram:ID>" + "<ram:ID>" + XMLTools.encodeXML(trans.getNumber()) + "</ram:ID>"
// + " <ram:Name>RECHNUNG</ram:Name>" // + "<ram:Name>RECHNUNG</ram:Name>"
// + "<ram:TypeCode>380</ram:TypeCode>" // + "<ram:TypeCode>380</ram:TypeCode>"
+ "<ram:TypeCode>" + typecode + "</ram:TypeCode>" + "<ram:TypeCode>" + typecode + "</ram:TypeCode>"
+ "<ram:IssueDateTime>" + "<ram:IssueDateTime>"
@@ -387,6 +451,33 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
XMLTools.encodeXML(currentItem.getProduct().getDescription()) + XMLTools.encodeXML(currentItem.getProduct().getDescription()) +
"</ram:Description>"; "</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>" xml += "</ram:SpecifiedTradeProduct>"
+ "<ram:SpecifiedLineTradeAgreement>"; + "<ram:SpecifiedLineTradeAgreement>";
@@ -412,11 +503,11 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
+ "<ram:BasisQuantity unitCode=\"" + XMLTools.encodeXML(currentItem.getProduct().getUnit()) + "<ram:BasisQuantity unitCode=\"" + XMLTools.encodeXML(currentItem.getProduct().getUnit())
+ "\">" + quantityFormat(currentItem.getBasisQuantity()) + "</ram:BasisQuantity>" + "\">" + quantityFormat(currentItem.getBasisQuantity()) + "</ram:BasisQuantity>"
+ allowanceChargeStr + allowanceChargeStr
// + " <AppliedTradeAllowanceCharge>" // + "<AppliedTradeAllowanceCharge>"
// + " <ChargeIndicator>false</ChargeIndicator>" // + "<ChargeIndicator>false</ChargeIndicator>"
// + " <ActualAmount currencyID=\"EUR\">0.6667</ActualAmount>" // + "<ActualAmount currencyID=\"EUR\">0.6667</ActualAmount>"
// + " <Reason>Rabatt</Reason>" // + "<Reason>Rabatt</Reason>"
// + " </AppliedTradeAllowanceCharge>" // + "</AppliedTradeAllowanceCharge>"
+ "</ram:GrossPriceProductTradePrice>"; + "</ram:GrossPriceProductTradePrice>";
} }
@@ -456,9 +547,16 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
+ "<ram:LineTotalAmount>" + currencyFormat(lc.getItemTotalNetAmount()) + "<ram:LineTotalAmount>" + currencyFormat(lc.getItemTotalNetAmount())
+ "</ram:LineTotalAmount>" // currencyID=\"EUR\" + "</ram:LineTotalAmount>" // currencyID=\"EUR\"
+ "</ram:SpecifiedTradeSettlementLineMonetarySummation>"; + "</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:AdditionalReferencedDocument><ram:IssuerAssignedID>" + currentItem.getAdditionalReferencedDocumentID() + "</ram:IssuerAssignedID><ram:TypeCode>130</ram:TypeCode></ram:AdditionalReferencedDocument>";
} }
xml += "</ram:SpecifiedLineTradeSettlement>" xml += "</ram:SpecifiedLineTradeSettlement>"
+ "</ram:IncludedSupplyChainTradeLineItem>"; + "</ram:IncludedSupplyChainTradeLineItem>";
@@ -475,53 +573,53 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
+ getTradePartyAsXML(trans.getSender(), true, false) + getTradePartyAsXML(trans.getSender(), true, false)
+ "</ram:SellerTradeParty>" + "</ram:SellerTradeParty>"
+ "<ram:BuyerTradeParty>"; + "<ram:BuyerTradeParty>";
// + " <ID>GE2020211</ID>" // + "<ID>GE2020211</ID>"
// + " <GlobalID schemeID=\"0088\">4000001987658</GlobalID>" // + "<GlobalID schemeID=\"0088\">4000001987658</GlobalID>"
xml += getTradePartyAsXML(trans.getRecipient(), false, false); xml += getTradePartyAsXML(trans.getRecipient(), false, false);
xml += "</ram:BuyerTradeParty>"; xml += "</ram:BuyerTradeParty>";
if (trans.getSellerOrderReferencedDocumentID() != null) { if (trans.getSellerOrderReferencedDocumentID() != null) {
xml += " <ram:SellerOrderReferencedDocument>" xml += "<ram:SellerOrderReferencedDocument>"
+ " <ram:IssuerAssignedID>" + "<ram:IssuerAssignedID>"
+ XMLTools.encodeXML(trans.getSellerOrderReferencedDocumentID()) + "</ram:IssuerAssignedID>" + XMLTools.encodeXML(trans.getSellerOrderReferencedDocumentID()) + "</ram:IssuerAssignedID>"
+ " </ram:SellerOrderReferencedDocument>"; + "</ram:SellerOrderReferencedDocument>";
} }
if (trans.getBuyerOrderReferencedDocumentID() != null) { if (trans.getBuyerOrderReferencedDocumentID() != null) {
xml += " <ram:BuyerOrderReferencedDocument>" xml += "<ram:BuyerOrderReferencedDocument>"
+ " <ram:IssuerAssignedID>" + "<ram:IssuerAssignedID>"
+ XMLTools.encodeXML(trans.getBuyerOrderReferencedDocumentID()) + "</ram:IssuerAssignedID>" + XMLTools.encodeXML(trans.getBuyerOrderReferencedDocumentID()) + "</ram:IssuerAssignedID>"
+ " </ram:BuyerOrderReferencedDocument>"; + "</ram:BuyerOrderReferencedDocument>";
} }
if (trans.getContractReferencedDocument() != null) { if (trans.getContractReferencedDocument() != null) {
xml += " <ram:ContractReferencedDocument>" xml += "<ram:ContractReferencedDocument>"
+ " <ram:IssuerAssignedID>" + "<ram:IssuerAssignedID>"
+ XMLTools.encodeXML(trans.getContractReferencedDocument()) + "</ram:IssuerAssignedID>" + XMLTools.encodeXML(trans.getContractReferencedDocument()) + "</ram:IssuerAssignedID>"
+ " </ram:ContractReferencedDocument>"; + "</ram:ContractReferencedDocument>";
} }
// Additional Documents of XRechnung (Rechnungsbegruendende Unterlagen - BG-24 XRechnung) // Additional Documents of XRechnung (Rechnungsbegruendende Unterlagen - BG-24 XRechnung)
if (trans.getAdditionalReferencedDocuments() != null) { if (trans.getAdditionalReferencedDocuments() != null) {
for (final FileAttachment f : trans.getAdditionalReferencedDocuments()) { for (final FileAttachment f : trans.getAdditionalReferencedDocuments()) {
final String documentContent = new String(Base64.getEncoder().encodeToString(f.getData())); final String documentContent = new String(Base64.getEncoder().encodeToString(f.getData()));
xml += " <ram:AdditionalReferencedDocument>" xml += "<ram:AdditionalReferencedDocument>"
+ " <ram:IssuerAssignedID>" + f.getFilename() + "</ram:IssuerAssignedID>" + "<ram:IssuerAssignedID>" + f.getFilename() + "</ram:IssuerAssignedID>"
+ " <ram:TypeCode>916</ram:TypeCode>" + "<ram:TypeCode>916</ram:TypeCode>"
+ " <ram:Name>" + f.getDescription() + "</ram:Name>" + "<ram:Name>" + f.getDescription() + "</ram:Name>"
+ " <ram:AttachmentBinaryObject mimeCode=\"" + f.getMimetype() + "\"\n" + "<ram:AttachmentBinaryObject mimeCode=\"" + f.getMimetype() + "\"\n"
+ " filename=\"" + f.getFilename() + "\">" + documentContent + "</ram:AttachmentBinaryObject>" + "filename=\"" + f.getFilename() + "\">" + documentContent + "</ram:AttachmentBinaryObject>"
+ " </ram:AdditionalReferencedDocument>"; + "</ram:AdditionalReferencedDocument>";
} }
} }
if (trans.getSpecifiedProcuringProjectID() != null) { if (trans.getSpecifiedProcuringProjectID() != null) {
xml += " <ram:SpecifiedProcuringProject>" xml += "<ram:SpecifiedProcuringProject>"
+ " <ram:ID>" + "<ram:ID>"
+ XMLTools.encodeXML(trans.getSpecifiedProcuringProjectID()) + "</ram:ID>"; + XMLTools.encodeXML(trans.getSpecifiedProcuringProjectID()) + "</ram:ID>";
if (trans.getSpecifiedProcuringProjectName() != null) { if (trans.getSpecifiedProcuringProjectName() != null) {
xml += " <ram:Name >" + XMLTools.encodeXML(trans.getSpecifiedProcuringProjectName()) + "</ram:Name>"; xml += "<ram:Name>" + XMLTools.encodeXML(trans.getSpecifiedProcuringProjectName()) + "</ram:Name>";
} }
xml += " </ram:SpecifiedProcuringProject>"; xml += "</ram:SpecifiedProcuringProject>";
} }
xml += "</ram:ApplicableHeaderTradeAgreement>"; xml += "</ram:ApplicableHeaderTradeAgreement>";
xml += "<ram:ApplicableHeaderTradeDelivery>"; xml += "<ram:ApplicableHeaderTradeDelivery>";
@@ -538,12 +636,8 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
+ "<ram:OccurrenceDateTime>"; + "<ram:OccurrenceDateTime>";
xml += DATE.udtFormat(trans.getDeliveryDate()); xml += DATE.udtFormat(trans.getDeliveryDate());
xml += "</ram:OccurrenceDateTime>"; xml += "</ram:OccurrenceDateTime>";
xml += " </ram:ActualDeliverySupplyChainEvent>"; xml += "</ram:ActualDeliverySupplyChainEvent>";
} else {
if ((trans.getDocumentCode() != DocumentCodeTypeConstants.CREDITNOTE) && (getProfile() != Profiles.getByName("Minimum"))) {
throw new IllegalStateException("No delivery date provided");
}
} }
/* /*
* + "<DeliveryNoteReferencedDocument>" + * + "<DeliveryNoteReferencedDocument>" +
@@ -560,10 +654,19 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
xml += "</ram:ApplicableHeaderTradeDelivery>"; xml += "</ram:ApplicableHeaderTradeDelivery>";
xml += "<ram:ApplicableHeaderTradeSettlement>"; xml += "<ram:ApplicableHeaderTradeSettlement>";
if ((trans.getNumber() != null) && (getProfile() != Profiles.getByName("Minimum"))) {
xml += " <ram:PaymentReference>" + XMLTools.encodeXML(trans.getNumber()) + "</ram:PaymentReference>"; 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>";
} }
xml += " <ram:InvoiceCurrencyCode>" + trans.getCurrency() + "</ram:InvoiceCurrencyCode>";
if (trans.getTradeSettlementPayment() != null) { if (trans.getTradeSettlementPayment() != null) {
for (final IZUGFeRDTradeSettlementPayment payment : trans.getTradeSettlementPayment()) { for (final IZUGFeRDTradeSettlementPayment payment : trans.getTradeSettlementPayment()) {
@@ -599,12 +702,17 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
final String amountDueDateTypeCode = amount.getDueDateTypeCode(); final String amountDueDateTypeCode = amount.getDueDateTypeCode();
final boolean displayExemptionReason = CATEGORY_CODES_WITH_EXEMPTION_REASON.contains(amountCategoryCode); final boolean displayExemptionReason = CATEGORY_CODES_WITH_EXEMPTION_REASON.contains(amountCategoryCode);
if (getProfile() != Profiles.getByName("Minimum")) { if (getProfile() != Profiles.getByName("Minimum")) {
String exemptionReasonTextXML = "";
if ((displayExemptionReason) && (amount.getVatExemptionReasonText() != null)) {
exemptionReasonTextXML = "<ram:ExemptionReason>" + XMLTools.encodeXML(amount.getVatExemptionReasonText()) + "</ram:ExemptionReason>";
}
xml += "<ram:ApplicableTradeTax>" xml += "<ram:ApplicableTradeTax>"
+ "<ram:CalculatedAmount>" + currencyFormat(amount.getCalculated()) + "<ram:CalculatedAmount>" + currencyFormat(amount.getCalculated())
+ "</ram:CalculatedAmount>" //currencyID=\"EUR\" + "</ram:CalculatedAmount>" //currencyID=\"EUR\"
+ "<ram:TypeCode>VAT</ram:TypeCode>" + "<ram:TypeCode>VAT</ram:TypeCode>"
+ (displayExemptionReason ? exemptionReason : "") + exemptionReasonTextXML
+ "<ram:BasisAmount>" + currencyFormat(amount.getBasis()) + "</ram:BasisAmount>" // currencyID=\"EUR\" + "<ram:BasisAmount>" + currencyFormat(amount.getBasis()) + "</ram:BasisAmount>" // currencyID=\"EUR\"
+ "<ram:CategoryCode>" + amountCategoryCode + "</ram:CategoryCode>" + "<ram:CategoryCode>" + amountCategoryCode + "</ram:CategoryCode>"
+ (amountDueDateTypeCode != null ? "<ram:DueDateTypeCode>" + amountDueDateTypeCode + "</ram:DueDateTypeCode>" : "") + (amountDueDateTypeCode != null ? "<ram:DueDateTypeCode>" + amountDueDateTypeCode + "</ram:DueDateTypeCode>" : "")
@@ -625,40 +733,87 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
} }
if ((trans.getZFCharges() != null) && (trans.getZFCharges().length > 0)) { if ((trans.getZFCharges() != null) && (trans.getZFCharges().length > 0)) {
if (profile == Profiles.getByName("XRechnung")) {
for (final BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) { for (IZUGFeRDAllowanceCharge charge : trans.getZFCharges()) {
if (calc.getChargesForPercent(currentTaxPercent).compareTo(BigDecimal.ZERO) != 0) { xml += "<ram:SpecifiedTradeAllowanceCharge>" +
xml += " <ram:SpecifiedTradeAllowanceCharge>" +
"<ram:ChargeIndicator>" + "<ram:ChargeIndicator>" +
"<udt:Indicator>true</udt:Indicator>" + "<udt:Indicator>true</udt:Indicator>" +
"</ram:ChargeIndicator>" + "</ram:ChargeIndicator>" +
"<ram:ActualAmount>" + currencyFormat(calc.getChargesForPercent(currentTaxPercent)) + "</ram:ActualAmount>" + "<ram:ActualAmount>" + currencyFormat(charge.getTotalAmount(calc)) + "</ram:ActualAmount>";
"<ram:Reason>" + XMLTools.encodeXML(calc.getChargeReasonForPercent(currentTaxPercent)) + "</ram:Reason>" + if (charge.getReason() != null) {
"<ram:CategoryTradeTax>" + 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:TypeCode>VAT</ram:TypeCode>" +
"<ram:CategoryCode>" + VATPercentAmountMap.get(currentTaxPercent).getCategoryCode() + "</ram:CategoryCode>" + "<ram:CategoryCode>" + charge.getCategoryCode() + "</ram:CategoryCode>";
"<ram:RateApplicablePercent>" + vatFormat(currentTaxPercent) + "</ram:RateApplicablePercent>" + if (charge.getTaxPercent() != null) {
"</ram:CategoryTradeTax>" + xml += "<ram:RateApplicablePercent>" + vatFormat(charge.getTaxPercent()) + "</ram:RateApplicablePercent>";
"</ram:SpecifiedTradeAllowanceCharge> "; }
xml += "</ram:CategoryTradeTax>" +
"</ram:SpecifiedTradeAllowanceCharge>";
}
} else {
for (final BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) {
if (calc.getChargesForPercent(currentTaxPercent).compareTo(BigDecimal.ZERO) != 0) {
xml += "<ram:SpecifiedTradeAllowanceCharge>" +
"<ram:ChargeIndicator>" +
"<udt:Indicator>true</udt:Indicator>" +
"</ram:ChargeIndicator>" +
"<ram:ActualAmount>" + currencyFormat(calc.getChargesForPercent(currentTaxPercent)) + "</ram:ActualAmount>" +
"<ram:Reason>" + XMLTools.encodeXML(calc.getChargeReasonForPercent(currentTaxPercent)) + "</ram:Reason>" +
"<ram:CategoryTradeTax>" +
"<ram:TypeCode>VAT</ram:TypeCode>" +
"<ram:CategoryCode>" + VATPercentAmountMap.get(currentTaxPercent).getCategoryCode() + "</ram:CategoryCode>" +
"<ram:RateApplicablePercent>" + vatFormat(currentTaxPercent) + "</ram:RateApplicablePercent>" +
"</ram:CategoryTradeTax>" +
"</ram:SpecifiedTradeAllowanceCharge>";
}
} }
} }
} }
if ((trans.getZFAllowances() != null) && (trans.getZFAllowances().length > 0)) { if ((trans.getZFAllowances() != null) && (trans.getZFAllowances().length > 0)) {
for (final BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) { if (profile == Profiles.getByName("XRechnung")) {
if (calc.getAllowancesForPercent(currentTaxPercent).compareTo(BigDecimal.ZERO) != 0) { for (IZUGFeRDAllowanceCharge allowance : trans.getZFAllowances()) {
xml += "<ram:SpecifiedTradeAllowanceCharge>" + xml += "<ram:SpecifiedTradeAllowanceCharge>" +
"<ram:ChargeIndicator>" + "<ram:ChargeIndicator>" +
"<udt:Indicator>false</udt:Indicator>" + "<udt:Indicator>false</udt:Indicator>" +
"</ram:ChargeIndicator>" + "</ram:ChargeIndicator>" +
"<ram:ActualAmount>" + currencyFormat(calc.getAllowancesForPercent(currentTaxPercent)) + "</ram:ActualAmount>" + "<ram:ActualAmount>" + currencyFormat(allowance.getTotalAmount(calc)) + "</ram:ActualAmount>";
"<ram:Reason>" + XMLTools.encodeXML(calc.getAllowanceReasonForPercent(currentTaxPercent)) + "</ram:Reason>" + if (allowance.getReason() != null) {
"<ram:CategoryTradeTax>" + 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:TypeCode>VAT</ram:TypeCode>" +
"<ram:CategoryCode>" + VATPercentAmountMap.get(currentTaxPercent).getCategoryCode() + "</ram:CategoryCode>" + "<ram:CategoryCode>" + allowance.getCategoryCode() + "</ram:CategoryCode>";
"<ram:RateApplicablePercent>" + vatFormat(currentTaxPercent) + "</ram:RateApplicablePercent>" + if (allowance.getTaxPercent() != null) {
"</ram:CategoryTradeTax>" + xml += "<ram:RateApplicablePercent>" + vatFormat(allowance.getTaxPercent()) + "</ram:RateApplicablePercent>";
"</ram:SpecifiedTradeAllowanceCharge> "; }
xml += "</ram:CategoryTradeTax>" +
"</ram:SpecifiedTradeAllowanceCharge>";
}
} else {
for (final BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) {
if (calc.getAllowancesForPercent(currentTaxPercent).compareTo(BigDecimal.ZERO) != 0) {
xml += "<ram:SpecifiedTradeAllowanceCharge>" +
"<ram:ChargeIndicator>" +
"<udt:Indicator>false</udt:Indicator>" +
"</ram:ChargeIndicator>" +
"<ram:ActualAmount>" + currencyFormat(calc.getAllowancesForPercent(currentTaxPercent)) + "</ram:ActualAmount>" +
"<ram:Reason>" + XMLTools.encodeXML(calc.getAllowanceReasonForPercent(currentTaxPercent)) + "</ram:Reason>" +
"<ram:CategoryTradeTax>" +
"<ram:TypeCode>VAT</ram:TypeCode>" +
"<ram:CategoryCode>" + VATPercentAmountMap.get(currentTaxPercent).getCategoryCode() + "</ram:CategoryCode>" +
"<ram:RateApplicablePercent>" + vatFormat(currentTaxPercent) + "</ram:RateApplicablePercent>" +
"</ram:CategoryTradeTax>" +
"</ram:SpecifiedTradeAllowanceCharge>";
}
} }
} }
} }
@@ -689,6 +844,12 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
} else { } else {
xml += buildPaymentTermsXml(); 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>"; final String allowanceTotalLine = "<ram:AllowanceTotalAmount>" + currencyFormat(calc.getAllowancesForPercent(null)) + "</ram:AllowanceTotalAmount>";
@@ -710,43 +871,39 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
// // // //
// currencyID=\"EUR\" // currencyID=\"EUR\"
if (getProfile() != Profiles.getByName("Minimum")) { if (getProfile() != Profiles.getByName("Minimum")) {
xml += " <ram:TotalPrepaidAmount>" + currencyFormat(calc.getTotalPrepaid()) + "</ram:TotalPrepaidAmount>"; xml += "<ram:TotalPrepaidAmount>" + currencyFormat(calc.getTotalPrepaid()) + "</ram:TotalPrepaidAmount>";
} }
xml += "<ram:DuePayableAmount>" + currencyFormat(calc.getGrandTotal().subtract(calc.getTotalPrepaid())) + "</ram:DuePayableAmount>" xml += "<ram:DuePayableAmount>" + currencyFormat(calc.getGrandTotal().subtract(calc.getTotalPrepaid())) + "</ram:DuePayableAmount>"
+ "</ram:SpecifiedTradeSettlementHeaderMonetarySummation>"; + "</ram:SpecifiedTradeSettlementHeaderMonetarySummation>";
if (trans.getInvoiceReferencedDocumentID() != null) { if (trans.getInvoiceReferencedDocumentID() != null) {
xml += " <ram:InvoiceReferencedDocument>" xml += "<ram:InvoiceReferencedDocument>"
+ " <ram:IssuerAssignedID>" + "<ram:IssuerAssignedID>"
+ XMLTools.encodeXML(trans.getInvoiceReferencedDocumentID()) + "</ram:IssuerAssignedID>"; + XMLTools.encodeXML(trans.getInvoiceReferencedDocumentID()) + "</ram:IssuerAssignedID>";
if (trans.getInvoiceReferencedIssueDate() != null) { if (trans.getInvoiceReferencedIssueDate() != null) {
xml += "<ram:FormattedIssueDateTime>" xml += "<ram:FormattedIssueDateTime>"
+ DATE.qdtFormat(trans.getInvoiceReferencedIssueDate()) + DATE.qdtFormat(trans.getInvoiceReferencedIssueDate())
+ "</ram:FormattedIssueDateTime>"; + "</ram:FormattedIssueDateTime>";
} }
xml += " </ram:InvoiceReferencedDocument>"; xml += "</ram:InvoiceReferencedDocument>";
} }
xml += "</ram:ApplicableHeaderTradeSettlement>"; xml += "</ram:ApplicableHeaderTradeSettlement>";
// + " <IncludedSupplyChainTradeLineItem>\n" // + "<IncludedSupplyChainTradeLineItem>\n"
// + " <AssociatedDocumentLineDocument>\n" // + "<AssociatedDocumentLineDocument>\n"
// + " <IncludedNote>\n" // + "<IncludedNote>\n"
// + " <Content>Wir erlauben uns Ihnen folgende Positionen aus der Lieferung Nr. // + "<Content>Wir erlauben uns Ihnen folgende Positionen aus der Lieferung Nr.
// 2013-51112 in Rechnung zu stellen:</Content>\n" // 2013-51112 in Rechnung zu stellen:</Content>\n"
// + " </IncludedNote>\n" // + "</IncludedNote>\n"
// + " </AssociatedDocumentLineDocument>\n" // + "</AssociatedDocumentLineDocument>\n"
// + " </IncludedSupplyChainTradeLineItem>\n"; // + "</IncludedSupplyChainTradeLineItem>\n";
xml += "</rsm:SupplyChainTradeTransaction>" xml += "</rsm:SupplyChainTradeTransaction>"
+ "</rsm:CrossIndustryInvoice>"; + "</rsm:CrossIndustryInvoice>";
final byte[] zugferdRaw; final byte[] zugferdRaw;
try { zugferdRaw = xml.getBytes(StandardCharsets.UTF_8);
zugferdRaw = xml.getBytes("UTF-8");
zugferdData = XMLTools.removeBOM(zugferdRaw); zugferdData = XMLTools.removeBOM(zugferdRaw);
} catch (final UnsupportedEncodingException e) {
Logger.getLogger(ZUGFeRD2PullProvider.class.getName()).log(Level.SEVERE, null, e);
}
} }
protected String buildItemNotes(IZUGFeRDExportableItem currentItem) { protected String buildItemNotes(IZUGFeRDExportableItem currentItem) {
@@ -760,8 +917,9 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
} }
protected String buildNotes(IExportableTransaction exportableTransaction) { protected String buildNotes(IExportableTransaction exportableTransaction) {
final List<IncludedNote> includedNotes = Optional.ofNullable(exportableTransaction.getNotesWithSubjectCode()) final List<IncludedNote> includedNotes = new ArrayList<>();
.orElse(new ArrayList<>()); Optional.ofNullable(exportableTransaction.getNotesWithSubjectCode()).ifPresent(includedNotes::addAll);
if (exportableTransaction.getNotes() != null) { if (exportableTransaction.getNotes() != null) {
for (final String currentNote : exportableTransaction.getNotes()) { for (final String currentNote : exportableTransaction.getNotes()) {
includedNotes.add(IncludedNote.unspecifiedNote(currentNote)); includedNotes.add(IncludedNote.unspecifiedNote(currentNote));

View File

@@ -19,25 +19,21 @@
package org.mustangproject.ZUGFeRD; package org.mustangproject.ZUGFeRD;
import java.io.IOException;
import java.io.InputStream;
import org.apache.pdfbox.preflight.PreflightDocument; import org.apache.pdfbox.preflight.PreflightDocument;
import org.apache.pdfbox.preflight.ValidationResult;
import org.apache.pdfbox.preflight.exception.ValidationException; import org.apache.pdfbox.preflight.exception.ValidationException;
import org.apache.pdfbox.preflight.parser.PreflightParser; import org.apache.pdfbox.preflight.parser.PreflightParser;
import org.mustangproject.EStandard; import org.mustangproject.EStandard;
import javax.activation.DataSource; import jakarta.activation.DataSource;
import java.io.IOException;
import java.io.InputStream;
public class ZUGFeRDExporterFromA1 extends ZUGFeRDExporterFromA3 implements IZUGFeRDExporter { public class ZUGFeRDExporterFromA1 extends ZUGFeRDExporterFromA3 {
protected boolean ignorePDFAErrors = false;
public ZUGFeRDExporterFromA1 ignorePDFAErrors() {
this.ignorePDFAErrors = true;
return this;
}
private static boolean isValidA1(DataSource dataSource) throws IOException { 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 { 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 * NonSequentialParser. Some additional controls are present to check a set of
* PDF/A requirements. (Stream length consistency, EOL after some Keyword...) * 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 * Once the syntax validation is done, the parser can provide a
* PreflightDocument (that inherits from PDDocument) This document process the * PreflightDocument (that inherits from PDDocument) This document process the
* end of PDF/A validation. * end of PDF/A validation.
*/ */
document.validate(); ValidationResult res = document.validate();
// Get validation result // Get validation result
return document.getResult().isValid(); return res.isValid();
} catch (ValidationException e) { } catch (ValidationException e) {
/* /*
* the parse method can throw a SyntaxValidationException if the PDF file can't * the parse method can throw a SyntaxValidationException if the PDF file can't
@@ -70,14 +66,17 @@ public class ZUGFeRDExporterFromA1 extends ZUGFeRDExporterFromA3 implements IZUG
} }
public ZUGFeRDExporterFromA1 setProfile(Profile p) { @Override
public ZUGFeRDExporterFromA1 setProfile(Profile p) {
return (ZUGFeRDExporterFromA1)super.setProfile(p); return (ZUGFeRDExporterFromA1)super.setProfile(p);
} }
public ZUGFeRDExporterFromA1 setProfile(String profileName) { @Override
public ZUGFeRDExporterFromA1 setProfile(String profileName) {
return (ZUGFeRDExporterFromA1)super.setProfile(profileName); return (ZUGFeRDExporterFromA1)super.setProfile(profileName);
} }
public boolean ensurePDFIsValid(final DataSource dataSource) throws IOException { @Override
public boolean ensurePDFIsValid(final DataSource dataSource) throws IOException {
if (!ignorePDFAErrors && !isValidA1(dataSource)) { if (!ignorePDFAErrors && !isValidA1(dataSource)) {
throw new IOException("File is not a valid PDF/A-1 input file"); throw new IOException("File is not a valid PDF/A-1 input file");
} }
@@ -85,35 +84,45 @@ public class ZUGFeRDExporterFromA1 extends ZUGFeRDExporterFromA3 implements IZUG
} }
public ZUGFeRDExporterFromA1 load(String pdfFilename) throws IOException { @Override
public ZUGFeRDExporterFromA1 load(String pdfFilename) throws IOException {
return (ZUGFeRDExporterFromA1) super.load(pdfFilename); return (ZUGFeRDExporterFromA1) super.load(pdfFilename);
} }
public ZUGFeRDExporterFromA1 load(byte[] pdfBinary) throws IOException { @Override
public ZUGFeRDExporterFromA1 load(byte[] pdfBinary) throws IOException {
return (ZUGFeRDExporterFromA1) super.load(pdfBinary); return (ZUGFeRDExporterFromA1) super.load(pdfBinary);
} }
public ZUGFeRDExporterFromA1 load(InputStream pdfSource) throws IOException{ @Override
public ZUGFeRDExporterFromA1 load(InputStream pdfSource) throws IOException{
return (ZUGFeRDExporterFromA1) super.load(pdfSource); return (ZUGFeRDExporterFromA1) super.load(pdfSource);
} }
public ZUGFeRDExporterFromA1 setCreator(String creator) { @Override
public ZUGFeRDExporterFromA1 setCreator(String creator) {
return (ZUGFeRDExporterFromA1) super.setCreator(creator); return (ZUGFeRDExporterFromA1) super.setCreator(creator);
} }
public ZUGFeRDExporterFromA1 setConformanceLevel(PDFAConformanceLevel newLevel) { @Override
public ZUGFeRDExporterFromA1 setConformanceLevel(PDFAConformanceLevel newLevel) {
return (ZUGFeRDExporterFromA1) super.setConformanceLevel(newLevel); return (ZUGFeRDExporterFromA1) super.setConformanceLevel(newLevel);
} }
public ZUGFeRDExporterFromA1 setProducer(String producer){ @Override
public ZUGFeRDExporterFromA1 setProducer(String producer){
return (ZUGFeRDExporterFromA1) super.setProducer(producer); return (ZUGFeRDExporterFromA1) super.setProducer(producer);
} }
public ZUGFeRDExporterFromA1 setZUGFeRDVersion(EStandard est, int version){ @Override
public ZUGFeRDExporterFromA1 setZUGFeRDVersion(EStandard est, int version){
return (ZUGFeRDExporterFromA1) super.setZUGFeRDVersion(est, version); return (ZUGFeRDExporterFromA1) super.setZUGFeRDVersion(est, version);
} }
public ZUGFeRDExporterFromA1 setZUGFeRDVersion(int version){ @Override
public ZUGFeRDExporterFromA1 setZUGFeRDVersion(int version){
return (ZUGFeRDExporterFromA1) super.setZUGFeRDVersion(version); return (ZUGFeRDExporterFromA1) super.setZUGFeRDVersion(version);
} }
public ZUGFeRDExporterFromA1 setXML(byte[] zugferdData) throws IOException{ @Override
public ZUGFeRDExporterFromA1 setXML(byte[] zugferdData) throws IOException{
return (ZUGFeRDExporterFromA1) super.setXML(zugferdData); return (ZUGFeRDExporterFromA1) super.setXML(zugferdData);
} }
public ZUGFeRDExporterFromA1 disableAutoClose(boolean disableAutoClose){ @Override
public ZUGFeRDExporterFromA1 disableAutoClose(boolean disableAutoClose){
return (ZUGFeRDExporterFromA1) super.disableAutoClose(disableAutoClose); return (ZUGFeRDExporterFromA1) super.disableAutoClose(disableAutoClose);
} }
public ZUGFeRDExporterFromA1 convertOnly() { public ZUGFeRDExporterFromA1 convertOnly() {

View File

@@ -20,9 +20,35 @@
*/ */
package org.mustangproject.ZUGFeRD; 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.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.PDMetadata;
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; 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.PDFontDescriptor;
import org.apache.pdfbox.pdmodel.font.PDType0Font; import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.graphics.color.PDOutputIntent; import org.apache.pdfbox.pdmodel.graphics.color.PDOutputIntent;
import org.apache.pdfbox.preflight.utils.ByteArrayDataSource;
import org.apache.xmpbox.XMPMetadata; import org.apache.xmpbox.XMPMetadata;
import org.apache.xmpbox.schema.AdobePDFSchema; import org.apache.xmpbox.schema.AdobePDFSchema;
import org.apache.xmpbox.schema.DublinCoreSchema; import org.apache.xmpbox.schema.DublinCoreSchema;
@@ -47,19 +72,22 @@ import org.apache.xmpbox.xml.XmpSerializer;
import org.mustangproject.EStandard; import org.mustangproject.EStandard;
import org.mustangproject.FileAttachment; import org.mustangproject.FileAttachment;
import javax.activation.DataSource; import jakarta.activation.DataSource;
import javax.activation.FileDataSource; import jakarta.activation.FileDataSource;
import javax.xml.transform.TransformerException;
import java.io.*;
import java.util.*;
public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporter, IExporter, Closeable { public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporter {
private boolean isFacturX = true; private boolean isFacturX = true;
public static final int DefaultZUGFeRDVersion = 2; public static final int DefaultZUGFeRDVersion = 2;
protected boolean ignorePDFAErrors = false;
public ZUGFeRDExporterFromA3 ignorePDFAErrors() {
this.ignorePDFAErrors = true;
return this;
}
protected PDFAConformanceLevel conformanceLevel = PDFAConformanceLevel.UNICODE; 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. * 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; protected PDDocument doc;
private HashMap<String, byte[]> additionalXMLs = new HashMap<String, byte[]>();
protected int ZFVersion = DefaultZUGFeRDVersion; protected int ZFVersion = DefaultZUGFeRDVersion;
private boolean attachZUGFeRDHeaders = true; private boolean attachZUGFeRDHeaders = true;
@@ -199,17 +224,16 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
* @return the filename of the file to be embedded * @return the filename of the file to be embedded
*/ */
public String getFilenameForVersion(int ver, Profile profile) { public String getFilenameForVersion(int ver, Profile profile) {
if (profile.getName().equals("XRECHNUNG")) {
return "xrechnung.xml";
}
if (isFacturX) { if (isFacturX) {
return "factur-x.xml"; return "factur-x.xml";
} else { } else {
if (ver == 1) { if (ver == 1) {
return "ZUGFeRD-invoice.xml"; return "ZUGFeRD-invoice.xml";
} else { } else {
if (profile.getName().equals("XRECHNUNG")) { return "zugferd-invoice.xml";
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 * Generate ZF2.1 files with filename factur-x.xml
* *
* @return this (fluent setter) * @return this (fluent setter)
* @deprecated * @deprecated It's now the default anyway
*/ */
@Deprecated
public ZUGFeRDExporterFromA3 setFacturX() { public ZUGFeRDExporterFromA3 setFacturX() {
isFacturX = true; isFacturX = true;
return this; return this;
@@ -235,10 +260,9 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
* *
* @param XRechnungVersion the XRechnung version * @param XRechnungVersion the XRechnung version
*/ */
public void setXRechnungSpecificVersion(String XRechnungVersion) public void setXRechnungSpecificVersion(String XRechnungVersion) {
{ this.XRechnungVersion = XRechnungVersion;
this.XRechnungVersion = XRechnungVersion; }
}
/*** /***
@@ -258,7 +282,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
*/ */
public ZUGFeRDExporterFromA3 load(byte[] pdfBinary) throws IOException { public ZUGFeRDExporterFromA3 load(byte[] pdfBinary) throws IOException {
ensurePDFIsValid(new ByteArrayDataSource(new ByteArrayInputStream(pdfBinary))); ensurePDFIsValid(new ByteArrayDataSource(new ByteArrayInputStream(pdfBinary)));
doc = PDDocument.load(pdfBinary); doc = Loader.loadPDF(pdfBinary);
return this; return this;
} }
@@ -282,15 +306,16 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
* @param ZUGFeRDfilename the pdf file name * @param ZUGFeRDfilename the pdf file name
* @throws IOException if anything is wrong in the target location * @throws IOException if anything is wrong in the target location
*/ */
@Override
public void export(String ZUGFeRDfilename) throws IOException { public void export(String ZUGFeRDfilename) throws IOException {
if (!documentPrepared) { if (!documentPrepared) {
prepareDocument(); prepareDocument();
} }
if ((!fileAttached) && (attachZUGFeRDHeaders)) { if ((!fileAttached) && (attachZUGFeRDHeaders)) {
throw new IOException( throw new IOException(
"File must be attached (usually with setTransaction) before perfoming this operation"); "File must be attached (usually with setTransaction) before perfoming this operation");
} }
doc.save(ZUGFeRDfilename); doc.save(ZUGFeRDfilename, CompressParameters.NO_COMPRESSION);
if (!disableAutoClose) { if (!disableAutoClose) {
close(); close();
} }
@@ -309,15 +334,16 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
* @param output the OutputStream * @param output the OutputStream
* @throws IOException if anything is wrong in the OutputStream * @throws IOException if anything is wrong in the OutputStream
*/ */
@Override
public void export(OutputStream output) throws IOException { public void export(OutputStream output) throws IOException {
if (!documentPrepared) { if (!documentPrepared) {
prepareDocument(); prepareDocument();
} }
if ((!fileAttached) && (attachZUGFeRDHeaders)) { if ((!fileAttached) && (attachZUGFeRDHeaders)) {
throw new IOException( throw new IOException(
"File must be attached (usually with setTransaction) before perfoming this operation"); "File must be attached (usually with setTransaction) before perfoming this operation");
} }
doc.save(output); doc.save(output, CompressParameters.NO_COMPRESSION);
if (!disableAutoClose) { if (!disableAutoClose) {
close(); close();
} }
@@ -371,7 +397,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
ef.setSize(data.length); ef.setSize(data.length);
ef.setCreationDate(new GregorianCalendar()); ef.setCreationDate(new GregorianCalendar());
ef.setModDate(GregorianCalendar.getInstance()); ef.setModDate(Calendar.getInstance());
fs.setEmbeddedFile(ef); fs.setEmbeddedFile(ef);
@@ -403,7 +429,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
doc.getDocumentCatalog().setNames(names); doc.getDocumentCatalog().setNames(names);
// AF entry (Array) in catalog with the FileSpec // 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)) { if ((AFEntry == null)) {
COSArray cosArray = new COSArray(); COSArray cosArray = new COSArray();
cosArray.add(fs); cosArray.add(fs);
@@ -413,7 +439,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
cosArray.add(fs); cosArray.add(fs);
doc.getDocumentCatalog().getCOSObject().setItem("AF", cosArray); doc.getDocumentCatalog().getCOSObject().setItem("AF", cosArray);
} else if ((AFEntry instanceof COSObject) && } else if ((AFEntry instanceof COSObject) &&
((COSObject) AFEntry).getObject() instanceof COSArray) { ((COSObject) AFEntry).getObject() instanceof COSArray) {
COSArray cosArray = (COSArray) ((COSObject) AFEntry).getObject(); COSArray cosArray = (COSArray) ((COSObject) AFEntry).getObject();
cosArray.add(fs); cosArray.add(fs);
} else { } else {
@@ -507,19 +533,18 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
*/ */
protected void addXMP(XMPMetadata metadata) { protected void addXMP(XMPMetadata metadata) {
String metaDataVersion = null; // default will be used String metaDataVersion = null; // default will be used
// The XRechnung version may be settable from outside. // The XRechnung version may be settable from outside.
if ((this.XRechnungVersion != null) && (this.profile != null) && 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;
metaDataVersion = this.XRechnungVersion; }
}
if (attachZUGFeRDHeaders) { if (attachZUGFeRDHeaders) {
XMPSchemaZugferd zf = new XMPSchemaZugferd(metadata, ZFVersion, isFacturX, xmlProvider.getProfile(), XMPSchemaZugferd zf = new XMPSchemaZugferd(metadata, ZFVersion, isFacturX, xmlProvider.getProfile(),
getNamespaceForVersion(ZFVersion), getPrefixForVersion(ZFVersion), getNamespaceForVersion(ZFVersion), getPrefixForVersion(ZFVersion),
getFilenameForVersion(ZFVersion, xmlProvider.getProfile()), metaDataVersion); getFilenameForVersion(ZFVersion, xmlProvider.getProfile()), metaDataVersion);
metadata.addSchema(zf); metadata.addSchema(zf);
} }
@@ -529,9 +554,8 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
metadata.addSchema(pdfaex); metadata.addSchema(pdfaex);
} }
private void removeCidSet(PDDocumentCatalog catalog, PDDocument doc) private void removeCidSet(PDDocument doc)
throws IOException throws IOException {
{
// https://github.com/ZUGFeRD/mustangproject/issues/249 // https://github.com/ZUGFeRD/mustangproject/issues/249
COSName cidSet = COSName.getPDFName("CIDSet"); COSName cidSet = COSName.getPDFName("CIDSet");
@@ -550,6 +574,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
PDType0Font typedFont = (PDType0Font) pdFont; PDType0Font typedFont = (PDType0Font) pdFont;
if (typedFont.getDescendantFont() instanceof PDCIDFontType2) { if (typedFont.getDescendantFont() instanceof PDCIDFontType2) {
@SuppressWarnings("unused")
PDCIDFontType2 f = (PDCIDFontType2) typedFont.getDescendantFont(); PDCIDFontType2 f = (PDCIDFontType2) typedFont.getDescendantFont();
PDFontDescriptor fontDescriptor = pdFont.getFontDescriptor(); PDFontDescriptor fontDescriptor = pdFont.getFontDescriptor();
@@ -571,7 +596,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
metadata = new PDMetadata(doc); metadata = new PDMetadata(doc);
cat.setMetadata(metadata); cat.setMetadata(metadata);
removeCidSet(cat, doc); removeCidSet(doc);
xmp = getXmpMetadata(); xmp = getXmpMetadata();
writeAdobePDFSchema(xmp); writeAdobePDFSchema(xmp);
writePDFAIdentificationSchema(xmp); writePDFAIdentificationSchema(xmp);
@@ -608,6 +633,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
* <code>setZUGFeRDXMLData(byte[] zugferdData)</code> * <code>setZUGFeRDXMLData(byte[] zugferdData)</code>
* @throws IOException if anything is wrong with already loaded PDF * @throws IOException if anything is wrong with already loaded PDF
*/ */
@Override
public IExporter setTransaction(IExportableTransaction trans) throws IOException { public IExporter setTransaction(IExportableTransaction trans) throws IOException {
this.trans = trans; this.trans = trans;
return prepare(); return prepare();
@@ -618,22 +644,20 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
xmlProvider.generateXML(trans); xmlProvider.generateXML(trans);
String filename = getFilenameForVersion(ZFVersion, xmlProvider.getProfile()); String filename = getFilenameForVersion(ZFVersion, xmlProvider.getProfile());
String relationship = "Alternative"; String relationship = "Alternative";
// ZUGFeRD 2.1.1 Technical Supplement | Part A | 2.2.2. Data Relationship // 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 // 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 // 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()) ||
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";
{ }
relationship = "Data"; }
}
}
PDFAttachGenericFile(doc, filename, relationship, PDFAttachGenericFile(doc, filename, relationship,
"Invoice metadata conforming to ZUGFeRD standard (http://www.ferd-net.de/front_content.php?idcat=231&lang=4)", "Invoice metadata conforming to ZUGFeRD standard (http://www.ferd-net.de/front_content.php?idcat=231&lang=4)",
"text/xml", xmlProvider.getXML()); "text/xml", xmlProvider.getXML());
for (FileAttachment attachment : fileAttachments) { for (FileAttachment attachment : fileAttachments) {
PDFAttachGenericFile(doc, attachment.getFilename(), attachment.getRelation(), attachment.getDescription(), attachment.getMimetype(), attachment.getData()); PDFAttachGenericFile(doc, attachment.getFilename(), attachment.getRelation(), attachment.getDescription(), attachment.getMimetype(), attachment.getData());
@@ -645,12 +669,12 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
/** /**
* Reads the XMPMetadata from the PDDocument, if it exists. * Reads the XMPMetadata from the PDDocument, if it exists.
* Otherwise creates XMPMetadata. * Otherwise creates XMPMetadata.
*
* @return the finished XMPMetadata object * @return the finished XMPMetadata object
* @throws IOException when e.g. XmpParsingException * @throws IOException when e.g. XmpParsingException
*/ */
protected XMPMetadata getXmpMetadata() protected XMPMetadata getXmpMetadata()
throws IOException throws IOException {
{
PDMetadata meta = doc.getDocumentCatalog().getMetadata(); PDMetadata meta = doc.getDocumentCatalog().getMetadata();
if ((meta != null) && (meta.getLength() > 0)) { if ((meta != null) && (meta.getLength() > 0)) {
try { 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 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. * Sets the PDFVersion to 1.4 if the field is empty.
*
* @param xmp the metadata as XML * @param xmp the metadata as XML
*/ */
protected void writeAdobePDFSchema(XMPMetadata xmp) { protected void writeAdobePDFSchema(XMPMetadata xmp) {
@@ -713,13 +738,13 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
} }
protected PDFAIdentificationSchema getPDFAIdentificationSchema(XMPMetadata xmp) { protected PDFAIdentificationSchema getPDFAIdentificationSchema(XMPMetadata xmp) {
PDFAIdentificationSchema pdfaid = xmp.getPDFIdentificationSchema(); PDFAIdentificationSchema pdfaid = xmp.getPDFAIdentificationSchema();
if (pdfaid != null) if (pdfaid != null)
if (overwrite) if (overwrite)
xmp.removeSchema(pdfaid); xmp.removeSchema(pdfaid);
else else
return pdfaid; return pdfaid;
return xmp.createAndAddPFAIdentificationSchema(); return xmp.createAndAddPDFAIdentificationSchema();
} }
protected void writeDublinCoreSchema(XMPMetadata xmp) { 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())) if (overwrite || isEmpty(xsb.getCreatorTool()) || "UnknownApplication".equals(xsb.getCreatorTool()))
xsb.setCreatorTool(creatorTool); xsb.setCreatorTool(creatorTool);
if (overwrite || xsb.getCreateDate() == null) if (overwrite || xsb.getCreateDate() == null)
xsb.setCreateDate(GregorianCalendar.getInstance()); xsb.setCreateDate(Calendar.getInstance());
} }
protected XMPBasicSchema getXmpBasicSchema(XMPMetadata xmp) { 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 * 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 * @throws IOException if the ICC file cannot be read or attached to doc
*/ */
protected void addSRGBOutputIntend() protected void addSRGBOutputIntend()
throws IOException throws IOException {
{
if (!doc.getDocumentCatalog().getOutputIntents().isEmpty()) { if (!doc.getDocumentCatalog().getOutputIntents().isEmpty()) {
return; return;
} }
@@ -863,12 +888,12 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
public ZUGFeRDExporterFromA3 setZUGFeRDVersion(EStandard est, int version) { public ZUGFeRDExporterFromA3 setZUGFeRDVersion(EStandard est, int version) {
this.ZFVersion = version; this.ZFVersion = version;
if ((version<1) || (version>2)) { if ((version < 1) || (version > 2)) {
throw new IllegalArgumentException("Version not supported"); throw new IllegalArgumentException("Version not supported");
} }
int generation=version; int generation = version;
if ((est==EStandard.facturx)&&(version==1)) { if ((est == EStandard.facturx) && (version == 1)) {
generation=2; generation = 2;
} }
if (generation == 1) { if (generation == 1) {
ZUGFeRD1PullProvider z1p = new ZUGFeRD1PullProvider(); ZUGFeRD1PullProvider z1p = new ZUGFeRD1PullProvider();

View File

@@ -20,6 +20,13 @@
*/ */
package org.mustangproject.ZUGFeRD; 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.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog; import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.common.PDMetadata; 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.schema.PDFAIdentificationSchema;
import org.apache.xmpbox.xml.DomXmpParser; import org.apache.xmpbox.xml.DomXmpParser;
import org.apache.xmpbox.xml.XmpParsingException; import org.apache.xmpbox.xml.XmpParsingException;
import org.mustangproject.FileAttachment;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.activation.DataSource; import jakarta.activation.DataSource;
import java.io.*;
/*** /***
* Auto-detects the source PDF-A-Version and acts accordingly * Auto-detects the source PDF-A-Version and acts accordingly
@@ -43,16 +50,31 @@ public class ZUGFeRDExporterFromPDFA implements IZUGFeRDExporter {
protected IZUGFeRDExporter theExporter; protected IZUGFeRDExporter theExporter;
protected boolean ignorePDFAErrors = false;
public ZUGFeRDExporterFromPDFA ignorePDFAErrors() {
this.ignorePDFAErrors = true;
return this;
}
protected void determineAndSetExporter(int PDFAVersion) { protected void determineAndSetExporter(int PDFAVersion) {
if (PDFAVersion == 3) { if (PDFAVersion == 3) {
theExporter = new ZUGFeRDExporterFromA3(); theExporter = new ZUGFeRDExporterFromA3();
if (ignorePDFAErrors) {
((ZUGFeRDExporterFromA3)theExporter).ignorePDFAErrors();
}
} else if (PDFAVersion == 1) { } else if (PDFAVersion == 1) {
theExporter = new ZUGFeRDExporterFromA1(); theExporter = new ZUGFeRDExporterFromA1();
if (ignorePDFAErrors) {
((ZUGFeRDExporterFromA1)theExporter).ignorePDFAErrors();
}
} else { } else {
throw new IllegalArgumentException("PDF-A version not supported"); throw new IllegalArgumentException("PDF-A version not supported");
} }
} }
protected IZUGFeRDExporter getExporter() {
public IZUGFeRDExporter getExporter() {
if (theExporter==null) { if (theExporter==null) {
throw new RuntimeException("In ZUGFeRDExporterFromPDFA, source must always be loaded before other operations are performed."); 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 * @throws IOException
*/ */
private int getPDFAVersion(byte[] byteArrayInputStream) throws IOException { private int getPDFAVersion(byte[] byteArrayInputStream) throws IOException {
PDDocument document = PDDocument.load(byteArrayInputStream); PDDocument document = Loader.loadPDF(byteArrayInputStream);
PDDocumentCatalog catalog = document.getDocumentCatalog(); PDDocumentCatalog catalog = document.getDocumentCatalog();
PDMetadata metadata = catalog.getMetadata(); PDMetadata metadata = catalog.getMetadata();
// the PDF version we could get through the document but we want the PDF-A version, // 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(); DomXmpParser xmpParser = new DomXmpParser();
XMPMetadata xmp = xmpParser.parse(metadata.createInputStream()); XMPMetadata xmp = xmpParser.parse(metadata.createInputStream());
PDFAIdentificationSchema pdfaSchema = xmp.getPDFIdentificationSchema(); PDFAIdentificationSchema pdfaSchema = xmp.getPDFAIdentificationSchema();
if (pdfaSchema != null) { if (pdfaSchema != null) {
return pdfaSchema.getPart(); return pdfaSchema.getPart();
} }
@@ -147,16 +169,16 @@ public class ZUGFeRDExporterFromPDFA implements IZUGFeRDExporter {
return getExporter().setCreator(creator); return getExporter().setCreator(creator);
} }
public ZUGFeRDExporterFromPDFA setProfile(Profile p) { public IZUGFeRDExporter setProfile(Profile p) {
return (ZUGFeRDExporterFromPDFA) getExporter().setProfile(p); return getExporter().setProfile(p);
} }
public ZUGFeRDExporterFromPDFA setProfile(String profileName) { public IZUGFeRDExporter setProfile(String profileName) {
Profile p = Profiles.getByName(profileName); Profile p = Profiles.getByName(profileName);
if (p==null) { if (p==null) {
throw new RuntimeException("Profile not found."); throw new RuntimeException("Profile not found.");
} }
return (ZUGFeRDExporterFromPDFA) getExporter().setProfile(p); return getExporter().setProfile(p);
} }
public IZUGFeRDExporter setConformanceLevel(PDFAConformanceLevel newLevel) { public IZUGFeRDExporter setConformanceLevel(PDFAConformanceLevel newLevel) {
@@ -227,5 +249,14 @@ public class ZUGFeRDExporterFromPDFA implements IZUGFeRDExporter {
public void export(OutputStream output) throws IOException { public void export(OutputStream output) throws IOException {
getExporter().export(output); 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);
}
} }

View File

@@ -1,53 +1,275 @@
package org.mustangproject.ZUGFeRD; package org.mustangproject.ZUGFeRD;
import java.io.IOException; import org.apache.commons.io.IOUtils;
import java.io.InputStream; import org.apache.pdfbox.Loader;
import java.math.BigDecimal; import org.apache.pdfbox.pdmodel.PDDocument;
import java.math.RoundingMode; import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
import java.nio.charset.StandardCharsets; import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
import java.text.ParseException; import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
import java.text.SimpleDateFormat; import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
import java.util.ArrayList; import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
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.mustangproject.*; import org.mustangproject.*;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import org.w3c.dom.NodeList; 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 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() { public ZUGFeRDInvoiceImporter() {
super(); //constructor for extending classes
} }
public ZUGFeRDInvoiceImporter(String filename) { public ZUGFeRDInvoiceImporter(String pdfFilename) {
super(filename); setPDFFilename(pdfFilename);
} }
public ZUGFeRDInvoiceImporter(InputStream stream) { public ZUGFeRDInvoiceImporter(InputStream pdfStream) {
super(stream); setInputStream(pdfStream);
} }
public void fromXML(String XML) { public void setPDFFilename(String pdfFilename) {
try { try (InputStream bis = Files.newInputStream(Paths.get(pdfFilename), StandardOpenOption.READ)) {
containsMeta = true; extractLowLevel(bis);
setRawXML(XML.getBytes(StandardCharsets.UTF_8)); } catch (final IOException e) {
} catch (IOException e) { LOGGER.error("Failed to extract ZUGFeRD data", e);
LOGGER.error(e.getMessage(), e); throw new ZUGFeRDExportException(e);
} }
} }
public void setInputStream(InputStream pdfStream) {
try {
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 * 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 { public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseException {
String number = ""; String number = "";
String typeCode = null;
String deliveryPeriodStart = null;
String deliveryPeriodEnd = null;
/* /*
* dummywerte sind derzeit noch setDueDate setIssueDate setDeliveryDate * dummywerte sind derzeit noch setDueDate setIssueDate setDeliveryDate
* setSender setRecipient setnumber bspw. due date * setSender setRecipient setnumber bspw. due date
@@ -68,9 +293,19 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
XPath xpath = xpathFact.newXPath(); XPath xpath = xpathFact.newXPath();
XPathExpression xpr = xpath.compile("//*[local-name()=\"SellerTradeParty\"]|//*[local-name()=\"AccountingSupplierParty\"]/*"); XPathExpression xpr = xpath.compile("//*[local-name()=\"SellerTradeParty\"]|//*[local-name()=\"AccountingSupplierParty\"]/*");
NodeList SellerNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); 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\"]/*"); xpr = xpath.compile("//*[local-name()=\"BuyerTradeParty\"]|//*[local-name()=\"AccountingCustomerParty\"]/*");
NodeList BuyerNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); 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\"]"); xpr = xpath.compile("//*[local-name()=\"ExchangedDocument\"]|//*[local-name()=\"HeaderExchangedDocument\"]");
NodeList ExchangedDocumentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); NodeList ExchangedDocumentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
@@ -78,32 +313,42 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
BigDecimal expectedGrandTotal = null; BigDecimal expectedGrandTotal = null;
NodeList totalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); NodeList totalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
if (totalNodes.getLength() > 0) { if (totalNodes.getLength() > 0) {
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 issueDate = null;
Date dueDate = null; Date dueDate = null;
Date deliveryDate = null; Date deliveryDate = null;
String despatchAdviceReferencedDocument = null;
for (int i = 0; i < ExchangedDocumentNodes.getLength(); i++) { for (int i = 0; i < ExchangedDocumentNodes.getLength(); i++) {
// nodes.item(i).getTextContent())) {
Node exchangedDocumentNode = ExchangedDocumentNodes.item(i); Node exchangedDocumentNode = ExchangedDocumentNodes.item(i);
NodeList exchangedDocumentChilds = exchangedDocumentNode.getChildNodes(); NodeList exchangedDocumentChilds = exchangedDocumentNode.getChildNodes();
for (int documentChildIndex = 0; documentChildIndex < exchangedDocumentChilds for (int documentChildIndex = 0; documentChildIndex < exchangedDocumentChilds.getLength(); documentChildIndex++) {
.getLength(); documentChildIndex++) {
Node item = exchangedDocumentChilds.item(documentChildIndex); Node item = exchangedDocumentChilds.item(documentChildIndex);
if ((item.getLocalName() != null) && (item.getLocalName().equals("ID"))) { 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"))) { if ((item.getLocalName() != null) && (item.getLocalName().equals("IssueDateTime"))) {
NodeList issueDateTimeChilds = item.getChildNodes(); NodeList issueDateTimeChilds = item.getChildNodes();
for (int issueDateChildIndex = 0; issueDateChildIndex < issueDateTimeChilds for (int issueDateChildIndex = 0; issueDateChildIndex < issueDateTimeChilds.getLength(); issueDateChildIndex++) {
.getLength(); issueDateChildIndex++) {
if ((issueDateTimeChilds.item(issueDateChildIndex).getLocalName() != null) if ((issueDateTimeChilds.item(issueDateChildIndex).getLocalName() != null)
&& (issueDateTimeChilds.item(issueDateChildIndex).getLocalName() && (issueDateTimeChilds.item(issueDateChildIndex).getLocalName().equals("DateTimeString"))) {
.equals("DateTimeString"))) { issueDate = new SimpleDateFormat("yyyyMMdd").parse(XMLTools.trimOrNull(issueDateTimeChilds.item(issueDateChildIndex)));
issueDate = new SimpleDateFormat("yyyyMMdd")
.parse(issueDateTimeChilds.item(issueDateChildIndex).getTextContent());
} }
} }
} }
@@ -113,50 +358,49 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
if (rootNode.equals("Invoice")) { if (rootNode.equals("Invoice")) {
// UBL... // UBL...
number = extractString("//*[local-name()=\"Invoice\"]/*[local-name()=\"ID\"]").trim(); number = extractString("//*[local-name()=\"Invoice\"]/*[local-name()=\"ID\"]").trim();
issueDate = new SimpleDateFormat("yyyy-MM-dd") issueDate = new SimpleDateFormat("yyyy-MM-dd").parse(extractString("//*[local-name()=\"Invoice\"]/*[local-name()=\"IssueDate\"]").trim());
.parse(extractString("//*[local-name()=\"Invoice\"]/*[local-name()=\"IssueDate\"]").trim());
String dueDt = extractString("//*[local-name()=\"Invoice\"]/*[local-name()=\"DueDate\"]").trim(); String dueDt = extractString("//*[local-name()=\"Invoice\"]/*[local-name()=\"DueDate\"]").trim();
if (dueDt.length() > 0) { if (dueDt.length() > 0) {
dueDate = new SimpleDateFormat("yyyy-MM-dd") dueDate = new SimpleDateFormat("yyyy-MM-dd").parse(dueDt);
.parse(dueDt);
} }
String deliveryDt = extractString("//*[local-name()=\"Delivery\"]/*[local-name()=\"ActualDeliveryDate\"]").trim(); String deliveryDt = extractString("//*[local-name()=\"Delivery\"]/*[local-name()=\"ActualDeliveryDate\"]").trim();
if (deliveryDt.length() > 0) { if (deliveryDt.length() > 0) {
deliveryDate = new SimpleDateFormat("yyyy-MM-dd") deliveryDate = new SimpleDateFormat("yyyy-MM-dd").parse(deliveryDt);
.parse(deliveryDt);
} }
} }
xpr = xpath.compile("//*[local-name()=\"ApplicableHeaderTradeDelivery\"]"); xpr = xpath.compile("//*[local-name()=\"ApplicableHeaderTradeDelivery\"]");
NodeList headerTradeDeliveryNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); NodeList headerTradeDeliveryNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
for (int i = 0; i < headerTradeDeliveryNodes.getLength(); i++) { for (int i = 0; i < headerTradeDeliveryNodes.getLength(); i++) {
// nodes.item(i).getTextContent())) {
Node headerTradeDeliveryNode = headerTradeDeliveryNodes.item(i); Node headerTradeDeliveryNode = headerTradeDeliveryNodes.item(i);
NodeList headerTradeDeliveryChilds = headerTradeDeliveryNode.getChildNodes(); NodeList headerTradeDeliveryChilds = headerTradeDeliveryNode.getChildNodes();
for (int deliveryChildIndex = 0; deliveryChildIndex < headerTradeDeliveryChilds for (int deliveryChildIndex = 0; deliveryChildIndex < headerTradeDeliveryChilds.getLength(); deliveryChildIndex++) {
.getLength(); deliveryChildIndex++) { if (headerTradeDeliveryChilds.item(deliveryChildIndex).getLocalName() != null) {
if ((headerTradeDeliveryChilds.item(deliveryChildIndex).getLocalName() != null) if (headerTradeDeliveryChilds.item(deliveryChildIndex).getLocalName().equals("ActualDeliverySupplyChainEvent")) {
&& (headerTradeDeliveryChilds.item(deliveryChildIndex).getLocalName() NodeList actualDeliveryChilds = headerTradeDeliveryChilds.item(deliveryChildIndex).getChildNodes();
.equals("ActualDeliverySupplyChainEvent"))) { for (int actualDeliveryChildIndex = 0; actualDeliveryChildIndex < actualDeliveryChilds.getLength(); actualDeliveryChildIndex++) {
NodeList actualDeliveryChilds = headerTradeDeliveryChilds.item(deliveryChildIndex).getChildNodes(); if ((actualDeliveryChilds.item(actualDeliveryChildIndex).getLocalName() != null)
for (int actualDeliveryChildIndex = 0; actualDeliveryChildIndex < actualDeliveryChilds && (actualDeliveryChilds.item(actualDeliveryChildIndex).getLocalName().equals("OccurrenceDateTime"))) {
.getLength(); actualDeliveryChildIndex++) { NodeList occurenceChilds = actualDeliveryChilds.item(actualDeliveryChildIndex).getChildNodes();
if ((actualDeliveryChilds.item(actualDeliveryChildIndex).getLocalName() != null) for (int occurenceChildIndex = 0; occurenceChildIndex < occurenceChilds.getLength(); occurenceChildIndex++) {
&& (actualDeliveryChilds.item(actualDeliveryChildIndex).getLocalName() if ((occurenceChilds.item(occurenceChildIndex).getLocalName() != null)
.equals("OccurrenceDateTime"))) { && (occurenceChilds.item(occurenceChildIndex).getLocalName().equals("DateTimeString"))) {
NodeList occurenceChilds = actualDeliveryChilds.item(actualDeliveryChildIndex) deliveryDate = new SimpleDateFormat("yyyyMMdd").parse(XMLTools.trimOrNull(occurenceChilds.item(occurenceChildIndex)));
.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());
} }
} }
} }
} }
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 buyerOrderIssuerAssignedID = null;
String sellerOrderIssuerAssignedID = null; String sellerOrderIssuerAssignedID = null;
for (int i = 0; i < headerTradeAgreementNodes.getLength(); i++) { for (int i = 0; i < headerTradeAgreementNodes.getLength(); i++) {
// nodes.item(i).getTextContent())) { // XMLTools.trimOrNull(nodes.item(i)))) {
Node headerTradeAgreementNode = headerTradeAgreementNodes.item(i); Node headerTradeAgreementNode = headerTradeAgreementNodes.item(i);
NodeList headerTradeAgreementChilds = headerTradeAgreementNode.getChildNodes(); NodeList headerTradeAgreementChilds = headerTradeAgreementNode.getChildNodes();
for (int agreementChildIndex = 0; agreementChildIndex < headerTradeAgreementChilds for (int agreementChildIndex = 0; agreementChildIndex < headerTradeAgreementChilds.getLength(); agreementChildIndex++) {
.getLength(); agreementChildIndex++) { if (headerTradeAgreementChilds.item(agreementChildIndex).getLocalName() != null) {
if ((headerTradeAgreementChilds.item(agreementChildIndex).getLocalName() != null) if (headerTradeAgreementChilds.item(agreementChildIndex).getLocalName().equals("BuyerOrderReferencedDocument")) {
&& (headerTradeAgreementChilds.item(agreementChildIndex).getLocalName() NodeList buyerOrderChilds = headerTradeAgreementChilds.item(agreementChildIndex).getChildNodes();
.equals("BuyerOrderReferencedDocument"))) { for (int buyerOrderChildIndex = 0; buyerOrderChildIndex < buyerOrderChilds.getLength(); buyerOrderChildIndex++) {
NodeList buyerOrderChilds = headerTradeAgreementChilds.item(agreementChildIndex).getChildNodes(); if ((buyerOrderChilds.item(buyerOrderChildIndex).getLocalName() != null)
for (int buyerOrderChildIndex = 0; buyerOrderChildIndex < buyerOrderChilds && (buyerOrderChilds.item(buyerOrderChildIndex).getLocalName().equals("IssuerAssignedID"))) {
.getLength(); buyerOrderChildIndex++) { buyerOrderIssuerAssignedID = XMLTools.trimOrNull(buyerOrderChilds.item(buyerOrderChildIndex));
if ((buyerOrderChilds.item(buyerOrderChildIndex).getLocalName() != null) }
&& (buyerOrderChilds.item(buyerOrderChildIndex).getLocalName()
.equals("IssuerAssignedID"))) {
buyerOrderIssuerAssignedID = buyerOrderChilds.item(buyerOrderChildIndex).getTextContent();
} }
} }
}
if ((headerTradeAgreementChilds.item(agreementChildIndex).getLocalName() != null) if (headerTradeAgreementChilds.item(agreementChildIndex).getLocalName().equals("SellerOrderReferencedDocument")) {
&& (headerTradeAgreementChilds.item(agreementChildIndex).getLocalName() NodeList sellerOrderChilds = headerTradeAgreementChilds.item(agreementChildIndex).getChildNodes();
.equals("SellerOrderReferencedDocument"))) { for (int sellerOrderChildIndex = 0; sellerOrderChildIndex < sellerOrderChilds.getLength(); sellerOrderChildIndex++) {
NodeList sellerOrderChilds = headerTradeAgreementChilds.item(agreementChildIndex).getChildNodes(); if ((sellerOrderChilds.item(sellerOrderChildIndex).getLocalName() != null)
for (int sellerOrderChildIndex = 0; sellerOrderChildIndex < sellerOrderChilds && (sellerOrderChilds.item(sellerOrderChildIndex).getLocalName().equals("IssuerAssignedID"))) {
.getLength(); sellerOrderChildIndex++) { sellerOrderIssuerAssignedID = XMLTools.trimOrNull(sellerOrderChilds.item(sellerOrderChildIndex));
if ((sellerOrderChilds.item(sellerOrderChildIndex).getLocalName() != null) }
&& (sellerOrderChilds.item(sellerOrderChildIndex).getLocalName()
.equals("IssuerAssignedID"))) {
sellerOrderIssuerAssignedID = sellerOrderChilds.item(sellerOrderChildIndex).getTextContent();
} }
} }
} }
@@ -201,63 +439,90 @@ 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\"]"); xpr = xpath.compile("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]|//*[local-name()=\"ApplicableSupplyChainTradeSettlement\"]");
NodeList headerTradeSettlementNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); NodeList headerTradeSettlementNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
List<BankDetails> bankDetails = new ArrayList<>();
String directDebitMandateID = null;
String IBAN = null, BIC = null; String IBAN = null, BIC = null;
for (int i = 0; i < headerTradeSettlementNodes.getLength(); i++) { for (int i = 0; i < headerTradeSettlementNodes.getLength(); i++) {
// nodes.item(i).getTextContent())) { // XMLTools.trimOrNull(nodes.item(i)))) {
Node headerTradeSettlementNode = headerTradeSettlementNodes.item(i); Node headerTradeSettlementNode = headerTradeSettlementNodes.item(i);
NodeList headerTradeSettlementChilds = headerTradeSettlementNode.getChildNodes(); NodeList headerTradeSettlementChilds = headerTradeSettlementNode.getChildNodes();
for (int settlementChildIndex = 0; settlementChildIndex < headerTradeSettlementChilds for (int settlementChildIndex = 0; settlementChildIndex < headerTradeSettlementChilds.getLength(); settlementChildIndex++) {
.getLength(); settlementChildIndex++) {
if ((headerTradeSettlementChilds.item(settlementChildIndex).getLocalName() != null) if ((headerTradeSettlementChilds.item(settlementChildIndex).getLocalName() != null)
&& (headerTradeSettlementChilds.item(settlementChildIndex).getLocalName() && (headerTradeSettlementChilds.item(settlementChildIndex).getLocalName().equals("SpecifiedTradePaymentTerms"))) {
.equals("SpecifiedTradePaymentTerms"))) {
NodeList paymentTermChilds = headerTradeSettlementChilds.item(settlementChildIndex).getChildNodes(); NodeList paymentTermChilds = headerTradeSettlementChilds.item(settlementChildIndex).getChildNodes();
for (int paymentTermChildIndex = 0; paymentTermChildIndex < paymentTermChilds for (int paymentTermChildIndex = 0; paymentTermChildIndex < paymentTermChilds.getLength(); paymentTermChildIndex++) {
.getLength(); paymentTermChildIndex++) { if ((paymentTermChilds.item(paymentTermChildIndex).getLocalName() != null) && (paymentTermChilds.item(paymentTermChildIndex).getLocalName().equals("DueDateDateTime"))) {
if ((paymentTermChilds.item(paymentTermChildIndex).getLocalName() != null) && (paymentTermChilds
.item(paymentTermChildIndex).getLocalName().equals("DueDateDateTime"))) {
NodeList dueDateChilds = paymentTermChilds.item(paymentTermChildIndex).getChildNodes(); NodeList dueDateChilds = paymentTermChilds.item(paymentTermChildIndex).getChildNodes();
for (int dueDateChildIndex = 0; dueDateChildIndex < dueDateChilds for (int dueDateChildIndex = 0; dueDateChildIndex < dueDateChilds.getLength(); dueDateChildIndex++) {
.getLength(); dueDateChildIndex++) { if ((dueDateChilds.item(dueDateChildIndex).getLocalName() != null) && (dueDateChilds.item(dueDateChildIndex).getLocalName().equals("DateTimeString"))) {
if ((dueDateChilds.item(dueDateChildIndex).getLocalName() != null) && (dueDateChilds dueDate = new SimpleDateFormat("yyyyMMdd").parse(XMLTools.trimOrNull(dueDateChilds.item(dueDateChildIndex)));
.item(dueDateChildIndex).getLocalName().equals("DateTimeString"))) {
dueDate = new SimpleDateFormat("yyyyMMdd")
.parse(dueDateChilds.item(dueDateChildIndex).getTextContent());
} }
} }
} }
if ((paymentTermChilds.item(paymentTermChildIndex).getLocalName() != null) && (paymentTermChilds.item(paymentTermChildIndex).getLocalName().equals("DirectDebitMandateID"))) {
directDebitMandateID = paymentTermChilds.item(paymentTermChildIndex).getTextContent();
}
} }
} }
if ((headerTradeSettlementChilds.item(settlementChildIndex).getLocalName() != null) if ((headerTradeSettlementChilds.item(settlementChildIndex).getLocalName() != null)
&& (headerTradeSettlementChilds.item(settlementChildIndex).getLocalName() && (headerTradeSettlementChilds.item(settlementChildIndex).getLocalName().equals("SpecifiedTradeSettlementPaymentMeans"))) {
.equals("SpecifiedTradeSettlementPaymentMeans"))) {
NodeList paymentMeansChilds = headerTradeSettlementChilds.item(settlementChildIndex).getChildNodes(); NodeList paymentMeansChilds = headerTradeSettlementChilds.item(settlementChildIndex).getChildNodes();
for (int paymentMeansChildIndex = 0; paymentMeansChildIndex < paymentMeansChilds IBAN = null;
.getLength(); paymentMeansChildIndex++) { BIC = null;
if ((paymentMeansChilds.item(paymentMeansChildIndex).getLocalName() != null) && (paymentMeansChilds for (int paymentMeansChildIndex = 0; paymentMeansChildIndex < paymentMeansChilds.getLength(); paymentMeansChildIndex++) {
.item(paymentMeansChildIndex).getLocalName().equals("PayeePartyCreditorFinancialAccount"))) {
if ((paymentMeansChilds.item(paymentMeansChildIndex).getLocalName() != null) && (paymentMeansChilds.item(paymentMeansChildIndex).getLocalName().equals("PayeePartyCreditorFinancialAccount") || paymentMeansChilds.item(paymentMeansChildIndex).getLocalName().equals("PayerPartyDebtorFinancialAccount"))) {
NodeList accountChilds = paymentMeansChilds.item(paymentMeansChildIndex).getChildNodes(); NodeList accountChilds = paymentMeansChilds.item(paymentMeansChildIndex).getChildNodes();
for (int accountChildIndex = 0; accountChildIndex < accountChilds for (int accountChildIndex = 0; accountChildIndex < accountChilds.getLength(); accountChildIndex++) {
.getLength(); accountChildIndex++) { if ((accountChilds.item(accountChildIndex).getLocalName() != null) && (accountChilds.item(accountChildIndex).getLocalName().equals("IBANID"))) {//CII
if ((accountChilds.item(accountChildIndex).getLocalName() != null) && (accountChilds IBAN = XMLTools.trimOrNull(accountChilds.item(accountChildIndex));
.item(accountChildIndex).getLocalName().equals("IBANID"))) {//CII
IBAN = accountChilds.item(accountChildIndex).getTextContent();
} }
} }
} }
if ((paymentMeansChilds.item(paymentMeansChildIndex).getLocalName() != null) && (paymentMeansChilds if ((paymentMeansChilds.item(paymentMeansChildIndex).getLocalName() != null) && (paymentMeansChilds.item(paymentMeansChildIndex).getLocalName().equals("PayeeSpecifiedCreditorFinancialInstitution"))) {
.item(paymentMeansChildIndex).getLocalName().equals("PayeePartyCreditorFinancialInstitution"))) {
NodeList accountChilds = paymentMeansChilds.item(paymentMeansChildIndex).getChildNodes(); NodeList accountChilds = paymentMeansChilds.item(paymentMeansChildIndex).getChildNodes();
for (int accountChildIndex = 0; accountChildIndex < accountChilds for (int accountChildIndex = 0; accountChildIndex < accountChilds.getLength(); accountChildIndex++) {
.getLength(); accountChildIndex++) { if ((accountChilds.item(accountChildIndex).getLocalName() != null) && (accountChilds.item(accountChildIndex).getLocalName().equals("BICID"))) {//CII
if ((accountChilds.item(accountChildIndex).getLocalName() != null) && (accountChilds BIC = XMLTools.trimOrNull(accountChilds.item(accountChildIndex));
.item(accountChildIndex).getLocalName().equals("BICID"))) {//CII }
BIC = accountChilds.item(accountChildIndex).getTextContent(); }
}
}
if (IBAN != null) {
BankDetails bd = new BankDetails(IBAN);
if (BIC != null) {
bd.setBIC(BIC);
}
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));
} }
} }
} }
@@ -266,39 +531,47 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
} }
} }
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 xpr = xpath.compile("//*[local-name()=\"PaymentMeans\"]"); //UBL only
NodeList paymentMeansNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); NodeList paymentMeansNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
for (int i = 0; i < paymentMeansNodes.getLength(); i++) { for (int i = 0; i < paymentMeansNodes.getLength(); i++) {
// nodes.item(i).getTextContent())) { // XMLTools.trimOrNull(nodes.item(i)))) {
Node paymentMeansNode = paymentMeansNodes.item(i); Node paymentMeansNode = paymentMeansNodes.item(i);
NodeList paymentMeansChilds = paymentMeansNode.getChildNodes(); NodeList paymentMeansChilds = paymentMeansNode.getChildNodes();
for (int meansChildIndex = 0; meansChildIndex < paymentMeansChilds for (int meansChildIndex = 0; meansChildIndex < paymentMeansChilds.getLength(); meansChildIndex++) {
.getLength(); meansChildIndex++) {
if ((paymentMeansChilds.item(meansChildIndex).getLocalName() != null) if ((paymentMeansChilds.item(meansChildIndex).getLocalName() != null)
&& (paymentMeansChilds.item(meansChildIndex).getLocalName() && (paymentMeansChilds.item(meansChildIndex).getLocalName().equals("PayeeFinancialAccount"))) {
.equals("PayeeFinancialAccount"))) {
NodeList paymentTermChilds = paymentMeansChilds.item(meansChildIndex).getChildNodes(); NodeList paymentTermChilds = paymentMeansChilds.item(meansChildIndex).getChildNodes();
for (int paymentTermChildIndex = 0; paymentTermChildIndex < paymentTermChilds for (int paymentTermChildIndex = 0; paymentTermChildIndex < paymentTermChilds.getLength(); paymentTermChildIndex++) {
.getLength(); paymentTermChildIndex++) { if ((paymentTermChilds.item(paymentTermChildIndex).getLocalName() != null) && (paymentTermChilds.item(paymentTermChildIndex).getLocalName().equals("ID"))) {
if ((paymentTermChilds.item(paymentTermChildIndex).getLocalName() != null) && (paymentTermChilds IBAN = XMLTools.trimOrNull(paymentTermChilds.item(paymentTermChildIndex));
.item(paymentTermChildIndex).getLocalName().equals("ID"))) { if (IBAN != null) {
IBAN = paymentTermChilds.item(paymentTermChildIndex).getTextContent(); BankDetails bd = new BankDetails(IBAN);
bankDetails.add(bd);
}
} }
} }
} }
} }
} }
zpp.setDueDate(dueDate).setDeliveryDate(deliveryDate).setIssueDate(issueDate) zpp.setDueDate(dueDate).setDeliveryDate(deliveryDate).setIssueDate(issueDate).setSender(new TradeParty(SellerNodes)).setRecipient(new TradeParty(BuyerNodes)).setNumber(number).setDocumentCode(typeCode);
.setSender(new TradeParty(SellerNodes)).setRecipient(new TradeParty(BuyerNodes)).setNumber(number);
if (IBAN != null) { if ((directDebitMandateID != null) && (IBAN != null)) {
BankDetails bd = new BankDetails(IBAN); DirectDebit d = new DirectDebit(IBAN, directDebitMandateID);
if (BIC != null) { zpp.getSender().addDebitDetails(d);
bd.setBIC(BIC); }
}
zpp.getSender().addBankDetails(bd); bankDetails.forEach(bankDetail -> zpp.getSender().addBankDetails(bankDetail));
if (payeeNodes.getLength() > 0) {
zpp.setPayee(new TradeParty(payeeNodes));
} }
if (buyerOrderIssuerAssignedID != null) { if (buyerOrderIssuerAssignedID != null) {
@@ -307,15 +580,17 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
if (sellerOrderIssuerAssignedID != null) { if (sellerOrderIssuerAssignedID != null) {
zpp.setSellerOrderReferencedDocumentID(sellerOrderIssuerAssignedID); 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()); 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\"]"); xpr = xpath.compile("//*[local-name()=\"BuyerReference\"]");
String buyerReference = null; String buyerReference = null;
totalNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); prepaidNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
if (totalNodes.getLength() > 0) { if (prepaidNodes.getLength() > 0) {
buyerReference = totalNodes.item(0).getTextContent(); buyerReference = XMLTools.trimOrNull(prepaidNodes.item(0));
} }
if (buyerReference != null) { if (buyerReference != null) {
zpp.setReferenceNumber(buyerReference); 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 // item level charges+allowances are not yet handled but a lower item price will
// be read, // be read,
// so the invoice remains arithmetically correct // so the invoice remains arithmetically correct
@@ -344,6 +629,7 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
boolean isCharge = true; boolean isCharge = true;
String chargeAmount = null; String chargeAmount = null;
String reason = null; String reason = null;
String reasonCode = null;
String taxPercent = null; String taxPercent = null;
for (int chargeChildIndex = 0; chargeChildIndex < chargeNodeChilds.getLength(); chargeChildIndex++) { for (int chargeChildIndex = 0; chargeChildIndex < chargeNodeChilds.getLength(); chargeChildIndex++) {
String chargeChildName = chargeNodeChilds.item(chargeChildIndex).getLocalName(); String chargeChildName = chargeNodeChilds.item(chargeChildIndex).getLocalName();
@@ -351,26 +637,24 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
if (chargeChildName.equals("ChargeIndicator")) { if (chargeChildName.equals("ChargeIndicator")) {
NodeList indicatorChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes(); NodeList indicatorChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes();
for (int indicatorChildIndex = 0; indicatorChildIndex < indicatorChilds for (int indicatorChildIndex = 0; indicatorChildIndex < indicatorChilds.getLength(); indicatorChildIndex++) {
.getLength(); indicatorChildIndex++) {
if ((indicatorChilds.item(indicatorChildIndex).getLocalName() != null) if ((indicatorChilds.item(indicatorChildIndex).getLocalName() != null)
&& (indicatorChilds.item(indicatorChildIndex).getLocalName() && (indicatorChilds.item(indicatorChildIndex).getLocalName().equals("Indicator"))) {
.equals("Indicator"))) { isCharge = XMLTools.trimOrNull(indicatorChilds.item(indicatorChildIndex)).equalsIgnoreCase("true");
isCharge = indicatorChilds.item(indicatorChildIndex).getTextContent()
.equalsIgnoreCase("true");
} }
} }
} else if (chargeChildName.equals("ActualAmount")) { } else if (chargeChildName.equals("ActualAmount")) {
chargeAmount = chargeNodeChilds.item(chargeChildIndex).getTextContent(); chargeAmount = XMLTools.trimOrNull(chargeNodeChilds.item(chargeChildIndex));
} else if (chargeChildName.equals("Reason")) { } 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")) { } else if (chargeChildName.equals("CategoryTradeTax")) {
NodeList taxChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes(); NodeList taxChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes();
for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) { for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) {
String taxItemName = taxChilds.item(taxChildIndex).getLocalName(); String taxItemName = taxChilds.item(taxChildIndex).getLocalName();
if ((taxItemName != null) && (taxItemName.equals("RateApplicablePercent") if ((taxItemName != null) && (taxItemName.equals("RateApplicablePercent") || taxItemName.equals("ApplicablePercent"))) {
|| taxItemName.equals("ApplicablePercent"))) { taxPercent = XMLTools.trimOrNull(taxChilds.item(taxChildIndex));
taxPercent = taxChilds.item(taxChildIndex).getTextContent();
} }
} }
} }
@@ -382,16 +666,21 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
if (reason != null) { if (reason != null) {
c.setReason(reason); c.setReason(reason);
} }
if (reasonCode != null) {
c.setReasonCode(reasonCode);
}
if (taxPercent != null) { if (taxPercent != null) {
c.setTaxPercent(new BigDecimal(taxPercent)); c.setTaxPercent(new BigDecimal(taxPercent));
} }
zpp.addCharge(c); zpp.addCharge(c);
} else { } else {
Allowance a = new Allowance(new BigDecimal(chargeAmount)); Allowance a = new Allowance(new BigDecimal(chargeAmount));
if (reason != null) { if (reason != null) {
a.setReason(reason); a.setReason(reason);
} }
if (reasonCode != null) {
a.setReasonCode(reasonCode);
}
if (taxPercent != null) { if (taxPercent != null) {
a.setTaxPercent(new BigDecimal(taxPercent)); a.setTaxPercent(new BigDecimal(taxPercent));
} }
@@ -401,7 +690,8 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
} }
TransactionCalculator tc = new TransactionCalculator(zpp); TransactionCalculator tc = new TransactionCalculator(zpp);
String expectedStringTotalGross = tc.getGrandTotal().toPlainString(); String expectedStringTotalGross = tc.getGrandTotal()
.subtract(Objects.requireNonNullElse(zpp.getTotalPrepaidAmount(), BigDecimal.ZERO)).toPlainString();
EStandard whichType; EStandard whichType;
try { try {
whichType = getStandard(); whichType = getStandard();
@@ -414,13 +704,95 @@ public class ZUGFeRDInvoiceImporter extends ZUGFeRDImporter {
&& ((!expectedStringTotalGross.equals(XMLTools.nDigitFormat(expectedGrandTotal, 2))) && ((!expectedStringTotalGross.equals(XMLTools.nDigitFormat(expectedGrandTotal, 2)))
&& (!ignoreCalculationErrors))) { && (!ignoreCalculationErrors))) {
throw new ParseException( 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; 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 * 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() { public void fromXML(String XML) {
ignoreCalculationErrors = true; try {
containsMeta = true;
setRawXML(XML.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
LOGGER.error(e.getMessage(), e);
}
} }
} }

View File

@@ -20,28 +20,57 @@
*/ */
package org.mustangproject.ZUGFeRD; package org.mustangproject.ZUGFeRD;
import org.apache.fop.apps.*; import java.io.BufferedOutputStream;
import org.apache.fop.configuration.Configuration; import java.io.ByteArrayInputStream;
import org.apache.fop.configuration.ConfigurationException; import java.io.ByteArrayOutputStream;
import org.apache.fop.configuration.DefaultConfigurationBuilder; import java.io.File;
import org.apache.xmlgraphics.util.MimeConstants; import java.io.FileInputStream;
import org.mustangproject.CII.CIIToUBL; import java.io.FileNotFoundException;
import org.mustangproject.ClasspathResolverURIAdapter; import java.io.FileOutputStream;
import org.xml.sax.SAXException; 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.sax.SAXResult;
import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource; import javax.xml.transform.stream.StreamSource;
import java.io.*; import org.apache.fop.apps.FOPException;
import java.net.URI; import org.apache.fop.apps.FOUserAgent;
import java.net.URISyntaxException; import org.apache.fop.apps.Fop;
import java.nio.charset.StandardCharsets; import org.apache.fop.apps.FopFactory;
import java.nio.file.Files; import org.apache.fop.apps.FopFactoryBuilder;
import java.nio.file.Paths; import org.apache.fop.apps.io.ResourceResolverFactory;
import java.util.logging.Level; import org.apache.fop.configuration.Configuration;
import java.util.logging.Logger; 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 { public class ZUGFeRDVisualizer {
@@ -53,7 +82,7 @@ public class ZUGFeRDVisualizer {
static final ClassLoader CLASS_LOADER = ZUGFeRDVisualizer.class.getClassLoader(); static final ClassLoader CLASS_LOADER = ZUGFeRDVisualizer.class.getClassLoader();
private static final String RESOURCE_PATH = ""; 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 // private static File createTempFileResult(final Transformer transformer, final
// StreamSource toTransform, // StreamSource toTransform,
// final String suffix) throws TransformerException, IOException { // final String suffix) throws TransformerException, IOException {
@@ -68,6 +97,7 @@ public class ZUGFeRDVisualizer {
private TransformerFactory mFactory = null; private TransformerFactory mFactory = null;
private Templates mXsltXRTemplate = null; private Templates mXsltXRTemplate = null;
private Templates mXsltUBLTemplate = null; private Templates mXsltUBLTemplate = null;
private Templates mXsltCIOTemplate = null;
private Templates mXsltHTMLTemplate = null; private Templates mXsltHTMLTemplate = null;
private Templates mXsltPDFTemplate = null; private Templates mXsltPDFTemplate = null;
private Templates mXsltZF1HTMLTemplate = null; private Templates mXsltZF1HTMLTemplate = null;
@@ -78,28 +108,60 @@ public class ZUGFeRDVisualizer {
mFactory.setURIResolver(new ClasspathResourceURIResolver()); 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) public String visualize(String xmlFilename, Language lang)
throws FileNotFoundException, TransformerException, UnsupportedEncodingException { throws FileNotFoundException, TransformerException, IOException, SAXException, ParserConfigurationException {
try { try {
if (mXsltXRTemplate == null) {
mXsltXRTemplate = mFactory.newTemplates(
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/cii-xr.xsl")));
}
if (mXsltPDFTemplate == null) { if (mXsltPDFTemplate == null) {
mXsltPDFTemplate = mFactory.newTemplates( mXsltPDFTemplate = mFactory.newTemplates(
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/xr-pdf.xsl"))); new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/xr-pdf.xsl")));
} }
if (mXsltHTMLTemplate == null) { if (mXsltHTMLTemplate == null) {
mXsltHTMLTemplate = mFactory.newTemplates(new StreamSource( mXsltHTMLTemplate = mFactory.newTemplates(new StreamSource(
CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/xrechnung-html." + lang.name().toLowerCase() + ".xsl"))); CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/xrechnung-html." + lang.name().toLowerCase() + ".xsl")));
} }
if (mXsltZF1HTMLTemplate == null) { if (mXsltZF1HTMLTemplate == null) {
mXsltZF1HTMLTemplate = mFactory.newTemplates(new StreamSource( mXsltZF1HTMLTemplate = mFactory.newTemplates(new StreamSource(
CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/ZUGFeRD_1p0_c1p0_s1p0.xslt"))); CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/ZUGFeRD_1p0_c1p0_s1p0.xslt")));
} }
} catch (TransformerConfigurationException ex) { } 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 { try {
fileContent = new String(Files.readAllBytes(Paths.get(xmlFilename)), StandardCharsets.UTF_8); fileContent = new String(Files.readAllBytes(Paths.get(xmlFilename)), StandardCharsets.UTF_8);
} catch (IOException e2) { } catch (IOException e2) {
LOG.log(Level.SEVERE, null, e2); LOGGER.error("Failed to read file content", e2);
} }
ByteArrayOutputStream iaos = new ByteArrayOutputStream(); ByteArrayOutputStream iaos = new ByteArrayOutputStream();
ByteArrayOutputStream baos = 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; 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); applyZF1XSLT(fis, baos);
} else if (thestandard == EStandard.facturx) {
} else if (fileContent.contains(zf2Signature)) {
//zf2 or fx //zf2 or fx
applyZF2XSLT(fis, iaos); applyZF2XSLT(fis, iaos);
doPostProcessing = true; doPostProcessing = true;
} else if (fileContent.contains(ublSignature)) { } else if (thestandard == EStandard.ubl) {
//zf2 or fx //zf2 or fx
applyUBL2XSLT(fis, iaos); applyUBL2XSLT(fis, iaos);
doPostProcessing = true; doPostProcessing = true;
} else if (thestandard == EStandard.ubl_creditnote) {
} else if (fileContent.contains(ublCreditNoteSignature)) {
//zf2 or fx //zf2 or fx
applyUBLCreditNote2XSLT(fis, iaos); applyUBLCreditNote2XSLT(fis, iaos);
doPostProcessing = true; 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) { if (doPostProcessing) {
// take the copy of the stream and re-write it to an InputStream // take the copy of the stream and re-write it to an InputStream
@@ -158,61 +224,52 @@ public class ZUGFeRDVisualizer {
// ByteArrayOutputStream // ByteArrayOutputStream
iaos.writeTo(out); iaos.writeTo(out);
} catch (IOException e) { } catch (IOException e) {
LOG.log(Level.SEVERE, null, e); LOGGER.error("Failed to write to stream", e);
} finally { } finally {
// close the PipedOutputStream here because we're done writing data // close the PipedOutputStream here because we're done writing data
// once this thread has completed its run // once this thread has completed its run
if (out != null) { StreamHelper.close(out);
// close the PipedOutputStream cleanly
try {
out.close();
} catch (IOException e) {
// TODO Auto-generated catch block
LOG.log(Level.SEVERE, null, e);
}
}
} }
} }
}).start(); }).start();
applyXSLTToHTML(in, baos); applyXSLTToHTML(in, baos);
} catch (IOException e1) { } 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) protected String toFOP(String xmlFilename)
throws FileNotFoundException, TransformerException, UnsupportedEncodingException { throws FileNotFoundException, TransformerException {
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); FileInputStream fis = new FileInputStream(xmlFilename);
String fileContent = ""; EStandard theStandard = findOutStandardFromRootNode(fis);
fis = new FileInputStream(xmlFilename);//rewind :-(
try { try {
fileContent = new String(Files.readAllBytes(Paths.get(xmlFilename)), StandardCharsets.UTF_8); if (mXsltPDFTemplate == null) {
} catch (IOException e2) { mXsltPDFTemplate = mFactory.newTemplates(
LOG.log(Level.SEVERE, null, e2); new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/xr-pdf.xsl")));
}
} catch (TransformerConfigurationException ex) {
LOGGER.error("Failed to init XSLT templates", ex);
} }
ByteArrayOutputStream iaos = new ByteArrayOutputStream(); ByteArrayOutputStream iaos = new ByteArrayOutputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
//zf2 or fx //zf2 or fx
applyZF2XSLT(fis, iaos); 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(); PipedInputStream in = new PipedInputStream();
PipedOutputStream out; PipedOutputStream out;
@@ -227,68 +284,38 @@ public class ZUGFeRDVisualizer {
// ByteArrayOutputStream // ByteArrayOutputStream
iaos.writeTo(out); iaos.writeTo(out);
} catch (IOException e) { } catch (IOException e) {
LOG.log(Level.SEVERE, null, e); LOGGER.error("Failed to write to stream", e);
} finally { } finally {
// close the PipedOutputStream here because we're done writing data // close the PipedOutputStream here because we're done writing data
// once this thread has completed its run // once this thread has completed its run
if (out != null) { StreamHelper.close(out);
// close the PipedOutputStream cleanly
try {
out.close();
} catch (IOException e) {
// TODO Auto-generated catch block
LOG.log(Level.SEVERE, null, e);
}
}
} }
} }
}).start(); }).start();
applyXSLTToPDF(in, baos); applyXSLTToPDF(in, baos);
} catch (IOException e1) { } 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) { public void toPDF(String xmlFilename, String pdfFilename) {
// the writing part // the writing part
CIIToUBL c2u = new CIIToUBL(); File XMLinputFile = new File(xmlFilename);
String sourceFilename = "factur-x.xml";
File CIIinputFile = new File(xmlFilename);
String expected = null;
String result = null; String result = null;
ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer();
/* remove file endings so that tests can also pass after checking /* remove file endings so that tests can also pass after checking
out from git with arbitrary options (which may include CSRF changes) out from git with arbitrary options (which may include CSRF changes)
*/ */
try { try {
result = zvi.toFOP(CIIinputFile.getAbsolutePath()); result = this.toFOP(XMLinputFile.getAbsolutePath());
} catch (FileNotFoundException e) { } catch (FileNotFoundException | TransformerException e) {
Logger.getLogger(ZUGFeRDVisualizer.class.getName()).log(Level.SEVERE, null, e); LOGGER.error("Failed to apply FOP", 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);
} }
/*
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(); DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder();
Configuration cfg = null; 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 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(); 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. // Step 2: Set up output stream.
// Note: Using BufferedOutputStream for performance reasons (helpful with FileOutputStreams). // Note: Using BufferedOutputStream for performance reasons (helpful with FileOutputStreams).
@@ -330,33 +362,39 @@ public class ZUGFeRDVisualizer {
// Step 6: Start XSLT transformation and FOP processing // Step 6: Start XSLT transformation and FOP processing
transformer.transform(src, res); transformer.transform(src, res);
//Files.write(Paths.get("C:\\Users\\jstaerk\\temp\\fop.pdf"), res.toString().getBytes(StandardCharsets.UTF_8)); } catch (FOPException | IOException | TransformerException e) {
LOGGER.error("Failed to create PDF", e);
} 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);
} }
} }
protected void applyZF2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream) protected void applyZF2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
throws TransformerException { throws TransformerException {
if (mXsltXRTemplate == null) {
mXsltXRTemplate = mFactory.newTemplates(
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/cii-xr.xsl")));
}
Transformer transformer = mXsltXRTemplate.newTransformer(); Transformer transformer = mXsltXRTemplate.newTransformer();
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream)); 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) protected void applyUBL2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
throws TransformerException { throws TransformerException {
if (mXsltUBLTemplate == null) { if (mXsltUBLTemplate == null) {
mXsltUBLTemplate = mFactory.newTemplates( mXsltUBLTemplate = mFactory.newTemplates(
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/ubl-invoice-xr.xsl"))); new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/ubl-invoice-xr.xsl")));
} }
Transformer transformer = mXsltUBLTemplate.newTransformer(); Transformer transformer = mXsltUBLTemplate.newTransformer();
@@ -364,10 +402,10 @@ public class ZUGFeRDVisualizer {
} }
protected void applyUBLCreditNote2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream) protected void applyUBLCreditNote2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
throws TransformerException { throws TransformerException {
if (mXsltUBLTemplate == null) { if (mXsltUBLTemplate == null) {
mXsltUBLTemplate = mFactory.newTemplates( mXsltUBLTemplate = mFactory.newTemplates(
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/ubl-creditnote-xr.xsl"))); new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/ubl-creditnote-xr.xsl")));
} }
Transformer transformer = mXsltUBLTemplate.newTransformer(); Transformer transformer = mXsltUBLTemplate.newTransformer();
@@ -375,21 +413,21 @@ public class ZUGFeRDVisualizer {
} }
protected void applyZF1XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream) protected void applyZF1XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
throws TransformerException { throws TransformerException {
Transformer transformer = mXsltZF1HTMLTemplate.newTransformer(); Transformer transformer = mXsltZF1HTMLTemplate.newTransformer();
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream)); transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
} }
protected void applyXSLTToHTML(final InputStream xmlFile, final OutputStream HTMLOutstream) protected void applyXSLTToHTML(final InputStream xmlFile, final OutputStream HTMLOutstream)
throws TransformerException { throws TransformerException {
Transformer transformer = mXsltHTMLTemplate.newTransformer(); Transformer transformer = mXsltHTMLTemplate.newTransformer();
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream)); transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
} }
protected void applyXSLTToPDF(final InputStream xmlFile, final OutputStream PDFOutstream) protected void applyXSLTToPDF(final InputStream xmlFile, final OutputStream PDFOutstream)
throws TransformerException { throws TransformerException {
Transformer transformer = mXsltPDFTemplate.newTransformer(); Transformer transformer = mXsltPDFTemplate.newTransformer();
transformer.transform(new StreamSource(xmlFile), new StreamResult(PDFOutstream)); transformer.transform(new StreamSource(xmlFile), new StreamResult(PDFOutstream));

View File

@@ -30,5 +30,5 @@ public class TaxCategoryCodeTypeConstants {
public static final String UNTAXEDSERVICE = "O"; public static final String UNTAXEDSERVICE = "O";
public static final String INTRACOMMUNITY = "K"; 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());
} }

View File

@@ -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;
}
}

View 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();
}
}

Binary file not shown.

View File

@@ -25,7 +25,7 @@
<renderer mime="application/pdf"> <renderer mime="application/pdf">
<fonts><!-- https://xmlgraphics.apache.org/fop/1.1/fonts.html auto-embed --> <fonts><!-- https://xmlgraphics.apache.org/fop/1.1/fonts.html auto-embed -->
<auto-detect/> <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="SourceSerifPro" style="normal" weight="normal" />
<font-triplet name="Times-Bold" style="normal" weight="normal" /> <font-triplet name="Times-Bold" style="normal" weight="normal" />
</font> </font>

File diff suppressed because it is too large Load Diff

View File

@@ -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_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_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="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="uebersichtKaeufer" id="BG-7">Informationen zum Käufer</entry>
<entry key="uebersichtVerkaeufer" id="BG-4">Informationen zum Verkäufer</entry> <entry key="uebersichtVerkaeufer" id="BG-4">Informationen zum Verkäufer</entry>
<entry key="uebersichtRechnungsInfo" id="invoice-data">Rechnungsdaten</entry> <entry key="uebersichtRechnungsInfo" id="invoice-data">Rechnungsdaten</entry>
@@ -214,7 +214,7 @@
<entry key="uebersichtZahlungLastschrift" id="BG-19">Lastschrift</entry> <entry key="uebersichtZahlungLastschrift" id="BG-19">Lastschrift</entry>
<entry key="uebersichtZahlungUeberweisung" id="BG-17">Überweisung</entry> <entry key="uebersichtZahlungUeberweisung" id="BG-17">Überweisung</entry>
<entry key="uebersichtBemerkungen" id="BG-1">Bemerkungen zur Rechnung</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="detailsPositionAbrechnungszeitraum" id="BG-26">Abrechnungszeitraum</entry>
<entry key="detailsPositionPreiseinzelheiten" id="BG-29">Preiseinzelheiten</entry> <entry key="detailsPositionPreiseinzelheiten" id="BG-29">Preiseinzelheiten</entry>
<entry key="detailsPositionNachlaesse" id="BG-27">Nachlässe auf Ebene der Rechnungsposition</entry> <entry key="detailsPositionNachlaesse" id="BG-27">Nachlässe auf Ebene der Rechnungsposition</entry>

View File

@@ -807,10 +807,11 @@
</xsl:template> </xsl:template>
<xsl:template name="zusaetzeVertrag"> <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="identifier" select="'zusaetzeVertrag'"/>
<xsl:with-param name="content"> <xsl:with-param name="content">
<xsl:call-template name="list"> <xsl:call-template name="list">
<xsl:with-param name="layout" select="'einspaltig'"/>
<xsl:with-param name="content"> <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:Tender_or_lot_reference"/>
<xsl:apply-templates mode="list-entry" select="xr:Receiving_advice_reference"/> <xsl:apply-templates mode="list-entry" select="xr:Receiving_advice_reference"/>

View File

@@ -22,6 +22,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
@@ -65,7 +66,7 @@ public class BackwardCompatibilityTest extends TestCase implements IExportableTr
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
ze.export(baos); ze.export(baos);
ze.close(); ze.close();
String pdfContent = baos.toString("UTF-8"); String pdfContent = baos.toString(StandardCharsets.UTF_8);
assertFalse(pdfContent.indexOf("(via mustangproject.org") == -1); assertFalse(pdfContent.indexOf("(via mustangproject.org") == -1);
// check for pdf-a schema extension // check for pdf-a schema extension
// assertFalse(pdfContent.indexOf("<zf:ConformanceLevel>EN 16931</zf:ConformanceLevel>") == -1); // 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(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
ze.export(baos); ze.export(baos);
ze.close(); ze.close();
String pdfContent = baos.toString("UTF-8"); String pdfContent = baos.toString(StandardCharsets.UTF_8);
assertFalse(pdfContent.indexOf("(via mustangproject.org") == -1); assertFalse(pdfContent.indexOf("(via mustangproject.org") == -1);
// check for pdf-a schema extension // check for pdf-a schema extension
// assertFalse(pdfContent.indexOf("<zf:ConformanceLevel>EN 16931</zf:ConformanceLevel>") == -1); // 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(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
ze.export(baos); ze.export(baos);
ze.close(); ze.close();
String pdfContent = baos.toString("UTF-8"); String pdfContent = baos.toString(StandardCharsets.UTF_8);
assertFalse(pdfContent.indexOf("(via mustangproject.org") == -1); assertFalse(pdfContent.indexOf("(via mustangproject.org") == -1);
// check for pdf-a schema extension // check for pdf-a schema extension
// assertFalse(pdfContent.indexOf("<zf:ConformanceLevel>EN 16931</zf:ConformanceLevel>") == -1); // assertFalse(pdfContent.indexOf("<zf:ConformanceLevel>EN 16931</zf:ConformanceLevel>") == -1);
@@ -316,7 +317,7 @@ public class BackwardCompatibilityTest extends TestCase implements IExportableTr
// //
public IZUGFeRDTradeSettlementPayment[] getTradeSettlementPayment() { public IZUGFeRDTradeSettlementPayment[] getTradeSettlementPayment() {
Payment P = new Payment(); Payment P = new Payment();
IZUGFeRDTradeSettlementPayment[] allP = new Payment[1]; IZUGFeRDTradeSettlementPayment[] allP = new Payment[1];
allP[0] = P; allP[0] = P;

View File

@@ -22,16 +22,7 @@ import junit.framework.Test;
import junit.framework.TestSuite; import junit.framework.TestSuite;
import junit.framework.TestCase; 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.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import org.mustangproject.XMLTools; import org.mustangproject.XMLTools;
public class BaseTest extends TestCase { 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.00", XMLTools.nDigitFormat(new BigDecimal("12"),2));
assertEquals("12", XMLTools.nDigitFormat(new BigDecimal("12"),0)); assertEquals("12", XMLTools.nDigitFormat(new BigDecimal("12"),0));
assertEquals("20000123.342", XMLTools.nDigitFormat(new BigDecimal("20000123.3419"),3)); 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));
} }
} }

View File

@@ -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' }));
}
}

View File

@@ -6,24 +6,25 @@ import static org.junit.Assert.assertEquals;
import org.junit.Test; import org.junit.Test;
import org.mustangproject.*; import org.mustangproject.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.logging.Level;
import java.util.logging.Logger;
/*** /***
* tests the linecalculator and transactioncalculator classes * tests the linecalculator and transactioncalculator classes
* *
*/ */
public class CalculationTest { public class CalculationTest {
private static final Logger LOGGER = LoggerFactory.getLogger(CalculationTest.class);
@Test @Test
public void testLineCalculator_simpleAmounts_resultInValidVATAmount() { public void testLineCalculator_simpleAmounts_resultInValidVATAmount() {
final IZUGFeRDExportableProduct product = new IZUGFeRDExportableProductImpl().setVatPercent(valueOf(16)); final IZUGFeRDExportableProduct product = new IZUGFeRDExportableProductImpl().setVatPercent(valueOf(16));
final IZUGFeRDExportableItem currentItem = new IZUGFeRDExportableItemImpl().setPrice(valueOf(100)) final IZUGFeRDExportableItem currentItem = new IZUGFeRDExportableItemImpl().setPrice(valueOf(100))
.setQuantity(TEN) .setQuantity(TEN)
.setProduct(product); .setProduct(product);
final LineCalculator calculator = new LineCalculator(currentItem); final LineCalculator calculator = new LineCalculator(currentItem);
@@ -40,9 +41,9 @@ public class CalculationTest {
final IZUGFeRDAllowanceCharge allowance = new IZUGFeRDAllowanceChargeImpl().setTotalAmount(valueOf(14.8730)); final IZUGFeRDAllowanceCharge allowance = new IZUGFeRDAllowanceChargeImpl().setTotalAmount(valueOf(14.8730));
final IZUGFeRDExportableItem currentItem = new IZUGFeRDExportableItemImpl().setPrice(valueOf(148.73)) final IZUGFeRDExportableItem currentItem = new IZUGFeRDExportableItemImpl().setPrice(valueOf(148.73))
.setQuantity(valueOf(12)) .setQuantity(valueOf(12))
.setItemAllowances(new IZUGFeRDAllowanceCharge[]{allowance}) .setItemAllowances(new IZUGFeRDAllowanceCharge[]{allowance})
.setProduct(product); .setProduct(product);
final LineCalculator calculator = new LineCalculator(currentItem); final LineCalculator calculator = new LineCalculator(currentItem);
@@ -59,10 +60,10 @@ public class CalculationTest {
// 20 % charge // 20 % charge
final IZUGFeRDAllowanceCharge charge = new IZUGFeRDAllowanceChargeImpl().setTotalAmount(valueOf(29.746)); final IZUGFeRDAllowanceCharge charge = new IZUGFeRDAllowanceChargeImpl().setTotalAmount(valueOf(29.746));
final IZUGFeRDExportableItem currentItem = new IZUGFeRDExportableItemImpl().setPrice(valueOf(148.73)) final IZUGFeRDExportableItem currentItem = new IZUGFeRDExportableItemImpl().setPrice(valueOf(148.73))
.setQuantity(valueOf(12)) .setQuantity(valueOf(12))
.setItemAllowances(new IZUGFeRDAllowanceCharge[]{allowance}) .setItemAllowances(new IZUGFeRDAllowanceCharge[]{allowance})
.setItemCharges(new IZUGFeRDAllowanceCharge[]{charge}) .setItemCharges(new IZUGFeRDAllowanceCharge[]{charge})
.setProduct(product); .setProduct(product);
final LineCalculator calculator = new LineCalculator(currentItem); final LineCalculator calculator = new LineCalculator(currentItem);
@@ -92,111 +93,112 @@ public class CalculationTest {
invoice.setDeliveryDate(sqlDate.parse("2020-12-31")); invoice.setDeliveryDate(sqlDate.parse("2020-12-31"));
invoice.setDueDate(sqlDate.parse("2021-01-15")); invoice.setDueDate(sqlDate.parse("2021-01-15"));
} catch (Exception e) { } catch (Exception e) {
Logger.getLogger(CalculationTest.class.getName()).log(Level.SEVERE, null, e); LOGGER.error("Failed to set dates", e);
} }
/* trade party (sender) */ /* trade party (sender) */
TradeParty sender = new TradeParty("Maier GmbH", "Musterweg 5", "11111", "Testung", "DE"); TradeParty sender = new TradeParty("Maier GmbH", "Musterweg 5", "11111", "Testung", "DE");
sender.addVATID("DE2222222222"); sender.addVATID("DE2222222222");
invoice.setSender(sender); invoice.setSender(sender);
/* trade party (recipient) */
/* trade party (recipient) */
TradeParty recipient = new TradeParty("Teston GmbH" + " " + "Zentrale" + " " + "", "Testweg 5", "11111", "Testung", "DE"); TradeParty recipient = new TradeParty("Teston GmbH" + " " + "Zentrale" + " " + "", "Testweg 5", "11111", "Testung", "DE");
recipient.setID("111111"); recipient.setID("111111");
recipient.addVATID("DE111111111"); recipient.addVATID("DE111111111");
invoice.setRecipient(recipient); invoice.setRecipient(recipient);
/* item */
/* item */
Product product; Product product;
Item item; Item item;
BigDecimal item_increase = BigDecimal.ZERO;
BigDecimal item_discount = BigDecimal.ZERO;
product = new Product("AAA", "", "H84", sales_tax_percent1).setSellerAssignedID("1AAA"); product = new Product("AAA", "", "H84", sales_tax_percent1).setSellerAssignedID("1AAA");
item = new Item(product, new BigDecimal("4.750"), new BigDecimal(5.00)); 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) { 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) { 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); 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"); product = new Product("BBB", "", "H84", sales_tax_percent1).setSellerAssignedID("2BBB");
item = new Item(product, new BigDecimal("5.750"), new BigDecimal(4.00)); item = new Item(product, new BigDecimal("5.750"), new BigDecimal(4.00));
item_discount = BigDecimal.ZERO;
if (item_increase.compareTo(BigDecimal.ZERO) > 0) { 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) { 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); invoice.addItem(item);
product = new Product("CCC", "", "H84", sales_tax_percent1).setSellerAssignedID("3CCC"); product = new Product("CCC", "", "H84", sales_tax_percent1).setSellerAssignedID("3CCC");
item = new Item(product, new BigDecimal("6.750"), new BigDecimal(3.00)); 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) { 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) { 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); invoice.addItem(item);
product = new Product("DDD", "", "H84", sales_tax_percent1).setSellerAssignedID("4DDD"); product = new Product("DDD", "", "H84", sales_tax_percent1).setSellerAssignedID("4DDD");
item = new Item(product, new BigDecimal("7.750"), new BigDecimal(2.00)); item = new Item(product, new BigDecimal("7.750"), new BigDecimal(2.00));
item_discount = BigDecimal.ZERO;
if (item_increase.compareTo(BigDecimal.ZERO) > 0) { 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) { 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); invoice.addItem(item);
product = new Product("EEE", "", "H84", sales_tax_percent1).setSellerAssignedID("5EEE"); product = new Product("EEE", "", "H84", sales_tax_percent1).setSellerAssignedID("5EEE");
item = new Item(product, new BigDecimal("8.750"), new BigDecimal(1.00)); item = new Item(product, new BigDecimal("8.750"), new BigDecimal(1.00));
item_discount = BigDecimal.ZERO;
if (item_increase.compareTo(BigDecimal.ZERO) > 0) { 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) { 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); invoice.addItem(item);
if (total_increase_percent.compareTo(BigDecimal.ZERO) > 0) { // reset values for additional charge and discount used on invoice level
invoice.addCharge(new Charge().setPercent(total_increase_percent).setTaxPercent(sales_tax_percent1).setCategoryCode("ZZZ").setReason("Zuschläge")); 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) { 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); 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());
}
} }

View File

@@ -45,7 +45,7 @@ import java.util.GregorianCalendar;
import static org.xmlunit.assertj.XmlAssert.assertThat; import static org.xmlunit.assertj.XmlAssert.assertThat;
@FixMethodOrder(MethodSorters.NAME_ASCENDING) @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_PDF = "./target/testout-DX.pdf";
final String TARGET_XML = "./target/testout-DX.xml"; final String TARGET_XML = "./target/testout-DX.xml";

View File

@@ -21,12 +21,9 @@
*/ */
package org.mustangproject.ZUGFeRD; package org.mustangproject.ZUGFeRD;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.assertj.core.util.Lists;
import org.junit.FixMethodOrder; import org.junit.FixMethodOrder;
import org.junit.runners.MethodSorters; import org.junit.runners.MethodSorters;
import org.mustangproject.*; import org.mustangproject.*;
@@ -39,7 +36,7 @@ public class DeSerializationTest extends TestCase {
public void testJackson() throws JsonProcessingException { public void testJackson() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper(); 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); String jsonArray = mapper.writeValueAsString(i);
// [{"stringValue":"a","intValue":1,"booleanValue":true}, // [{"stringValue":"a","intValue":1,"booleanValue":true},
@@ -48,6 +45,90 @@ public class DeSerializationTest extends TestCase {
Invoice fromJSON = mapper.readValue(jsonArray, Invoice.class); Invoice fromJSON = mapper.readValue(jsonArray, Invoice.class);
assertEquals(fromJSON.getNumber(), i.getNumber()); assertEquals(fromJSON.getNumber(), i.getNumber());
assertEquals(fromJSON.getZFItems().length, i.getZFItems().length); 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);
}
} }

View File

@@ -23,6 +23,7 @@ import java.math.BigDecimal;
public class IZUGFeRDAllowanceChargeImpl implements IZUGFeRDAllowanceCharge { public class IZUGFeRDAllowanceChargeImpl implements IZUGFeRDAllowanceCharge {
private BigDecimal totalAmount; private BigDecimal totalAmount;
private String reason; private String reason;
private String reasonCode;
private BigDecimal taxPercent; private BigDecimal taxPercent;
boolean isCharge=true; boolean isCharge=true;
@@ -36,6 +37,11 @@ public class IZUGFeRDAllowanceChargeImpl implements IZUGFeRDAllowanceCharge {
return reason; return reason;
} }
@Override
public String getReasonCode() {
return reasonCode;
}
@Override @Override
public BigDecimal getTaxPercent() { public BigDecimal getTaxPercent() {
return taxPercent; return taxPercent;
@@ -61,6 +67,11 @@ public class IZUGFeRDAllowanceChargeImpl implements IZUGFeRDAllowanceCharge {
return this; return this;
} }
public IZUGFeRDAllowanceChargeImpl setReasonCode(String reasonCode) {
this.reasonCode = reasonCode;
return this;
}
public IZUGFeRDAllowanceChargeImpl setTaxPercent(BigDecimal taxPercent) { public IZUGFeRDAllowanceChargeImpl setTaxPercent(BigDecimal taxPercent) {
this.taxPercent = taxPercent; this.taxPercent = taxPercent;
return this; return this;

View File

@@ -256,13 +256,13 @@ public class MustangReaderWriterCustomXMLTest extends TestCase {
"</ram:ApplicableHeaderTradeSettlement>\n" + "</ram:ApplicableHeaderTradeSettlement>\n" +
"</rsm:SupplyChainTradeTransaction>\n" + "</rsm:SupplyChainTradeTransaction>\n" +
"</rsm:CrossIndustryInvoice>"; "</rsm:CrossIndustryInvoice>";
zea1.setXML(ownZUGFeRDXML.getBytes("UTF-8")); zea1.setXML(ownZUGFeRDXML.getBytes(StandardCharsets.UTF_8));
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
zea1.disableAutoClose(true); zea1.disableAutoClose(true);
zea1.export(TARGET_PDF); zea1.export(TARGET_PDF);
zea1.export(baos); zea1.export(baos);
zea1.close(); 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("(via mustangproject.org") == -1);
assertFalse(pdfContent.indexOf("<fx:ConformanceLevel>EN 16931</fx:ConformanceLevel>") == -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:Name>Heiße Luft pro Liter</ram:Name>\n" + "<ram:Description/>\n"
+ "</ram:SpecifiedTradeProduct>\n" + "</ram:IncludedSupplyChainTradeLineItem>\n" + "</ram:SpecifiedTradeProduct>\n" + "</ram:IncludedSupplyChainTradeLineItem>\n"
+ "</rsm:SpecifiedSupplyChainTradeTransaction>\n" + "</rsm:CrossIndustryDocument>"; + "</rsm:SpecifiedSupplyChainTradeTransaction>\n" + "</rsm:CrossIndustryDocument>";
zea1.setXML(ownZUGFeRDXML.getBytes("UTF-8")); zea1.setXML(ownZUGFeRDXML.getBytes(StandardCharsets.UTF_8));
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
zea1.disableAutoClose(true); zea1.disableAutoClose(true);
zea1.export(TARGET_PDF); zea1.export(TARGET_PDF);
zea1.export(baos); zea1.export(baos);
zea1.close(); 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("(via mustangproject.org") == -1);
assertFalse(pdfContent.indexOf("<zf:ConformanceLevel>BASIC</zf:ConformanceLevel>") == -1); assertFalse(pdfContent.indexOf("<zf:ConformanceLevel>BASIC</zf:ConformanceLevel>") == -1);

View File

@@ -21,6 +21,7 @@ package org.mustangproject.ZUGFeRD;
import junit.framework.Test; import junit.framework.Test;
import junit.framework.TestSuite; import junit.framework.TestSuite;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.common.PDMetadata; import org.apache.pdfbox.pdmodel.common.PDMetadata;
import org.apache.xmpbox.XMPMetadata; import org.apache.xmpbox.XMPMetadata;
@@ -42,7 +43,7 @@ import java.util.GregorianCalendar;
@FixMethodOrder(MethodSorters.NAME_ASCENDING) @FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class MustangReaderWriterEdgeTest extends MustangReaderTestCase { 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 // Not testing extended profile here, lineThree not possible
@Override @Override
public String getAdditionalAddressExtension() { public String getAdditionalAddressExtension() {
@@ -252,7 +253,7 @@ public class MustangReaderWriterEdgeTest extends MustangReaderTestCase {
setupPdfUnderTest(SOURCE_PDF, TARGET_PDF); setupPdfUnderTest(SOURCE_PDF, TARGET_PDF);
// now check the contents // 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(); PDMetadata meta = doc.getDocumentCatalog().getMetadata();
assertNotNull("The pdf must contain XMPMetadata", meta); assertNotNull("The pdf must contain XMPMetadata", meta);
@@ -262,7 +263,7 @@ public class MustangReaderWriterEdgeTest extends MustangReaderTestCase {
DomXmpParser xmpParser = new DomXmpParser(); DomXmpParser xmpParser = new DomXmpParser();
XMPMetadata xmp = xmpParser.parse(xmpBytes); XMPMetadata xmp = xmpParser.parse(xmpBytes);
PDFAIdentificationSchema pdfai = xmp.getPDFIdentificationSchema(); PDFAIdentificationSchema pdfai = xmp.getPDFAIdentificationSchema();
assertNotNull("The pdf must contain a PDFIdentificationSchema", pdfai); assertNotNull("The pdf must contain a PDFIdentificationSchema", pdfai);
assertTrue("The PDF/A conformance must be A, B or U", assertTrue("The PDF/A conformance must be A, B or U",

View File

@@ -21,6 +21,7 @@ package org.mustangproject.ZUGFeRD;
import junit.framework.Test; import junit.framework.Test;
import junit.framework.TestSuite; import junit.framework.TestSuite;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.common.PDMetadata; import org.apache.pdfbox.pdmodel.common.PDMetadata;
import org.apache.pdfbox.pdmodel.encryption.InvalidPasswordException; import org.apache.pdfbox.pdmodel.encryption.InvalidPasswordException;
@@ -38,6 +39,7 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.*; import java.util.*;
@@ -212,8 +214,10 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
public void testForeignImport() { public void testForeignImport() {
InputStream inputStream = this.getClass().getResourceAsStream("/zugferd_invoice.pdf"); 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 // Reading ZUGFeRD
String amount = zi.getAmount(); String amount = zi.getAmount();
@@ -328,13 +332,13 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
} }
private void checkPdfA3B(File tempFile) throws IOException, InvalidPasswordException { 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(); PDMetadata metadata = doc.getDocumentCatalog().getMetadata();
InputStream exportXMPMetadata = metadata.exportXMPMetadata(); InputStream exportXMPMetadata = metadata.exportXMPMetadata();
byte[] xmpBytes = new byte[exportXMPMetadata.available()]; byte[] xmpBytes = new byte[exportXMPMetadata.available()];
exportXMPMetadata.read(xmpBytes); exportXMPMetadata.read(xmpBytes);
final XMPMetadata xmp = new DomXmpParser().parse(xmpBytes); final XMPMetadata xmp = new DomXmpParser().parse(xmpBytes);
PDFAIdentificationSchema pdfaid = xmp.getPDFIdentificationSchema(); PDFAIdentificationSchema pdfaid = xmp.getPDFAIdentificationSchema();
assertEquals(pdfaid.getPart().intValue(), 3); assertEquals(pdfaid.getPart().intValue(), 3);
assertEquals(pdfaid.getConformance(), "U"); assertEquals(pdfaid.getConformance(), "U");
} catch (XmpParsingException e) { } catch (XmpParsingException e) {
@@ -367,7 +371,7 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
ze.export(baos); ze.export(baos);
ze.close(); ze.close();
String pdfContent = baos.toString("UTF-8"); String pdfContent = baos.toString(StandardCharsets.UTF_8);
assertFalse(pdfContent.indexOf("(via mustangproject.org") == -1); assertFalse(pdfContent.indexOf("(via mustangproject.org") == -1);
// check for pdf-a schema extension // check for pdf-a schema extension
// assertFalse(pdfContent.indexOf("<zf:ConformanceLevel>EN 16931</zf:ConformanceLevel>") == -1); // assertFalse(pdfContent.indexOf("<zf:ConformanceLevel>EN 16931</zf:ConformanceLevel>") == -1);
@@ -411,7 +415,7 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
ze.export(baos); ze.export(baos);
ze.close(); ze.close();
String pdfContent = baos.toString("UTF-8"); String pdfContent = baos.toString(StandardCharsets.UTF_8);
assertFalse(pdfContent.indexOf(DocumentContextParameterTypeConstants.BASIC) >= 0); assertFalse(pdfContent.indexOf(DocumentContextParameterTypeConstants.BASIC) >= 0);
assertFalse(pdfContent.indexOf(DocumentContextParameterTypeConstants.EXTENDED) >= 0); assertFalse(pdfContent.indexOf(DocumentContextParameterTypeConstants.EXTENDED) >= 0);
assertTrue(pdfContent.indexOf(DocumentContextParameterTypeConstants.COMFORT) >= 0); assertTrue(pdfContent.indexOf(DocumentContextParameterTypeConstants.COMFORT) >= 0);
@@ -447,7 +451,7 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
ze.export(baos); ze.export(baos);
ze.close(); ze.close();
String pdfContent = baos.toString("UTF-8"); String pdfContent = baos.toString(StandardCharsets.UTF_8);
assertFalse(pdfContent.indexOf("(via mustangproject.org") == -1); assertFalse(pdfContent.indexOf("(via mustangproject.org") == -1);
// check for pdf-a schema extension // check for pdf-a schema extension
assertFalse(pdfContent.indexOf("<fx:ConformanceLevel>EN 16931</fx:ConformanceLevel>") == -1); assertFalse(pdfContent.indexOf("<fx:ConformanceLevel>EN 16931</fx:ConformanceLevel>") == -1);

View File

@@ -28,7 +28,6 @@ import org.junit.runners.MethodSorters;
import org.mustangproject.*; import org.mustangproject.*;
import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathExpressionException;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.math.BigDecimal; import java.math.BigDecimal;
@@ -42,7 +41,7 @@ import java.util.Date;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
@FixMethodOrder(MethodSorters.NAME_ASCENDING) @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 = "./target/testout-OX.pdf";
final String TARGET_PDF_EDGE = "./target/testout-OX-edge.pdf"; final String TARGET_PDF_EDGE = "./target/testout-OX-edge.pdf";
final String TARGET_XML = "./target/testout-OX.xml"; final String TARGET_XML = "./target/testout-OX.xml";

View File

@@ -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)));
}
}

View File

@@ -23,13 +23,10 @@ package org.mustangproject.ZUGFeRD;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.mustangproject.*; import org.mustangproject.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.GregorianCalendar;
/*** /***
* This is a test to confirm the minimum steps to implement a interface are still sufficient * 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 { 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() { public void testMinimumCreditNote() {
String ownNumber = "NUMFACTURE"; String ownNumber = "NUMFACTURE";
@@ -63,14 +61,14 @@ public class ProfilesMinimumBasicWLTest extends TestCase {
Invoice i = new Invoice() Invoice i = new Invoice()
.setIssueDate(new Date()) .setIssueDate(new Date())
.setSender( .setSender(
new TradeParty().setName(ownOrgName).setCountry("FR") new TradeParty().setName(ownOrgName).setCountry("FR").setVATID("FR555444333222111")
.addBankDetails(new BankDetails(ownIBAN, ownBIC))) .addBankDetails(new BankDetails(ownIBAN, ownBIC)))
.setRecipient(recipient) .setRecipient(recipient)
.setNumber(ownNumber) .setNumber(ownNumber)
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), new BigDecimal(123), new BigDecimal(1))) .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), new BigDecimal(123), new BigDecimal(1)))
.setCreditNote(); .setCreditNote();
ze.setTransaction(i); ze.setTransaction(i);
ze.export(TARGET_PDF_FX_MINIMUM); ze.export(TARGET_PDF_FX_MINIMUM_CN);
// check for pdf-a schema extension // check for pdf-a schema extension
// assertFalse(pdfContent.indexOf("<zf:ConformanceLevel>EN 16931</zf:ConformanceLevel>") == -1); // 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) // 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 // Reading ZUGFeRD
assertEquals("146.37",zi.getAmount()); assertEquals("146.37",zi.getAmount());
@@ -118,13 +116,13 @@ public class ProfilesMinimumBasicWLTest extends TestCase {
.setIssueDate(new Date()) .setIssueDate(new Date())
.setDueDate(new Date()) .setDueDate(new Date())
.setSender( .setSender(
new TradeParty().setName(ownOrgName).setCountry("FR") new TradeParty().setName(ownOrgName).setCountry("FR").setVATID("FR555444333222111")
.addBankDetails(new BankDetails(ownIBAN))) .addBankDetails(new BankDetails(ownIBAN)))
.setRecipient(recipient) .setRecipient(recipient)
.setNumber(ownNumber).setTotalPrepaidAmount(new BigDecimal("1")) .setNumber(ownNumber).setTotalPrepaidAmount(new BigDecimal("1"))
.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), new BigDecimal(123), new BigDecimal(1))); .addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), new BigDecimal(123), new BigDecimal(1)));
ze.setTransaction(i); ze.setTransaction(i);
ze.export(TARGET_PDF_FX_MINIMUM); ze.export(TARGET_PDF_FX_MINIMUM_INV);
// check for pdf-a schema extension // check for pdf-a schema extension
// assertFalse(pdfContent.indexOf("<zf:ConformanceLevel>EN 16931</zf:ConformanceLevel>") == -1); // 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) // 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 // Reading ZUGFeRD
assertEquals("145.37",zi.getAmount()); assertEquals("146.37",zi.getAmount());
// assertEquals(zi.getBIC(), ownBIC); // assertEquals(zi.getBIC(), ownBIC);
// assertEquals(zi.getIBAN(), ownIBAN); // assertEquals(zi.getIBAN(), ownIBAN);
assertEquals(ownOrgName, zi.getHolder()); assertEquals(ownOrgName, zi.getHolder());

View File

@@ -1,6 +1,8 @@
package org.mustangproject.ZUGFeRD; package org.mustangproject.ZUGFeRD;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.junit.Ignore;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -9,6 +11,7 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@Ignore
public class ResourceCase extends TestCase { public class ResourceCase extends TestCase {
private static final Logger LOGGER = LoggerFactory.getLogger(ResourceCase.class.getCanonicalName()); // log output is private static final Logger LOGGER = LoggerFactory.getLogger(ResourceCase.class.getCanonicalName()); // log output is

View File

@@ -22,15 +22,16 @@ import java.net.URISyntaxException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; 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 * This test utility class is providing usability functions to test file resources
*/ */
public class ResourceUtilities { 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 * 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 = ResourceUtilities.class.getClassLoader().getResource(relativeFilePath).toURI();
uri = new URI(toExternalForm(uri)); uri = new URI(toExternalForm(uri));
} catch (URISyntaxException ex) { } catch (URISyntaxException ex) {
LOG.log(Level.SEVERE, null, ex); LOGGER.error("Failed to parse URI", ex);
} }
if (uri == null) { if (uri == null) {
throw new FileNotFoundException("Could not find the file '" + relativeFilePath + "'!"); throw new FileNotFoundException("Could not find the file '" + relativeFilePath + "'!");
@@ -129,7 +130,7 @@ public class ResourceUtilities {
try { try {
filepath = ResourceUtilities.class.getClassLoader().getResource("").toURI().getPath() + relativeFilePath; filepath = ResourceUtilities.class.getClassLoader().getResource("").toURI().getPath() + relativeFilePath;
} catch (URISyntaxException ex) { } catch (URISyntaxException ex) {
LOG.log(Level.SEVERE, null, ex); LOGGER.error("Failed to parse URI", ex);
} }
return new File(filepath); return new File(filepath);
} }
@@ -142,7 +143,7 @@ public class ResourceUtilities {
try { try {
testFolder = ResourceUtilities.class.getClassLoader().getResource("").toURI().getPath(); testFolder = ResourceUtilities.class.getClassLoader().getResource("").toURI().getPath();
} catch (URISyntaxException ex) { } catch (URISyntaxException ex) {
LOG.log(Level.SEVERE, null, ex); LOGGER.error("Failed to parse URI", ex);
} }
return testFolder; return testFolder;
} }
@@ -206,7 +207,7 @@ public class ResourceUtilities {
try { try {
ret = new URI(sb.toString()).toASCIIString(); ret = new URI(sb.toString()).toASCIIString();
} catch (URISyntaxException ex) { } catch (URISyntaxException ex) {
LOG.log(Level.SEVERE, null, ex); LOGGER.error("Failed to parse URI", ex);
} }
return ret; return ret;
} }

View File

@@ -90,7 +90,7 @@ public class UBLTest extends ResourceCase {
final ByteArrayOutputStream baos=new ByteArrayOutputStream(); final ByteArrayOutputStream baos=new ByteArrayOutputStream();
oe.export(baos); oe.export(baos);
final String theXML = baos.toString("UTF-8"); final String theXML = baos.toString(StandardCharsets.UTF_8);
assertTrue(theXML.contains("<DespatchAdvice")); assertTrue(theXML.contains("<DespatchAdvice"));
Files.write(Paths.get(TARGET_XML), theXML.getBytes(StandardCharsets.UTF_8)); Files.write(Paths.get(TARGET_XML), theXML.getBytes(StandardCharsets.UTF_8));
} catch (final IOException e) { } catch (final IOException e) {
@@ -123,33 +123,8 @@ public class UBLTest extends ResourceCase {
} }
public void testEdgeInvoiceImportUBL2() {
public void testInvoiceImportOtherUBL() { File UBLinputFile = getResourceAsFile("ubl/04.01a-INVOICE_ubl.xml");
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");
boolean hasExceptions = false; boolean hasExceptions = false;
ZUGFeRDInvoiceImporter zii = null; ZUGFeRDInvoiceImporter zii = null;
@@ -158,15 +133,12 @@ public class UBLTest extends ResourceCase {
zii = new ZUGFeRDInvoiceImporter(new FileInputStream(UBLinputFile)); zii = new ZUGFeRDInvoiceImporter(new FileInputStream(UBLinputFile));
invoice = zii.extractInvoice(); invoice = zii.extractInvoice();
} catch (XPathExpressionException | ParseException | FileNotFoundException e) { } catch (XPathExpressionException | ParseException | FileNotFoundException e) {
e.printStackTrace();
hasExceptions = true; hasExceptions = true;
} }
assertFalse(hasExceptions); 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