Zur Startseite

Blog - Rechnungen mit LaTeX und Pandoc (Teil 1) - von Thomas Stockschläder am 1. Juni 2024

Dieser Blog-Post ist Teil der Serie Digitalisierung unseres Rechnung-Workflows, in dem wir die schrittweise digitalisierte Rechnungslegung von modevo vorstellen:

  • Rechnungen mit LaTeX und Pandoc (Teil 1)
  • Mini-CMS und CLI zur Rechnungserstellung (Teil 2)
  • Automatische Abrechnung einer Zeiterfassung mit Google-Sheets (Teil 3)
  • Migration in eine moderne Rails-Anwendung (Teil 4)
  • Modernes Rechnungs-UI mit Hotwire (Teil 5)

Rechnungen müssen nicht entweder mit der Schreibmaschine oder mit Buchhaltungssoftware erstellt werden. Digitalisierung ist ein Spektrum. Schon kleine Schritte in die richtige Richtung können viel Arbeit und Fehlerpotenzial sparen.

Im Kontext der Digitalisierung hat die Rechnungsstellung einige interessante Eigenschaften. Sie ist für den Geschäftsbtrieb unabdingbar, relativ leicht manuell zu bearbeiten, aber schwierig vollständig zu automatisieren. Diese Kombination verlockt schnell dazu, Unmengen Zeit in eine vollständige Automatisierung stecken zu wollen; nur um letzten Endes zu scheitern, oder weit von einem Return-Of-Investment zu sein:

XKCD 1319: Ein Graph, der Theorie und Realität beim Versuch, ein Programm zur Automatisierung einer zeitaufwändigen Aufgabe gegenüber stellt. In der Theorie geht man davon aus, dass man kurzzeitig mehr Arbeit investiert, um kurze Zeit später fast keine Arbeit mit der Aufgabe zu haben. In der Realität muss man debuggen, das Problem komplett neu denken und verbringt lange mit der Weiterentwicklung, während die eigentliche Aufgabe immer noch nicht automatisiert ist. Aber nun ist keine Zeit mehr für diese ursprüngliche Aufgabe.
XKCD 1319: Automation

In einem solchen Fall kann es sinnvoll sein, sich stattdessen für eine progressiven Vorgehensweise zu entschieden, die man nach und nach automatisieren kann. Bei der Technologie-Wahl gibt es viele Möglichkeiten – von einer einfachen Word- (oder Google Docs)-Datei über eine HTML-zu-PDF-Konvertierung bis hin zu einem professionell gesetzten Dokument via LaTeX.

Da uns präzise Typografie und granulare Kontrolle über das Endprodukt sehr wichtig sind, haben wir uns für unsere Geschäftspapiere und Rechnungen für LaTeX entschieden. Word und HTML-zu-PDF haben ihre Daseinsberechtigung, kamen für dieses Projekt für uns aber nicht in Frage.

Egal für welches Werkzeug man sich auch entscheidet: für die Automatisierung ist es sehr hilfreich, Formatierung und Inhalt zu trennen. Das ermöglicht ein voraussehbares Ergebnis und leichte Anpassbarkeit bspw. beim Wechsel des Briefkopfes. LaTeX selbst bietet zwar schon eine solide Trennung zwischen Formatierung und Inhalten, aber für meinen Geschmack noch nicht genug. Wenn der Inhalt in einem gängigen Format wie JSON, YAML, TOML, XML und bspw. als Markdown gespeichert wird, so ist es ein Leichtes, diese Inhalte später mit diversen Werkzeugen zu generieren. Das kann LaTeX selbst bieten.

Aber wie bringen wie bringen wir jetzt eine YAML- mit einer LaTeX-Datei zusammen, um daraus eine PDF zu machen? Darf ich vorstellen: Pandoc. Pandoc ist ein universeller Dokumentenkonverter, der es ermöglicht, Dokumente zwischen verschiedenen Markup- und Dateiformaten zu konvertieren, wie z.B. Markdown, HTML, LaTeX, PDF und Word.

Unser gewünschter Softwarestack ist also:

  • LaTeX für die Rechnungs-Vorlage und zur PDF-Generierung
  • YAML für Meta-Daten und Inhalte
  • Pandoc um das LaTeX-Dokument mit den YAML-Inhalten anzureichern

Pandoc können mit homebrew unter MacOS wie folgt installieren:

            
            brew install pandoc
            
In die Zwischenablage kopiert

Unter MacOS ist die Installation von LaTeX fast noch einfacher. Wir nutzen dafür MacTeX (*.pkg, 5,6GB).

Nach der, zugegeben, happigen Installationsgröße ist alles installiert, was wir für eine vollständige LaTeX-Umgebung benötigen. Dazu gehören u.a. Schriftarten Anwendungen für die Bibliografie-Verwaltung (BibDesk), Editoren, Rechtschreibprüfung und Ghostscript.

Unsere obligatorische Hallo Welt-Datei:

template.tex
            
            \documentclass[11pt]{article}

\begin{document}
\section{Einleitung}

Hallo Welt \\

\end{document}
            
In die Zwischenablage kopiert

Die wir wie folgt bauen und öffnen können:

            
            xelatex template.tex && open template.pdf
            
In die Zwischenablage kopiert

Das Ergebnis sieht dann so aus:

Unser 'Hallo Welt'-Dokument
So ist das zwar noch keine 5,6GB wert, aber dazu kommen wir noch.

Im nächsten Schritt benutzen wir Pandoc, um Daten aus einer yaml-Datei im LaTeX-Kontext benutzen zu können. Dafür erstellen wir eine details.yml:

details.yml
            
            ---
company: Beispiel GmbH
---
            
In die Zwischenablage kopiert

Der Zugriff in der TeX-Datei funktioniert so:

template.tex
            
            \documentclass[11pt]{article}

\begin{document}
\section{Einleitung}

Hallo Welt \\

$company$

\end{document}

            
In die Zwischenablage kopiert
            
            pandoc details.yml -o test.pdf --template=template.tex --pdf-engine=xelatex
            
In die Zwischenablage kopiert

Die PDF-Datei wird generiert und der company-Wert aus der details.yml steht nun auch drin. Aber nicht alle Rechnungsinformationen sind so simpel, wie der Firmenname. Für eine vollständige Rechnung benötigen wir etwas mehr:

details.yml
            
            ---
invoice-nr: 202400001
company: Beispiel GmbH
city: Beispielhausen
from:
- Beispielgasse 2
- 54321 Beispielhausen
to:
- Max Mustermann GmbH
- Musterstr. 1
- 12345 Musterstadt
vat: 19
service:
- description: 'Beratung'
  price: 500
  details:
  - 'Leistungsdatum: 01.04.2024'
- description: 'Hosting'
  price: 50.50
  details:
  - 'April 2024'
credentials:
  company: 'Beispiel GmbH'
  bank: 'Hamburger Sparkasse'
  iban: 'DE02 2005 0550 1015 8713 93'
  bic: 'HASPDEHH'
closingnote: |
  Bitte begleichen Sie die Rechnung binnen 30 Tagen auf folgendes Konto:

  \credentials

  Wir danken Ihnen für Ihr Vertrauen.

  \vspace{1em}Mit freundlichen Grüßen
---
            
In die Zwischenablage kopiert

Auf Basis einer angepassten LaTeX-Vorlage für DIN-Rechnungen:

template.tex
            
            %!TEX TS-program = xelatex
%!TEX encoding = UTF-8 Unicode

\documentclass[10pt, a4paper]{article}

% LAYOUT
%--------------------------------
\usepackage{geometry}
\geometry{a4paper, left=25mm, right=20mm, top=55mm, bottom=25mm}

% No page numbers
\pagenumbering{gobble}

% Left align
\usepackage[document]{ragged2e}

% TYPOGRAPHY
%--------------------------------
\usepackage{fontspec}
\usepackage{xunicode}
\usepackage{xltxtra}
\usepackage{xstring}
\usepackage[boldmath]{numprint}
\usepackage{qrcode}
\usepackage{alltt}

% converts LaTeX specials (quotes, dashes etc.) to Unicode
\defaultfontfeatures{Mapping=tex-text}
\setromanfont [Ligatures={Common}, Numbers={OldStyle}]{Open Sans}
\setsansfont[Scale=0.9]{Open Sans}
\setmonofont[Scale=0.8]{Courier}

% Set paragraph break
\setlength{\parskip}{1em}

% Custom ampersand
\newcommand{\amper}{{\fontspec[Scale=.95]{Open Sans}\selectfont\itshape\&}}

\setmainfont[SmallCapsFeatures={LetterSpace=5,Letters=SmallCaps}]{Open Sans}
\setsansfont{Open Sans}

% Command required by how Pandoc handles the list conversion
\providecommand{\tightlist}{%
  \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}

% TABLE CUSTOMIZATION
%--------------------------------
\usepackage{spreadtab}
\usepackage[compact]{titlesec} % For customizing title sections
\titlespacing*{\section}{0pt}{3pt}{-7pt} % Remove margin bottom from the title
\usepackage{arydshln} % For the dotted line on the table
\renewcommand{\arraystretch}{1.5} % Apply vertical padding to table cells
\usepackage{hhline} % For single-cell borders
\usepackage{enumitem} % For customizing lists
\setlist{nolistsep} % No whitespace around list items
\setlist[itemize]{leftmargin=0.5cm} % Reduce list left indent
\setlength{\tabcolsep}{9pt} % Larger gutter between columns

% LANGUAGE
%--------------------------------
\usepackage{polyglossia}
\setmainlanguage{german}

% PDF SETUP
%--------------------------------
\usepackage[xetex, bookmarks, colorlinks, breaklinks]{hyperref}
\hypersetup
{
  pdfauthor={$author$},
  pdfsubject=Rechnung Nr. $invoice-nr$,
  pdftitle=Rechnung Nr. $invoice-nr$,
  linkcolor=blue,
  citecolor=blue,
  filecolor=black,
  urlcolor=blue
}

% DOCUMENT
%--------------------------------
\begin{document}
\scriptsize
{\color{gray}
\textsc{\textbf{$company$}}
$for(from)$
\textbullet{} \textsc{$from$}
$endfor$
}

\normalsize \sffamily
$for(to)$
$to$\\
$endfor$

\vspace{4.5em}

\begin{flushright}
  \small
  $city$, \today
\end{flushright}

\vspace{1em}

\section*{\textsc{Rechnung} \textsc{$invoice-nr$}}
\vspace{2em}
\footnotesize
\newcounter{pos}
\setcounter{pos}{0}
\STautoround*{2}
\STsetdecimalsep{,}

\begin{spreadtab}[\STsavecell\invoicesum{c5}]{{tabular}[t t t]{lp{12.3cm}r}}
  \hdashline[1pt/1pt]
  @ \noalign{\vskip 1mm} \textbf{Pos.} & @ \textbf{Bezeichnung} & @
  \textbf{Preis in EUR} \\ \hline \noalign{\vskip 1mm}
      $for(service)$ @ \noalign{\vskip 2mm} \refstepcounter{pos} \thepos
        & @ $service.description$
        $if(service.details)$\newline {\vskip 1mm} \begin{itemize}
          $for(service.details)$\scriptsize \item $service.details$
          $endfor$ \end{itemize}
        $endif$ & {\numprint{:={$service.price$}}} \\$endfor$ \noalign{\vskip 1mm} \hline
  $if(vat)$
    @ & @ \multicolumn{1}{r}{Zwischensumme:}      & \numprint{:={sum(c1:[0,-1])}} \\ \hhline{~~-}
    @ & @ \multicolumn{1}{r}{zzgl. $vat$\% MwSt:} & \numprint{:={$vat$/100*[0,-1]}} \\ \hhline{~~-}
  $endif$
  {\bfseries}
  @ & @ \multicolumn{1}{r}{\textbf{Gesamt:}}   &
    \textbf{\numprint{:={$if(vat)$[0,-1]+[0,-2]$else$sum(c1:[0,-1])$endif$}}} \\ \hhline{~~-}
\end{spreadtab}

\vspace{1em}

% Credentials
%--------------------------------
\providecommand{\credentials}{
  \hspace{0.4cm}
  \begin{minipage}{0.7\textwidth}
    $credentials.company$ \\
    Kreditinstitut: $credentials.bank$ \\
    IBAN: $credentials.iban$ \\
    BIC: $credentials.bic$
  \end{minipage}
}

\sffamily
\small
$closingnote$

\medskip

$author$

\end{document}
            
In die Zwischenablage kopiert

Mit einer Makefile können wir den Umgang mit der Erstellung noch etwas vereinfachen:

Makefile
            
            TEX = pandoc
src = template.tex details.yml
FLAGS = --pdf-engine=xelatex

output.pdf : $(src)
    $(TEX) $(filter-out $<,$^ ) -o $@ --template=$< $(FLAGS) && open $@

.PHONY: clean
clean :
    rm output.pdf
            
In die Zwischenablage kopiert

Zur Erstellung der Rechnung und öffnen der PDF müssen wir dann nur noch make aufrufen:

            
            make
            
In die Zwischenablage kopiert
Unser 'Hallo Welt'-Dokument
Unsere LaTeX-Rechnung nimmt Form an.

Um das Template nicht unnötig kompliziert zu machen und Gemeinsamkeiten sowohl auf Rechnungen, Verträgen und anderen Geschäftspapieren nutzen zu können, binden wir den Briefkopf als PDF ein:

template.tex
            
            %!TEX TS-program = xelatex
%!TEX encoding = UTF-8 Unicode

\documentclass[10pt, a4paper]{article}

% LAYOUT
%--------------------------------
\usepackage{geometry}
\geometry{a4paper, left=25mm, right=20mm, top=55mm, bottom=25mm}

% No page numbers
\pagenumbering{gobble}

% Left align
\usepackage[document]{ragged2e}

\usepackage{wallpaper}
\ULCornerWallPaper{1}{letterhead.pdf}

% TYPOGRAPHY
%--------------------------------
\usepackage{fontspec}
% ...

            
In die Zwischenablage kopiert

Damit können wir eine letterhead.pdf speichern, die bspw. Logo, Ansprechpartner u.ä. beinhaltet.

Zusammfassung

Nach einer Anforderungsanalyse haben wir LaTeX und pandoc installiert, um die Inhalte unserer Rechnung in einer YML-Datei editieren und in Kombination mit einem LaTeX-Template und einem Makefile in ein professionell gesetztes Dokument zu verwandeln zu können.

Durch die leicht pflegbare details.yml können sowohl Mensch als auch Maschine die Rechnung leicht anpassen um eine solide Rechnung zu generieren.

Wie geht es weiter?

In Teil 2 gehen wir einen Schritt weiter und schreiben ein kleines Command-Line-Interface, um die Rechnung zu erstellen. Dabei wollen wir dem Benutzer in der Shell so viel Arbeit wie möglich abnehmen, ihm aber trotzdem volle Kontrolle geben.