4

I want to create a automatically calculating invoice environment.

The table fits to the page perfectly (of course) with tabularx. However, the calculation goes wrong since LaTeX creates the table multiple times to find out the width of the X column.

The calculations work great with tabular but then the table is fully fitting on the page depending on the font settings.

So I tried to manually calculate the width of the one variable column but it does not really work smoothly. Is there a better way to solve this?

\documentclass{scrlttr2}

\usepackage{tgheros} \renewcommand\familydefault{\sfdefault}

\RequirePackage{xspace}

% ===================================== % correct number displaying

\usepackage[mode=text, reset-text-series = false, separate-uncertainty, group-digits=integer, group-minimum-digits = 4]{siunitx}

\sisetup{group-separator = {,}, input-decimal-markers={,.}, output-decimal-marker = {,}}

\newcommand{\EURsimple}[1]{ \sisetup{minimum-decimal-digits=2, group-separator = {.}, output-decimal-marker = {,}} #1 \sisetup{minimum-decimal-digits=0, group-separator = {,}, output-decimal-marker = {,}} }

\newcommand{\EUR}[1]{ \num[minimum-decimal-digits=2, group-separator = {.}, output-decimal-marker = {,}]{#1},€ }

% ===================================== % automatic item numbering

\newcommand{\startnumberedlist}{\newcounter{numberedlistcounter}} \newcommand{\numberedlistitem}{\stepcounter{numberedlistcounter}\thenumberedlistcounter}

% ===================================== % calculation

\RequirePackage{calculator}

\newcommand{\resetinvoicesum}{\ADD{0}{0}{\invoicesum}} \newcommand{\addtoinvoicesum}[1]{\ADD{\invoicesum}{#1}{\invoicesum}\GLOBALCOPY{\invoicesum}{\invoicesum}}

% ===================================== % trying to calculate the needed width of the variable width column

\newlength{\invoicedescwidth} \setlength{\invoicedescwidth}{\textwidth}

\newlength{\invoicerestwidth} \settowidth{\invoicerestwidth}{\textbf{Pos. Anzahl Rechnungsbetrag Gesamtpreis}}

\addtolength{\invoicedescwidth}{-\invoicerestwidth} \addtolength{\invoicedescwidth}{-15ex} % <= this is not variable enough for different fonts.

% ===================================== % invoice environment

\RequirePackage{booktabs}

\newenvironment{invoice}[1][]{\startnumberedlist\resetinvoicesum\par\noindent \centering\begin{tabular}{rp{\invoicedescwidth}S[table-format=5.1]S[table-format=5.4]r}% \toprule[0.7pt]\textbf{Pos.} & \textbf{Beschreibung} & \textbf{Anzahl} & \textbf{Einzelpreis} & \textbf{Gesamtpreis} \ \toprule[0.7pt]} {\midrule[0.7pt] &&& \textbf{Rechnungsbetrag} & \textbf{\EUR{\invoicesum}} \ \cmidrule[0.7pt]{4-5} \end{tabular}% }

% invoiceitem, #1: description, #2: count, #3: price per item \newcommand{\invoiceitem}[3]{ \numberedlistitem & #1 & #2 & \EURsimple{#3} & \MULTIPLY{#2}{#3}{\tempsum} \ROUND[2]{\tempsum}{\tempsum} \addtoinvoicesum{\tempsum} \EUR{\tempsum}\} % price calculation and sum up all prices

\usepackage{Blindtext}

\begin{document} \begin{letter}{recipient} \opening{Hello}

    \Blindtext[1]

    \begin{invoice}
        \invoiceitem{Arbeitszeit in h}{800}{15}
        \invoiceitem{Schrauben}{2134}{0.1}
        \invoiceitem{Strommenge in kWh}{303.5}{0.3175}
        \invoiceitem{Position C}{2}{1123.1}
    \end{invoice}

\end{letter}

\end{document}

P.S.: I also tried tabularray but that does not seem to work with S columns of siunitx to arrange the numbers correctly at the decimal point.

mrCarnivore
  • 1,505
  • See if https://tex.stackexchange.com/questions/604087/ applying-rowcolor-to-tabularx-in-spreadtab-causes-tex-capacity-exceeded-error can help you. – Zarko Apr 22 '23 at 10:37
  • Longtable also stores the column widths in the aux file. See https://tex.stackexchange.com/questions/16251/how-to-obtain-width-of-longtable – John Kormylo Apr 22 '23 at 11:49

2 Answers2

5

The environment {NiceTabular} of nicematrix provides the ability to use X columns similar to those of tabularx. Unlike tabularx, nicematrix does not compile several times the tabular within a scratch box during a run of LaTeX but writes information on the aux file (and requires several runs of LaTeX).

\documentclass{scrlttr2}
\usepackage{nicematrix}

\usepackage{tgheros} \renewcommand\familydefault{\sfdefault}

\RequirePackage{xspace}

% ===================================== % correct number displaying

\usepackage[mode=text, reset-text-series = false, separate-uncertainty, group-digits=integer, group-minimum-digits = 4]{siunitx}

\sisetup{group-separator = {,}, input-decimal-markers={,.}, output-decimal-marker = {,}}

\newcommand{\EURsimple}[1]{ \sisetup{minimum-decimal-digits=2, group-separator = {.}, output-decimal-marker = {,}} #1 \sisetup{minimum-decimal-digits=0, group-separator = {,}, output-decimal-marker = {,}} }

\newcommand{\EUR}[1]{ \num[minimum-decimal-digits=2, group-separator = {.}, output-decimal-marker = {,}]{#1},€ }

% ===================================== % automatic item numbering

\newcommand{\startnumberedlist}{\newcounter{numberedlistcounter}} \newcommand{\numberedlistitem}{\stepcounter{numberedlistcounter}\thenumberedlistcounter}

% ===================================== % calculation

\RequirePackage{calculator}

\newcommand{\resetinvoicesum}{\ADD{0}{0}{\invoicesum}} \newcommand{\addtoinvoicesum}[1]{\ADD{\invoicesum}{#1}{\invoicesum}\GLOBALCOPY{\invoicesum}{\invoicesum}}

% ===================================== % invoice environment

\RequirePackage{booktabs}

\newenvironment{invoice}[1][]{\startnumberedlist\resetinvoicesum\par\noindent \centering\begin{NiceTabular}{rX[l]S[table-format=5.1]S[table-format=5.4]r}% \toprule[0.7pt]\textbf{Pos.} & \textbf{Beschreibung} & \textbf{Anzahl} & \textbf{Einzelpreis} & \textbf{Gesamtpreis} \ \toprule[0.7pt]} {\midrule[0.7pt] &&& \textbf{Rechnungsbetrag} & \textbf{\EUR{\invoicesum}}\ \cmidrule[0.7pt]{4-5} \end{NiceTabular}% }

% invoiceitem, #1: description, #2: count, #3: price per item \newcommand{\invoiceitem}[3]{ \numberedlistitem & #1 & #2 & \EURsimple{#3} & \MULTIPLY{#2}{#3}{\tempsum} \ROUND[2]{\tempsum}{\tempsum} \addtoinvoicesum{\tempsum} \EUR{\tempsum}\} % price calculation and sum up all prices

\usepackage{Blindtext}

\begin{document} \begin{letter}{recipient} \opening{Hello}

    \Blindtext[1]

    \begin{invoice}
        \invoiceitem{Arbeitszeit in h}{800}{15}
        \invoiceitem{Schrauben}{2134}{0.1}
        \invoiceitem{Strommenge in kWh and some other long text}{303.5}{0.3175}
        \invoiceitem{Position C}{2}{1123.1}
    \end{invoice}

\end{letter}

\end{document}

You need several compilations (several tools, such as latexmk will do for you those compilations).

Output of the above code

F. Pantigny
  • 40,250
  • Wow. Thank you! I did not know about nicematrix. That is a pretty simple solution. The only thing is: Why it the end result not aligned to the right (the same happened in my example and I cannot explain it)? – mrCarnivore Apr 22 '23 at 13:23
  • 1
    To avoid the extra space, you should not put space before the last double-backslash of en of row. I have modified my answer. – F. Pantigny Apr 22 '23 at 15:33
  • Thanks. I had not noticed that that might be causing it. – mrCarnivore Apr 22 '23 at 16:09
2

Some of the calculations in

\newlength{\invoicedescwidth}
\setlength{\invoicedescwidth}{\textwidth}
\newlength{\invoicerestwidth}
\settowidth{\invoicerestwidth}{\textbf{Pos. Anzahl Rechnungsbetrag Gesamtpreis}} 
\addtolength{\invoicedescwidth}{-\invoicerestwidth}
\addtolength{\invoicedescwidth}{-15ex}  % <= this is not variable enough for different fonts.

are suspect. E.g., why aren't you casting the text portion in \textsf? Why are there spaces in the Pos. Anzahl Rechnungsbetrag Gesamtpreis string? Where does 15ex come from? I suggest you replace the code block with

% manual calculation of required column width
\newlength\mylenA
\newlength\invoicedescwidth
\settowidth\mylenA{% some text, some numbers, no spaces, all \textsf
  \textsf{\textbf{Pos.Gesamtpreis}22\,124,511.123,3175}}
\setlength\invoicedescwidth{%
  \dimexpr\textwidth-\mylenA-8\tabcolsep\relax}

enter image description here

\documentclass{scrlttr2}

\usepackage{tgheros} \renewcommand\familydefault{\sfdefault}

\usepackage{xspace} \usepackage{booktabs} \usepackage{blindtext}

% correct number displaying \usepackage[mode=text, reset-text-series = false, separate-uncertainty, group-digits=integer, group-minimum-digits = 4 ]{siunitx} \sisetup{group-separator = {,}, input-decimal-markers={,.}, output-decimal-marker = {,}}

\newcommand{\EURsimple}[1]{ \sisetup{minimum-decimal-digits=2, group-separator = {.}, output-decimal-marker = {,}} #1 \sisetup{minimum-decimal-digits=0, group-separator = {,}, output-decimal-marker = {,}} }

\newcommand{\EUR}[1]{ \num[minimum-decimal-digits=2, group-separator = {.}, output-decimal-marker = {,}]{#1},€% }

% automatic item numbering \newcounter{numberedlistcounter} \newcommand{\resetnumberedlist}{% \setcounter{numberedlistcounter}{0}} \newcommand{\numberedlistitem}{% \stepcounter{numberedlistcounter}% \thenumberedlistcounter}

% manual calculation of required column width \newlength\mylenA \newlength\invoicedescwidth \settowidth\mylenA{%
\textsf{\textbf{Pos.Gesamtpreis}22,124,511.123,3175}} \setlength\invoicedescwidth{% \dimexpr\textwidth-\mylenA-8\tabcolsep\relax}

% calculations \RequirePackage{calculator} \newcommand{\resetinvoicesum}{\ADD{0}{0}{\invoicesum}} \newcommand{\addtoinvoicesum}[1]{\ADD{\invoicesum}{#1}{\invoicesum}\GLOBALCOPY{\invoicesum}{\invoicesum}}

% invoice environment \newenvironment{invoice}{% \resetnumberedlist\resetinvoicesum \medskip\par\noindent \begin{tabular}{@{} l >{\raggedright}p{\invoicedescwidth} S[table-format=5.1] S[table-format=5.4] r @{}}% \toprule[0.7pt] \textbf{Pos.} & \textbf{Beschreibung} & {\textbf{Anzahl}} & {\textbf{Einzelpreis}} & \textbf{Gesamtpreis} \ \midrule[0.7pt]}{% \bottomrule[0.7pt] \addlinespace \multicolumn{5}{r@{}}{% \textbf{Rechnungsbetrag}\hspace{0.75em}% \textbf{\EUR{\invoicesum}}} \ \addlinespace \end{tabular} \par }

\newcommand{\invoiceitem}[3]{ \numberedlistitem & #1 & #2 & \EURsimple{#3} & \MULTIPLY{#2}{#3}{\tempsum} \ROUND[2]{\tempsum}{\tempsum} \addtoinvoicesum{\tempsum} \EUR{\tempsum}\}

\begin{document} \begin{letter}{recipient} \opening{Hello} \Blindtext[1]

\begin{invoice}
    \invoiceitem{Arbeitszeit in h}{800}{15}
    \invoiceitem{Schrauben}{2134}{0.1}
    \invoiceitem{Strommenge in kWh}{303.5}{0.3175}
    \invoiceitem{Position C}{2}{1123.1}

\end{invoice}   
\end{letter}

\end{document}


Addendum to address one of the OP's follow-up comments: If you use a non-default sans-serif font -- especially one that uses variable-width digits -- I suggest you delay calculating the length parameters \mylenA and \invoicedescwidth until after \begin{document} and change the code block to

\newlength\mylenA
\newlength\invoicedescwidth
\settowidth\mylenA{%   
  \textsf{\textbf{Pos.Gesamtpreis}%
     \num[group-separator={\,}]{22222,2}%
     \num[group-separator={.}]{22222,2222}}}
\setlength\invoicedescwidth{%
  \dimexpr\textwidth-\mylenA-8\tabcolsep\relax}
Mico
  • 506,678
  • What does casting in \textsf do? And why did you choose the numbers: 22\,124,511.123,3175? – mrCarnivore Apr 22 '23 at 13:20
  • Thank you for the answer and re-formatting the tabular by making the Rechnungsbetrag span more columns to make the tabular look more aestetically pleasing. I had implemented something similar but I really like the line under the end sum therefore I had removed that implementation. Is there a way to keep it? – mrCarnivore Apr 22 '23 at 13:40
  • 1
    @mrCarnivore - \textsf ("text sans serife") is needed since you want to render the table in a sans-serife font (cf \renewcommand\familydefault{\sfdefault}). The numbers 22\,124,5 (incl. \, thinspace) and 11.123,3175 are the widest possible entries in columns 3 and 4. Speaking for myself, I find that underlining something as a means of emphasis tends to look vulgar -- I'd avoid it. Using bold-facing provides more than enough (maybe even too much?) emphasis as it is. – Mico Apr 22 '23 at 13:47
  • Thanks. I guessed in that direction but was confused by the , and . which I had interpreted as english punctuation marks and therefore there were 3 numbers that did not match my expectation. Your solution breaks (line is way too long) when using \usepackage[sfdefault,light]{inter} as a font instead. Strange... – mrCarnivore Apr 22 '23 at 13:47
  • @mrCarnivore - If you absolutely must resort to underlining, I suggest you run \usepackage[normalem]{ulem} \renewcommand\ULthickness{0.7pt} \setlength\ULdepth{5pt} in the preamble and execute \uline{\textbf{Rechnungsbetrag}\hspace{0.75em}\textbf{\EUR{\invoicesum}}}} in the invoice environment. – Mico Apr 22 '23 at 13:55
  • If you use a non-default sans-serif font, I suggest you delay the width calculations (of \mylenA and \invoicedescwidth until after \begin{document}. – Mico Apr 22 '23 at 13:57
  • Good idea. I added a \AddToHook{begindocument/before}{ around the calculations but there is no difference... – mrCarnivore Apr 22 '23 at 14:03
  • @mrCarnivore - Please see the addendum I just posted. – Mico Apr 22 '23 at 14:16