Releases

VersionDatumHinweiseKompatibilitätBemerkung
2.1.203.12.2024Erste veröffentlichte Fassungab Nuclos 4.2022.41.2

Überblick

Das Nuclet ermöglicht den automatisierten Empfang elektronischer Rechnungen (E-Rechnungen) im XML- oder PDF-Format, deren offizielle Validierung und Darstellung in einer HTML-Datei sowie die Vorbereitung zur Umwandlung in ein geeignetes Businessobjekt (im Weiteren: Eingangsrechnung).

Unterstützt wir das Format XRechnung in der Version 3.0.2 (20.06.2024) in der XML-Syntax Universal Business Language (UBL) der OASIS in der Version 2.1 und der XML-Syntax Cross Industry Invoice (CII) der UN/CEFACT in der Version D16B.
Weiterhin wird die Spezifikation ZUGFeRD mit ihren fünf Profilen in der Version 2.3.2 (15.11.2024) abgedeckt.

Der Empfang elektronischer Rechnung in veralteten Versionen der genannten Formate ist in vielen Fällen ebenfalls möglich, resultiert aber meist in zusätzlichen Validierungsmeldungen.

E-Rechnungen können über den Job Importiere ERechnungen automatisch aus einem vorgegebenen Verzeichnis (Nuclet-Parameter Ablagepfad) importiert werden.

  • Dabei wird ein Businessobjekt E-Rechnungsimport mit entsprechenden Meldungen erzeugt. Danach erfolgt - falls möglich - die Erstellung einer Eingangsrechnung aus den Daten.
  • Die Dateien werden anschließend in entsprechende Unterordner verschoben, damit sie nicht mehrfach verarbeitet werden.

E-Rechnungen können außerdem manuell über die Maske System -> E-Rechnungsimport importiert und in eine Eingangsrechnung überführt werden.

Das Beispiel-Nuclet unter Nuclet: Elektronische Rechnungen - XRechnung und ZUGFeRD kann auch hier verwendet werden.

Hinweise

Abhängigkeiten

Das Nuclet benötigt die beiden unter Nuclet: Elektronische Rechnungen - XRechnung und ZUGFeRD beschriebenen Nuclets.

  1. Zur Erstellung von Eingangsrechnungen muss eine projektspezifische Objektgeneratorregel erstellt werden, die die Zuordnung zu den Feldern der Eingangsrechnung übernimmt. An dieser Stelle kann auch das Interface org.nuclet.xrechnung.imp.ImportiereERechnungen.InvoiceHelper implementiert werden (die Zuordnung erfolgt über den Nuclet-Parameter ImplKlasseEingangsrechnung), das die im Job benötigten Methoden zur Validierung der Eindeutigkeit (getEingangsrechnung), Erstellung einer neuen Eingangsrechnung (newEingangsrechnung) und Zuordnung der Felder (setFields) bereitstellt. Ein vollständiges Beispiel ist unter Integration genannt.
  2. Über die Methode org.nuclet.xrechnung.imp.AktualisiereERechnungsimport#getXRechnung wird das allgemeine Java-Objekt auf Basis des XRechnungs-Standards zur Verfügung gestellt. Falls Felder benötigt werden, die nur in einer der beiden o.g. XML-Syntaxen vorhanden sind, können die jeweils vollständigen Java-Transformationen über org.nuclet.xrechnung.imp.AktualisiereERechnungsimport#getCompleteInvoice ermittelt werden.

Integration

SchrittBeschreibungScreenshot / Beschreibung
1

Falls noch nicht erfolgt, die beiden unter Nuclet: Elektronische Rechnungen - XRechnung und ZUGFeRD beschriebenen Nuclets importieren (siehe Nuclet Import).

Nach Import dieser Nuclets muss die Serverinstanz neu gestartet werden.


2

Import des Nuclets durchführen, anschließend muss erneut die Serverinstanz neu gestartet werden, da das Nuclet eigene Extensions enthält.


3

Einen Objektgenerator für die Eingangsrechnungen mit entsprechender Regel erstellen.

ErstelleEingangsrechnung.java
package org.nuclet.beispielxrechnung; 

import eu.ce.en16931._2017.xoev_de.kosit.standard.xrechnung_1.*;
import org.nuclet.xrechnung.imp.AktualisiereERechnungsimport;
import org.nuclet.xrechnung.imp.ERechnungsimport;
import org.nuclet.xrechnung.imp.ImportiereERechnungen;
import org.nuclos.api.businessobject.Flag;
import org.nuclos.api.businessobject.facade.Modifiable;
import org.nuclos.api.provider.QueryProvider;
import org.nuclos.api.rule.GenerateRule;
import org.nuclos.api.context.GenerateContext; 
import org.nuclos.api.annotation.Rule; 
import org.nuclos.api.exception.BusinessException;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static org.nuclet.xrechnung.imp.ImportiereERechnungen.*;
import static org.nuclet.xrechnung.imp.ImportiereERechnungen.getTextValue;

/** @name        
  * @description 
  * @usage       
  * @change      
*/
@Rule(name="ErstelleEingangsrechnung", description="ErstelleEingangsrechnung")
public class ErstelleEingangsrechnung implements GenerateRule {

	public void generate(GenerateContext context) throws BusinessException {
		ERechnungsimport imp = context.getSourceObject(ERechnungsimport.class);
		Rechnung rechnung = context.getTargetObject(Rechnung.class);
		setFields(imp, rechnung);
	}

	public static void setFields(ERechnungsimport imp, Rechnung rechnung) throws BusinessException {
		AktualisiereERechnungsimport.checkUpdate(imp, "Zu diesem Import wurde bereits eine Eingangsrechnung erstellt.");
		Invoice xRechnung = AktualisiereERechnungsimport.getXRechnung(imp);
		rechnung.setErechnungsimportId(imp.getId());
		rechnung.setRechnungsnummer(xRechnung.getInvoiceNumber().getValue());
		rechnung.setRechnungsdatum(getDate(xRechnung.getInvoiceIssueDate()));
        rechnung.setCoderechnungsart(getCodeValue(xRechnung.getInvoiceTypeCode()));
		rechnung.setFaelligkeit(getDate(xRechnung.getPaymentDueDate()));
		DocumentReference purchaseOrderReference = xRechnung.getPurchaseOrderReference();
		if (purchaseOrderReference != null) {
			rechnung.setBestellnummer(purchaseOrderReference.getValue());
		}
		rechnung.setZahlungskonditionen(getTextValue(xRechnung.getPaymentTerms()));
		SELLERType seller = xRechnung.getSELLER();
        if (seller != null) {
			SELLERCONTACTType sellerContact = seller.getSELLERCONTACT();
            if (sellerContact != null) {
				rechnung.setAnsprechpartnername(getTextValue(sellerContact.getSellerContactPoint()));
				rechnung.setAnsprechpartnertelefon(getTextValue(sellerContact.getSellerContactTelephoneNumber()));
				rechnung.setAnsprechpartneradresse(getTextValue(sellerContact.getSellerContactEmailAddress()));
			}
			String ustIdNr = getIdValue(seller.getSellerVATIdentifier());
            if (ustIdNr != null) {
				List<Kunde> kundeList = QueryProvider.execute(QueryProvider.create(Kunde.class).where(Kunde.Ustidnr.eq(ustIdNr)).setChunkSize(2L));
				int size = kundeList.size();
				Long kundeId = null;
				if (size == 1) {
                    kundeId = kundeList.iterator().next().getId();
                } else if (size == 0) {
					Kunde kunde = new Kunde();
					kunde.setName(getTextValue(seller.getSellerName()));
					kunde.setLeitweg(getTextValue(xRechnung.getBuyerReference()));
					SELLERPOSTALADDRESSType sellerpostaladdress = seller.getSELLERPOSTALADDRESS();
                    if (sellerpostaladdress != null) {
						kunde.setStrasse(getTextValue(sellerpostaladdress.getSellerAddressLine1()));
						kunde.setPlz(getTextValue(sellerpostaladdress.getSellerPostCode()));
						kunde.setOrt(getTextValue(sellerpostaladdress.getSellerCity()));
						kunde.setLaendercode(getCodeValue(sellerpostaladdress.getSellerCountryCode()));
					}
					kunde.setUstidnr(ustIdNr);
					kunde.save();
					kundeId = kunde.getId();
				}
                if (kundeId != null) {
					rechnung.setKundenreferenzId(kundeId);
                }
			}
		}
		rechnung.setWaehrung(getCodeValue(xRechnung.getInvoiceCurrencyCode()));
		List<VATBREAKDOWNType> vatbreakdown = xRechnung.getVATBREAKDOWN();
        if (vatbreakdown.size() == 1) {
			VATBREAKDOWNType breakdown = vatbreakdown.iterator().next();
			rechnung.setSteuercode(getCodeValue(breakdown.getVATCategoryCode()));
			rechnung.setSteuersatz(getBigDecimal(breakdown.getVATCategoryRate()));
        }
		for (INVOICELINEType line : xRechnung.getINVOICELINE()) {
			Rechnungsposition pos = new Rechnungsposition();
			ITEMINFORMATIONType iteminformation = line.getITEMINFORMATION();
            if (iteminformation != null) {
				pos.setBezeichnung(getTextValue(iteminformation.getItemName()));
			}
			pos.setMenge(getBigDecimal(line.getInvoicedQuantity()));
			pos.setEinheit(getCodeValue(line.getInvoicedQuantityUnitOfMeasureCode()));
			pos.setNettobetrag(getBigDecimal(line.getInvoiceLineNetAmount()));
			pos.setBestellposition(getIdValue(line.getInvoiceLineIdentifier()));
			INVOICELINEPERIODType period = line.getINVOICELINEPERIOD();
            if (period != null) {
				pos.setLeistungszeitraumvon(getDate(period.getInvoiceLinePeriodStartDate()));
				pos.setLeistungszeitraumbis(getDate(period.getInvoiceLinePeriodEndDate()));
			}
			rechnung.insertRechnungsposition(pos);
		}
	}

	public static class InvoiceHelperImpl implements InvoiceHelper {

		public Modifiable<?> newEingangsrechnung() {
			return new Rechnung();
		}

		public List<? extends Modifiable<?>> getEingangsrechnung(ERechnungsimport imp, Flag... flags) {
			List<Rechnung> list = new ArrayList<>();
			org.nuclet.beispielxrechnung.ERechnungsimport rechnungsimport = org.nuclet.beispielxrechnung.ERechnungsimport.get(imp.getId());
            if (rechnungsimport != null) {
                list = rechnungsimport.getRechnung(flags);
            }
			return list;
		}

		public void setFields(ERechnungsimport imp, Modifiable<?> eingangsrechnung) throws BusinessException {
			ErstelleEingangsrechnung.setFields(imp, (Rechnung) eingangsrechnung);
		}
	}
}
4

Bei Bedarf einen Integrationspunkt im Nuclet der Eingangsrechnung mit Verweis auf den E-Rechnungsimport erstellen und ein Referenzfeld über den BO-Editor anlegen.

5

Bei Bedarf das Layout ERechnungsimport ergänzen.

  • Unterformular für Eingangsrechnung einbinden
  • Objektgenerator über Knopf einbinden (alternativ nur über Menü ohne Layoutänderung)

6

Die Nuclet-Parameter geeignet setzen.

7

Die Funktionsfähigkeit des Jobs und des manuellen Imports mit geeigneten Beispielen (etwa xrechnung-testsuite/.../technical-cases oder zugferd-2.3.2) prüfen.