Rechnungen mit LaTeX und Pandoc (Teil 1)
Schritt für Schritt zum automatisierten Workflow für professionell gesetzte Dokumente.
Dieser Blog-Post ist Teil der Serie Digitalisierung unseres
Rechnung-Workflows
, in dem wir die schrittweise digitalisierte
Rechnungslegung von modevo vorstellen:
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:
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:
Pandoc können mit homebrew unter MacOS wie folgt installieren:
brew install pandoc
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:
\documentclass[11pt]{article}
\begin{document}
\section{Einleitung}
Hallo Welt \\
\end{document}
Die wir wie folgt bauen und öffnen können:
xelatex template.tex && open template.pdf
Das Ergebnis sieht dann so aus:
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
:
---
company: Beispiel GmbH
---
Der Zugriff in der TeX-Datei funktioniert so:
\documentclass[11pt]{article}
\begin{document}
\section{Einleitung}
Hallo Welt \\
$company$
\end{document}
pandoc details.yml -o test.pdf --template=template.tex --pdf-engine=xelatex
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:
---
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
---
Auf Basis einer angepassten LaTeX-Vorlage für DIN-Rechnungen:
%!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}
Mit einer Makefile
können wir den Umgang mit der Erstellung noch etwas vereinfachen:
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
Zur Erstellung der Rechnung und öffnen der PDF müssen wir dann nur noch make
aufrufen:
make
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:
%!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}
% ...
Damit können wir eine letterhead.pdf
speichern, die bspw. Logo,
Ansprechpartner u.ä. beinhaltet.
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.
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.