Dokumentation
PDFs erstellen
PDFs in Vue
PDFs in Nuxt
PDFs in React
PDFs in Angular
Das Invoice-Modell
Sie fürchten den amtlichen Papierkram? Müssen Sie nicht. Die EN 16931 definiert eine lange Liste von
Feldern, aber Sie brauchen eine Handvoll. Füllen Sie die wenigen Pflichtfelder aus, ergänzen Sie Details
nur dort, wo Ihr Fall es verlangt, und die Bibliothek berechnet jede Summe und schreibt darunter das
konforme XML. Jedes Feld entspricht einem echten Geschäftsbegriff (sein BT-/BG--Code steht im Typ,
falls Sie ihn je brauchen).
Die kleinste Rechnung
Das ist bereits eine vollständige, konforme Rechnung. Alles Weitere auf dieser Seite ist optional.
import { type Invoice } from "@jasy/e-invoice";
const invoice: Invoice = {
number: "RE-001",
issueDate: "2026-06-21", // ISO-Datum
currency: "EUR",
seller: {
name: "Northwind GmbH",
vatId: "DE265013614",
address: { city: "Berlin", postCode: "10115", country: "DE" },
},
buyer: {
name: "Globex AG",
address: { city: "München", postCode: "80331", country: "DE" },
},
lines: [
{
name: "Beratung",
quantity: 8,
unit: "HUR",
netUnitPrice: 120,
vat: { category: "S", ratePercent: 19 },
},
],
};
number, issueDate, currency, seller, buyer und mindestens eine Position in lines - das ist alles, was
verlangt ist. Übergeben Sie das an renderZugferd, und heraus kommen PDF und XML.
Verkäufer und Käufer - seller, buyer
Beide Parteien haben dieselbe Form. name und address (davon ist nur country Pflicht) sind
verpflichtend, alles andere ist optional.
seller: {
name: "Northwind GmbH", // eingetragener Firmenname
vatId: "DE265013614", // nötig, sobald Sie Umsatzsteuer berechnen
address: { line1: "Hauptstraße 1", city: "Berlin", postCode: "10115", country: "DE" },
contact: { name: "A. Schmidt", email: "rechnung@northwind.de", phone: "+49 30 123456" },
electronicAddress: "rechnung@northwind.de", // Pflicht für XRechnung / Peppol
},
Weitere nützliche Felder: tradingName, taxNumber (Steuernummer), legalRegistrationId
(Handelsregisternummer) und additionalLegalInfo (BT-33, der rechtliche Footer - Stammkapital,
Geschäftsführung). Der buyer kennt all das ebenso.
Die Adresse nimmt mehr als die vier Felder oben, wenn Sie sie brauchen: line2 und line3 für eine zweite
und dritte Adresszeile, und subdivision für eine Region oder ein Bundesland (BT-39 / BT-54). Pflicht ist
nur country.
Positionen - lines
Jede Position in lines nennt einen Artikel (name), eine Menge (quantity) in einer Einheit (unit),
den Netto-Einzelpreis (netUnitPrice) und ihre Umsatzsteuer (vat). Die Bibliothek multipliziert und summiert - Sie rechnen nie selbst eine Position zusammen.
lines: [
{
name: "Beratung",
description: "Workshop vor Ort", // optional
quantity: 8,
unit: "HUR", // UN/ECE Rec 20 - "HUR" Stunden, "C62" Stück, "DAY", "KGM" kg, "MTR" Meter ...
netUnitPrice: 120,
vat: { category: "S", ratePercent: 19 },
},
{
name: "Lizenz",
quantity: 1,
unit: "C62",
netUnitPrice: 480,
vat: { category: "S", ratePercent: 19 },
},
],
Mehr je Position, wenn Sie es brauchen: sellerItemId / buyerItemId / standardItemId (eine GTIN),
priceBaseQuantity (ein Preis „pro 100“), allowancesCharges je Position und eine freie note.
Und für Kunden, die automatisch abgleichen:
{
name: "Zähler Typ M-200",
quantity: 2,
unit: "C62",
netUnitPrice: 480,
vat: { category: "S", ratePercent: 19 },
orderLineRef: "12", // welche Zeile ihrer Bestellung damit beglichen wird (BT-132)
objectRef: "METER-99213", // der abgerechnete Gegenstand (BT-128)
buyerAccountingRef: "CC-4712", // ihre Kostenstelle, für diese Position (BT-133)
originCountry: "CH", // Ursprungsland (BT-159)
}
Umsatzsteuer - vat
Die Umsatzsteuer steht an der Position (und an jedem Eintrag in allowancesCharges): vat mit einer
Kategorie (category) und ihrem Satz (ratePercent).
category | Bedeutung | ratePercent |
|---|---|---|
S | Regelsteuersatz | der Prozentsatz, z. B. 19 |
Z | Nullsatz | 0 |
E | Steuerbefreit | weglassen |
AE | Steuerschuldnerschaft des Leistungsempfängers (Reverse Charge) | weglassen |
K | Innergemeinschaftliche Lieferung (EU) | weglassen |
G | Ausfuhr außerhalb der EU | weglassen |
O | Nicht steuerbar | weglassen |
Für die Kategorien außer dem Regelsatz verlangt die EN 16931 einen Grund. Geben Sie ihn einmal je
Kategorie in vatExemptionReasons an:
const invoice: Invoice = {
// ... jede Position verwendet { category: "AE" } ...
vatExemptionReasons: {
AE: {
text: "Reverse charge - Steuerschuldnerschaft des Leistungsempfängers (§13b UStG)",
code: "VATEX-EU-AE",
},
},
};
Die optionalen Extras
Greifen Sie danach, wenn Ihr Fall es verlangt. Nichts davon ist Pflicht.
Daten und Referenzen
dueDate: "2026-07-05",
buyerReference: "04011000-12345-34", // die Leitweg-ID - PFLICHT für XRechnung (B2G)
purchaseOrderRef: "PO-7782", // ihre Bestellnummer (BT-13)
salesOrderRef: "SO-1190", // Ihre Auftragsnummer (BT-14)
contractRef: "C-2026-09",
projectRef: "Neue Halle 4", // das Projekt, zu dem die Rechnung gehört (BT-11)
tenderRef: "VgV-2026-0031", // Vergabe oder Los, für öffentliche Aufträge (BT-17)
objectRef: "METER-99213", // was abgerechnet wird, wenn es ein Gegenstand ist (BT-18)
buyerAccountingRef: "CC-4711", // ihre Kostenstelle (BT-19)
notes: ["Vielen Dank für Ihren Auftrag."],
noteSubjectCode: "AAI", // worum es in den Hinweisen geht, als Code (BT-21)
Große Kunden bestehen oft auf buyerAccountingRef: Damit kann ihre Buchhaltung die Rechnung zuordnen,
ohne dass ein Mensch sie liest. Beide Parteien können außerdem eine Verzeichniskennung tragen - eine GLN
oder Ähnliches - als seller.identifier (BT-29) und buyer.identifier (BT-46).
Zahlung (payment) - damit der Käufer weiß, wohin und wie er zahlt:
payment: {
meansCode: "58", // SEPA-Überweisung (UNCL 4461); "30" ist eine einfache Überweisung
iban: "DE89370400440532013000",
bic: "COBADEFFXXX",
accountName: "Northwind GmbH",
terms: "Zahlbar innerhalb von 14 Tagen ohne Abzug.",
reference: "RE-2026-0184", // BT-83, was der Zahler angeben soll; ohne Angabe die Rechnungsnummer
meansText: "SEPA-Lastschrift", // BT-82, wenn ein Code allein es nicht sagt
},
reference ist der Verwendungszweck - der Text, den der Zahler auf die Überweisung schreibt. Lassen Sie ihn
weg, wird die Rechnungsnummer verwendet, und das ist fast immer das, was Sie wollen.
Skonto - cashDiscounts
„2 % innerhalb von 14 Tagen“ ist kein Nachlass im Sinne der EN 16931, und das ist die eine Stelle, an
der die naheliegende Abkürzung eine falsche Rechnung erzeugt. Ein Abzug, der nur gilt, wenn der Käufer früh
zahlt, darf die Rechnungssumme nicht mindern, denn beim Ausstellen weiß niemand, ob er gezogen wird. Er
gehört deshalb in die Zahlungsbedingungen, als payment.cashDiscounts:
payment: {
iban: "DE89370400440532013000",
terms: "Zahlbar innerhalb von 30 Tagen.",
cashDiscounts: [
{ days: 14, percent: 2 }, // 2 % bei Zahlung innerhalb von 14 Tagen
{ days: 30, percent: 1 },
],
},
Die Basis ist die Rechnungssumme einschließlich Umsatzsteuer, auf die in Deutschland üblicherweise Skonto
berechnet wird; je Stufe lässt sie sich mit baseAmount überschreiben. Die Summen bewegen sich nicht - das
Skonto wird auf die Seite gedruckt und in BT-20 in der maschinenlesbaren Form geschrieben, die die XRechnung
vorschreibt:
#SKONTO#TAGE=14#PROZENT=2.00#
allowancesCharges ab. Der Betrag würde sofort abgezogen, obwohl er
nur bei früher Zahlung gilt - eine Datei, die jeden Validator besteht und die falsche Summe nennt.
xrechnungProblems() sucht genau diesen Fehler und benennt ihn.SEPA-Lastschrift - directDebit
Wenn Sie einziehen, statt bezahlt zu werden, muss der Zahler die Lastschrift auf seinem Kontoauszug wiedererkennen:
payment: {
meansCode: "59", // SEPA-Lastschrift (UNCL 4461); "58" ist eine SEPA-Überweisung
directDebit: {
mandateReference: "MND-2024-00871", // das Mandat, das er unterschrieben hat (BT-89)
creditorId: "DE98ZZZ09999999999", // Ihre Gläubiger-ID (BT-90)
debitedIban: "DE02120300000000202051", // das belastete Konto (BT-91)
},
},
Alle drei stehen auf der Seite und im XML. Ein Einzug ohne Mandatsreferenz wird von der Vorabprüfung gemeldet, weil der Zahler ihn mit nichts abgleichen kann.
Nachlässe oder Zuschläge auf den Beleg (allowancesCharges, isCharge unterscheidet sie) - nach der Summe der Positionen angewendet, jeweils mit eigener
Umsatzsteuer:
allowancesCharges: [
{ isCharge: false, amount: 50, reason: "Treuerabatt", vat: { category: "S", ratePercent: 19 } },
{ isCharge: true, amount: 12, reason: "Versand", vat: { category: "S", ratePercent: 19 } },
],
Jeder nimmt außerdem einen reasonCode - das codierte Gegenstück zu reason (UNCL 5189 für einen Nachlass,
UNCL 7161 für einen Zuschlag). Manche Empfänger gleichen über den Code ab statt über den Text.
Sie können statt eines festen Betrags (amount) einen Prozentsatz (percent) angeben, und dann bleiben „10 %“ im XML und auf
der Seite erhalten, statt still in Euro umgerechnet zu werden:
allowancesCharges: [
{
isCharge: false,
baseAmount: 3980, // worauf sich der Prozentsatz bezieht (BT-93)
percent: 10, // der Satz (BT-94)
reason: "Rahmenvertrag",
vat: { category: "S", ratePercent: 19 },
},
],
Der Betrag wird abgeleitet. An einer Position funktioniert es genauso (BT-137/138 und BT-142/143). Geben
Sie einen ausdrücklichen amount und einen Prozentsatz an, gewinnt der Betrag - Sie zu übergehen würde
einen Tippfehler verstecken -, aber die Vorabprüfung meldet die Abweichung mit beiden Zahlen.
Auf der Seite steht dann Rahmenvertrag (10 % von 3.980,00 €) -398,00 € statt eines unerklärten -398,00 €.
Gutschriften und Korrekturen - precedingInvoices
Eine Gutschrift (type: 381), die nicht über precedingInvoices sagt, was sie korrigiert, lässt sich ihrem Original nicht zuordnen - und nach
dem Original sucht ein Prüfer:
const creditNote: Invoice = {
number: "GS-2026-0011",
type: 381, // Gutschrift
precedingInvoices: [{ number: "RE-2026-0187", issueDate: "2026-08-14" }],
// ... der Rest wie üblich
};
Mehrere sind erlaubt - eine Korrektur kann mehr als eine Rechnung betreffen. Das Datum ist optional, löst aber eine Nummer auf, die sich über die Jahre wiederholt.
Anhänge - supportingDocuments
Ein Stundenzettel, ein Leistungsnachweis, ein Lieferschein. Wer nach Aufwand abrechnet, hat so etwas, und es getrennt per E-Mail zu schicken verschenkt genau den Vorteil, für den es die E-Rechnung gibt:
supportingDocuments: [
{
reference: "TIMESHEET-09",
description: "Stundenzettel September",
file: {
content: readFileSync("stunden.csv"), // Uint8Array
mimeType: "text/csv",
filename: "stunden.csv",
},
},
// oder ein Link statt einer Datei:
{ reference: "AB-2026-14", description: "Auftragsbestätigung", url: "https://…/ab.pdf" },
],
Eine file reist zweimal mit: Base64-codiert im XML, wo eine Maschine sie findet, und als eingebettete
Datei im PDF/A-3, wo ein Mensch sie per Doppelklick öffnet. Nur das Rechnungs-XML ist als Rechnungsdaten
markiert, Anhänge als Ergänzungen. Die Seite nennt sie, damit niemand erst auf die Idee kommen muss, in der
Anhangsleiste nachzusehen.
Und noch ein paar: delivery (date, recipientName, eine Lieferadresse address und locationId
für eine Standortnummer statt einer Adresse), payeeName mit payeeIdentifier und
payeeLegalRegistrationId (wenn die Zahlung an jemand anderen als den Verkäufer geht - etwa einen Factor),
paidAmount (bereits gezahlt, wird abgezogen und ergibt den Zahlbetrag) und type (380 eine
Handelsrechnung, 381 eine Gutschrift).
Auf einen glatten Zahlbetrag runden - roundingAmount
Manche Häuser ziehen einen runden Betrag ein. roundingAmount ist eine Zahl mit Vorzeichen, die zum
Zahlbetrag addiert wird; die Rechnungssumme selbst bewegt sich nicht:
roundingAmount: -0.4, // 107,40 Summe -> 107,00 zu zahlen
Die Seite druckt die Rundungszeile mit Vorzeichen, denn ein Zahlbetrag, der nicht der Summe abzüglich des bereits Gezahlten entspricht, sieht sonst wie ein Rechenfehler aus.
Rechnungen in fremder Währung - taxCurrency
Ist die Rechnung nicht in Euro, schuldet ein deutscher Verkäufer dem Finanzamt die Umsatzsteuer trotzdem in
Euro. Geben Sie sie mit taxCurrency und taxTotalInTaxCurrency an, sonst ist die Rechnung rechtlich mangelhaft:
currency: "USD",
taxCurrency: "EUR", // die Währung, in der die Umsatzsteuer verbucht wird (BT-6)
taxTotalInTaxCurrency: 172.4, // die Steuersumme darin (BT-111)
xrechnungProblems() warnt, sobald
die Währung nicht EUR ist und keine Buchungswährung angegeben wurde.Das ist das ganze Modell. Beginnen Sie mit der kleinsten Rechnung und ergänzen Sie nur, was Sie brauchen - Summen und XML bleiben darunter jedes Mal korrekt.