Elektronische Rechnungen schreiben und lesen

Lizenz

Mustangproject liegt unter der liberalen Apache Public License 2 vor und darf kostenlos in kommerziellen und nichtkommerziellen Produkten genutzt werden.

Quelltext

Das git repository des Mustangproject-Quelltext liegt auf Github.

Architektur

Sie können Mustang von der Kommandozeile aufrufen oder in Ihre Java-Software einbinden, entweder indem Sie (mit Maven oder Gradle) auf Mustang verweisen oder indem Sie die entsprechenden Dateien herunterladen.
Sie können PDF und XML zusammenfügen, trennen und/oder validieren. Auf der Kommandozeile müssen Sie um Factur-X zu schreiben immer eine PDF/A-1 Datei und eine XML-Datei angeben, mit der Bibliothek müssen Sie mindestens eine PDF/A-1-Datei angeben und können eigenes XML einbinden oder die Rechnungsklassen benutzen die die Interfaces benutzen, oder direkt per
interfaces kommunizieren. Factur-X lesen geht derzeit nur im “Bibliotheksmodus” wo Sie in ein
Rechnungsobjekt parsen können,
oder einfach nur per ZUGFeRDImporter Basisdaten auslesen.
Mit einem anderen Artefakt können Sie auch den Validierer in Ihre Software einbetten.

In der Kommandozeile gibt es auch kleinere Statistiken für Verzeichnisse mit Factur-X-Dateien und Sie können
versuchsweise XML von ZUGFeRD Version 1 auf 2 übersetzen, PDF/A-1 nach PDF/A-3 konvertieren, oder XML visualisieren, was in diesem Diagram fehlt:
Mustang Architekturdiagramm

Neues Projekt

Die Tastenkürzel dieses Beispiels geht von Eclipse als IDE aus.

Starten Sie Eclipse und erstellen ein neues Maven-Projekt, beispielsweise „MustangSample“ als “simple project”, Gruppen-ID “org.mustangproject”, Artifakt “mustangtest”.

Sie werden

Mit Maven

Öffnen Sie Ihre pom.xml und fügen Sie die Abhängigkeiten


<dependencies>
    <dependency>
        <groupId>org.mustangproject</groupId>
        <artifactId>library</artifactId>
        <version>2.10.0</version>
    </dependency>
</dependencies>

                        

hinzu.

Mit Gradle

Erstellen Sie ein neues Java-Gradle-Projekt. Mit der groovy DSL können Sie die Abhängigkeiten mavenCentral() wie folgt einfügen:


    implementation 'org.mustangproject:library:2.10.0'
    implementation 'org.xmlunit:xmlunit-assertj:2.8.2'
 

Die komplette build.gradle kann dann wie folgt aussehen


 plugins {
    id 'groovy'
    id 'java'
}

group 'org.example'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {

    implementation 'org.mustangproject:library:2.10.0'
    implementation 'org.xmlunit:xmlunit-assertj:2.8.2'

    compile 'org.codehaus.groovy:groovy-all:2.3.11'
    testCompile group: 'junit', name: 'junit', version: '4.12'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
}

test {
    useJUnitPlatform()
}
 

ZUGFeRD-Daten lesen

  1. Erstellen Sie eine neue Klasse im src/main/java-Verzeichnis und nennen Sie es beispielsweise Reader. Wählen Sie dabei die “Public static void main()”-Option.
  2. Erstellen Sie eine “run configuration”: klicken Sie in Run|Run configurations auf Java Application,und dort auf das Icon für eine neue Konfiguration.
  3. Geben Sie innerhalb der Main-Methode ZUGFeRDImporter zu=new ZUGFeRDImporter("MustangGnuaccountingBeispielRE-20190610_507.pdf"); ein und
  4. fügen Sie die imports durch drücken von STRG+SHIFT+O (Windows) oder CMD+SHIFT+O (Mac) hinzu.
  5. prüfen Sie gegebenenfalls mit if (zu.canParse()) ob es sich um eine geeignete ZUGFeRD-Datei handelt
  6. und zu.parse() um in der Folge die getter-Methoden wie getAmount() nutzen zu können
  7. verwenden Sie schließlich beispielsweise zu.getAmount(). Es gibt nur für einige Attribute getter aber weitere können einfach ergänzt werden. Welche Daten verfügbar sind ersehen Sie beispielsweise der ZUGFeRD-invoice.xml die in jede ZUGFeRD-Datei eingebettet ist.
  8. Vollständiges Beispiel
    ZUGFeRDImporter zu=new ZUGFeRDImporter("MustangGnuaccountingBeispielRE-20190610_507.pdf");
    System.out.println("Endbetrag: "+zu.getAmount());

Eigenes XML schreiben

Mustangprojects erstellt das nötige XML für Sie, alternativ können Sie auch eigenes XML angeben.

Erstellen Sie dazu eine Klasse XMLWriter mit einer statischen main-Methode. Benutzen Sie


 try {
            System.out.println("Converting to PDF/A-3u");
            ZUGFeRDExporterFromA1 ze=new ZUGFeRDExporterFromA1().load("./MustangGnuaccountingBeispielRE-20190610_507blanko.pdf");

            /*
             * Add .setZUGFeRDVersion and .setZUGFeRDConformanceLevel
             * in the next lines to set the ZUGFeRD version respective profile of the
             * XML you are inserting.
             */
            ;
            System.out.println("Attaching ZUGFeRD-Data");

            /*
             * Mustangproject checks if the input PDF/A file looks halfway valid and
             * the XML data contains <rsm:CrossIndustry which is the case for both
             *  ZF1 (CrossIndustryDocument) and ZF2 files (CrossIndustryInvoice).
             * Insert your (validated) XML here.
             */
            String ownZUGFeRDXML = "";
            ze.setXML(ownZUGFeRDXML.getBytes());
            System.out.println("Writing ZUGFeRD-PDF");
            ze.export("./Target.pdf");
        } catch (IOException e) {
            e.printStackTrace();
        }


und importieren Sie die Klassen (STRG+SHIFT+O in Windows oder CMD+SHIFT+O auf dem Mac).Erstellen Sie eine Run-Configuration durch Klick auf Run|Run configurations, dann auf Java Application und dann Klick auf das Icon für “neu”.

Um das eigene EN16931 XML zu schreiben sei erwähnt:

Schema

Die EN16931 Schemadatei für UN/CEFACT ist das “SCRDM uncoupled” der 2016 (“16B”) Version
erhältlich von https://www.unece.org/cefact/xml_schemas/ .
Die Schemadatei des ZUGFeRD “info packet” sollte äquivalent sein.

CIUS

Als ZUGFeRD/Factur-X Profil können Sie bspw. einfach EN16931 nutzen. Als CIUS kommt XRechnung soweit als möglich in Frage.

Beispiel

Das XML der Mustangprojects Beispieldatei kann online eingesehen werden.

Der Grund warum diese Datei im XRechnungs-Validierer nicht funktioniert ist interessant:

Validatoren

Sie können sich den offiziellen XRechnung-Validator wie folgt herunterladen, konfigurieren und verwenden:


mkdir xr
cd xr
wget https://github.com/itplr-kosit/validator/releases/download/v1.0.2/validationtool-1.0.2-full.zip
wget https://github.com/itplr-kosit/validator-configuration-xrechnung/releases/download/release-2018-12-19/validator-configuration-xrechnung_1.2.0_2018-12-19.zip
unzip validationtool-1.0.2-full.zip
unzip validator-configuration-xrechnung_1.2.0_2018-12-19.zip
mkdir test/instances

Ihre Testdateien können Sie dann in test/instances ablegen. Validierungsberichte in HTML in test/reports erhalten Sie dann mit


java -jar validationtool-1.0.2-standalone.jar -s scenarios.xml -o test/reports -h test/instances/*.xml

Das EN16931 ZF Profile erwartet genau urn:cen.eu:en16931:2017 im RAM:ID-Element während die XRechnung urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_1.2 erwartet.

Fügen Sie im Beispiel der RAM:ID #compliant#urn:xoev-de:kosit:standard:xrechnung_1.2 hinzu sollte es den XRechnungs-Validierer erfolgreich durchlaufen.

Die Ausgabe von


java -jar Mustang-CLI-2.10.0.jar --action validate --source ihreDatei.xml

sollte bei Ihren Dateien “valid” sein.

Es ist leider nicht ausreichend, lediglich mit den Schemadateien zu prüfen: Die Schematron-Datei prüft nämlich zusätzlich noch andere Elemente läßt aber auch einiges außer Acht was in der Schemadatei steht.

Schematron kann beispielsweise prüfen ob das Lieferdatum vor dem Rechnungsdatum liegt und ob die Gesamtsumme der Summe der Posten entspricht.

Validierer prüfen in der Regel sowohl gegen Schema als auch Schematron und Sie sollten einen benutzen oder diese Doppelprüfung auch vornehmen.

Rechenregeln

Wenn Sie wissen möchten wie viele Stellen hinter dem Komma Sie bei Beträgen angeben müssen oder wie richtig gerechnet wird: Das ist Teil des ersten Teils des Standards.

Codelisten

Wenn Sie Artikel in Fünferpacks verkaufen oder keine deutsche USt-ID angeben möchten, oder Firmen anders identifizieren oder eine Rechnung nach Malaysia schicken möchten brauchen Sie andere Codes.

Das CEF hat eine sehr praktische Liste aller möglichen Codes in EN16931 veröffentlicht.

XML generieren und schreiben

Sie können Rechnungen schreiben ohne das XML selbst schreiben zu müssen indem sie die normalen setter verwenden oder die Interfaces implementieren.

Project setup ohne Maven

  1. Download
    1. Mustang
      1. die JAR-Datei https://www.mustangproject.org/deploy/mustang-2.10.0.jar
      2. die notice-Datei https://www.mustangproject.org/deploy/NOTICE
  2. In Ihrer Entwicklungsumgebung: Fügen Sie die http://mustangproject.org/deploy/mustang-2.10.0.jar den JAR-Dateien Ihres Projekts hinzu (Rechtsklick auf Projektname, Eigenschaften), hinzufügen als „external Jar“ zum „Build Path“ im Reiter „libraries“.

XRechnung

Der ZUGFeRD2PullProvider kann direkt (zf2p.generateXML(i); und zf2p.getXML())
genutzt werden um UN/CEFACT CII XRechnungen zu erzeugen, unter der Voraussetzung, dass das XRechnung-Profil gewählt wurde, ein Absender-Ansprechpartner
und eine Leitwegs-ID angegeben wurde.
Eine XRechnung wie diese kann so erzeugt werden:


		Invoice i = new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date())
				.setSender(new TradeParty("Test company","teststr","55232","teststadt","DE").addTaxID("DE4711").addVATID("DE0815").setContact(new Contact("Hans Test","+49123456789","test@example.org")).addBankDetails(new BankDetails("DE12500105170648489890","COBADEFXXX")))
				.setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE"))
				.setReferenceNumber("991-01484-64")//leitweg-id
				.setNumber("123").addItem(new Item(new Product("Testprodukt", "", "C62", BigDecimal.ZERO), /*price*/ new BigDecimal("1.0"),  /*qty*/ new BigDecimal("1.0")));

		ZUGFeRD2PullProvider zf2p = new ZUGFeRD2PullProvider();
		zf2p.setProfile(Profiles.getByName("XRechnung"));
		zf2p.generateXML(i);
		String theXML = new String(zf2p.getXML());
			try {
			BufferedWriter writer = new BufferedWriter(new FileWriter("xrechnung.xml"));
			writer.write(theXML);
			writer.close();
		} catch (IOException e) {
			e.printStackTrace();
		}

Es ist auch möglich, Dateianhänge Base64-codiert ins XML einzubringen:


		FileAttachment fe1=new FileAttachment("one.pdf", "application/pdf", "Alternative", b);
		FileAttachment fe2=new FileAttachment("two.pdf", "application/pdf", "Alternative", b);
		Invoice i = new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date())
				.setSender(new TradeParty(orgname,"teststr","55232","teststadt","DE").addTaxID("DE4711").addVATID("DE0815").setContact(new Contact("Hans Test","+49123456789","test@example.org")).addBankDetails(new BankDetails("DE12500105170648489890","COBADEFXXX")))
				.setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "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)))
				.embedFileInXML(fe1).embedFileInXML(fe2);

Order-X

Order-X erzeugen Sie mit der normalen IExportableTransaction-Interface oder dementsprechend der Invoice-Klasse. Zum Import kann sowohl der normale ZUGFeRDImporter als auch der ZUGFeRDInvoiceImporter genutzt werden. Es wird allerdings der OXExporterFromA1 (bzw. OXExporterFromA3) statt ZUGFeRDExporterFromA1/A3 genutzt der intern den OXPullProvider verwendet. Fälschlicherweise wird die Order-X-Version (1) noch mit setZUGFeRDVersion gesetzt. Möchten Sie ein anderes Profil verwenden können Sie dies unter Angabe des Order-X-Standards erhalten:


import org.mustangproject.EStandard;
import org.mustangproject.ZUGFeRD.Profiles;
...
zfConformanceLevelProfile = Profiles.getByName(EStandard.orderx, "EXTENDED", 1);

Das Zusammenführen erfolgt dann mit


import org.mustangproject.ZUGFeRD.OXExporterFromA1;
import org.mustangproject.*;
...

OXExporterFromA1 oe = new OXExporterFromA1().setProducer("My Application")
.setCreator(System.getProperty("user.name")).setZUGFeRDVersion(1).ignorePDFAErrors()
.load("blanko.pdf"));
oe.setTransaction(new Invoice().setDueDate(new Date()).setIssueDate(new Date()).setDeliveryDate(new Date()).setSender(new TradeParty(orgname, "teststr", "55232", "teststadt", "DE").addVATID("DE0815")).setOwnTaxID("4711").setRecipient(new TradeParty("Franz Müller", "teststr.12", "55232", "Entenhausen", "DE").addVATID("DE0816").setContact(new Contact("contact testname", "123456", "contact.testemail@example.org").setFax("0911623562"))).setNumber(number)
					.addItem(new Item(new Product("Testprodukt", "", "C62", new BigDecimal(19)), amount, new BigDecimal(1.0)));
oe.export("order-x.pdf");

Zusatzfunktionen

Neben dem Abschalten der Eingangsprüfung oder dem Auslesen des rohen XMLs können Sie beispielsweise ZUGFeRD 1 in Version 2, Factur-X/ZUGFeRD 2 in HTML oder Factur-X/ZUGFeRD 2 in UBL umwandeln.

Experimentell: Umwandeln von ZUGFeRD v1 XML nach Fatur-X/ZUGFeRD 2

import org.mustangproject.ZUGFeRD.XMLUpgrader;
...
		XMLUpgrader zmi = new XMLUpgrader();
		String xml = zmi.migrateFromV1ToV2("ZUGFeRD-invoice.xml");
		Files.write(Paths.get("factur-x.xml"), xml.getBytes());
		System.out.println("Written to " + outName);

Intern werden dazu die (noch unvollständigen) XSLT-Transformationen genutzt.

Visualisierung: Umwandeln von ZUGFeRD 2 in HTML

import org.mustangproject.ZUGFeRD.ZUGFeRDVisualizer;

	/**
	 * Export a resource embedded into a Jar file to the local file path.
	 *
	 * @param resourceName ie.: "/SmartLibrary.dll"
	 * @return The path to the exported resource
	 * @throws Exception e.g. if the specified resource does not exist at the specified location
	 */
	static public String ExportResource(String resourceName) throws Exception {
		InputStream stream = null;
		OutputStream resStreamOut = null;
		String jarFolder;
		try {
			stream = Main.class.getResourceAsStream(resourceName);//note that each / is a directory down in the "jar tree" been the jar the root of the tree
			if(stream == null) {
				throw new Exception("Cannot get resource \"" + resourceName + "\" from Jar file.");
			}

			int readBytes;
			byte[] buffer = new byte[4096];
			jarFolder = System.getProperty("user.dir");
			resStreamOut = new FileOutputStream(jarFolder + resourceName);
			while ((readBytes = stream.read(buffer)) > 0) {
				resStreamOut.write(buffer, 0, readBytes);
			}
		} catch (Exception ex) {
			throw ex;
		} finally {
			stream.close();
			resStreamOut.close();
		}

		return jarFolder + resourceName;
	}

...
		ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer();
		String xml = null;
		try {
			xml = zvi.visualize(sourceName);
			Files.write(Paths.get("out.html"), xml.getBytes());
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (TransformerException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		System.out.println("Written to out.html");

		try {
			ExportResource("/xrechnung-viewer.css");
			ExportResource("/xrechnung-viewer.js");

			System.out.println("xrechnung-viewer.css and xrechnung-viewer.js written as well (to local working dir)");
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}
Visualisierung: Umwandeln von Factur-X/ZUGFeRD 2 CII in UBL

import org.mustangproject.CII.CIIToUBL;
...
		CIIToUBL c2u = new CIIToUBL();
		c2u.convert(new File("factur-x.xml"), new File("ubl.xml"));

Setzt ein Testrechnungs-Flag
ZUGFeRDExporter.setTest()
Überspringt die Eingangsprüfung auf valides PDF/A-1
ZUGFeRDExporter.ignoreA1Errors()
Liefert die PDF XMP Metadaten
ZUGFeRDImporter().getMeta()
Liefert ebenfalls das XML der ZUGFeRD-PDF-Datei und entfernt gleichzeigt mögliche UTF8 BOMs
ZUGFeRDImporter().getUTF8()

Einbetten des Validierers

Verwenden Sie als Abhängigkeit die Validiererbibliothek statt der Library in Ihrer pom.xml


<dependency>
  <groupId>org.mustangproject</groupId>
  <artifactId>validator</artifactId>
  <version>2.10.0</version>
</dependency>

können Sie weiterhin alle Mustang-Funktionen nutzen und zusätzlich den Validierer wie folgt aufrufen:


ZUGFeRDValidator zfv=new ZUGFeRDValidator();
System.out.println(zfv.validate("factur-x.pdf"));