Difference between revisions of "Invoice template documentation"

From Freeside
Jump to: navigation, search
(Created page with "= General notes = Templates for PDF invoices are in LaTeX, with Perl inclusions in [@-- --@] blocks. Each block can insert a string at its position by either assigning the st...")
 
(Preamble)
 
(3 intermediate revisions by one other user not shown)
Line 1: Line 1:
 
= General notes =
 
= General notes =
 
 
Templates for PDF invoices are in LaTeX, with Perl inclusions in [@-- --@] blocks. Each block can insert a string at its position by either assigning the string to the $OUT variable, or simply returning a string. For details, see Text::Template.
 
Templates for PDF invoices are in LaTeX, with Perl inclusions in [@-- --@] blocks. Each block can insert a string at its position by either assigning the string to the $OUT variable, or simply returning a string. For details, see Text::Template.
  
Line 13: Line 12:
 
The template mechanics make it difficult to insert graphics other than the logo. During the rendering process, the content of the logo.eps config is written to a file with a random name, and then that name is passed as the 'logo_file' parameter. However, the \includegraphics command has access to the entire filesystem, so one option is to place static graphics somewhere under /home/freeside.
 
The template mechanics make it difficult to insert graphics other than the logo. During the rendering process, the content of the logo.eps config is written to a file with a random name, and then that name is passed as the 'logo_file' parameter. However, the \includegraphics command has access to the entire filesystem, so one option is to place static graphics somewhere under /home/freeside.
  
One useful tool for editing a template is Overleaf (https://www.overleaf.com/), which is a realtime LaTeX editor. This will require you to upload the logo EPS file with the correct randomly generated name (or edit the references to it) along with the modified longtable.sty file. Also, since it can't evaluate the Perl blocks, you have to work with the generated code rather than the template itself, and then manually edit your changes back into the template. It's still much easier than the cycle of "edit template, set config, download and examine PDF", especially since it will display LaTeX errors in a semi-useful way.
+
One useful tool for editing a template is Overleaf ([https://www.overleaf.com/ https://www.overleaf.com/]), which is a realtime LaTeX editor. This will require you to upload the logo EPS file with the correct randomly generated name (or edit the references to it) along with the modified longtable.sty file. Also, since it can't evaluate the Perl blocks, you have to work with the generated code rather than the template itself, and then manually edit your changes back into the template. It's still much easier than the cycle of "edit template, set config, download and examine PDF", especially since it will display LaTeX errors in a semi-useful way.
  
 
== Positioning ==
 
== Positioning ==
 
 
This is not used in the standard template but has been used in custom templates. Often in an invoice there's a need to place something at a known position on the page, like a return address that needs to line up with an envelope window. LaTeX isn't designed to facilitate that but LaTeX can do absolutely anything, so: the "textpos" package.
 
This is not used in the standard template but has been used in custom templates. Often in an invoice there's a need to place something at a known position on the page, like a return address that needs to line up with an envelope window. LaTeX isn't designed to facilitate that but LaTeX can do absolutely anything, so: the "textpos" package.
  
Line 25: Line 23:
 
  \setlength{\TPVertModule}{1in}
 
  \setlength{\TPVertModule}{1in}
  
The environment for a fixed position on the page is "textblock". The syntax is odd; notice parentheses rather than braces, and numbers without units.
+
The environment for a fixed position on the page is "textblock". The syntax is odd; notice parentheses rather than braces, and numbers without units (because the \setlength commands defined the units).
  
 
  \begin{textblock}{3.25}(0.625, 0.5)
 
  \begin{textblock}{3.25}(0.625, 0.5)
Line 37: Line 35:
  
 
== The standard invoice template ==
 
== The standard invoice template ==
 
 
=== Preamble ===
 
=== Preamble ===
 
 
Set up the document class and paper size, load all packages we need. In general, all \usepackage commands should be at the start of the document.
 
Set up the document class and paper size, load all packages we need. In general, all \usepackage commands should be at the start of the document.
  
Line 46: Line 42:
 
* "ifthen" provides the \ifthenelse command.
 
* "ifthen" provides the \ifthenelse command.
 
* "array" provides support for custom table column formats.
 
* "array" provides support for custom table column formats.
* "longtable" provides tables that can span multiple pages. We ship a modified longtable.sty, which reserves vertical space on the first page to allow for the coupon. The amount of vertical space is in the "\LTcouponspace" variable.
+
* "longtable" provides tables that can span multiple pages. We ship a modified longtable.sty, which reserves vertical space on the first page to allow for the coupon. The amount of vertical space is in the "\LTextracouponspace" variable.
 
* "afterpage" was removed from the template in May 2005 and we can remove the package at some point.
 
* "afterpage" was removed from the template in May 2005 and we can remove the package at some point.
 
* "multirow" is used to make a table cell in the coupon to hold the return address.
 
* "multirow" is used to make a table cell in the coupon to hold the return address.
Line 63: Line 59:
  
 
  \documentclass[letterpaper]{article}
 
  \documentclass[letterpaper]{article}
+
 
 
  \usepackage{fancyhdr,lastpage,ifthen,array,longtable,afterpage,caption,multirow,bigstrut}
 
  \usepackage{fancyhdr,lastpage,ifthen,array,longtable,afterpage,caption,multirow,bigstrut}
 
  \usepackage[breakwords]{truncate} % to avoid overflowing boxes
 
  \usepackage[breakwords]{truncate} % to avoid overflowing boxes
Line 80: Line 76:
 
  }';
 
  }';
 
  }
 
  }
  '';
+
  ''<nowiki>;</nowiki>
 
  --@]
 
  --@]
  
 
Set up dimensions. Most of these were determined empirically and really ought to use the "geometry" package. A few of the dimensions can be adjusted by config variables: \topmargin, \headsep, \textheight.
 
Set up dimensions. Most of these were determined empirically and really ought to use the "geometry" package. A few of the dimensions can be adjusted by config variables: \topmargin, \headsep, \textheight.
  
See [https://en.wikibooks.org/wiki/LaTeX/Page_Layout] and [http://mirrors.ctan.org/macros/latex/base/letter.pdf texdoc letter] for how LaTeX thinks a letter-class page is structured. Note that everything is laid out statically from the top of the page. The header starts at (1in + \voffset + \topmargin), and is placed in a box with height \headheight, and then the document body starts at (1in + \voffset + \topmargin + \headheight + \headsep), and then the footer starts (\textheight + \footskip) below that.
+
See [https://en.wikibooks.org/wiki/LaTeX/Page_Layout LaTeX Page Layout] and [http://mirrors.ctan.org/macros/latex/base/letter.pdf texdoc letter] for how LaTeX thinks a letter-class page is structured. Note that everything is laid out statically from the top of the page. The header starts at (1in + \voffset + \topmargin), and is placed in a box with height \headheight, and then the document body starts at (1in + \voffset + \topmargin + \headheight + \headsep), and then the footer starts (\textheight + \footskip) below that.
  
 
If the header doesn't fit within \headheight, LaTeX will complain, and then adjust the headheight, pushing everything else down. There's also not actually any space reserved for the footer; it has to fit into the space left below the body container.
 
If the header doesn't fit within \headheight, LaTeX will complain, and then adjust the headheight, pushing everything else down. There's also not actually any space reserved for the footer; it has to fit into the space left below the body container.
Line 101: Line 97:
 
  \setlength{\headsep}{[@-- defined($headsep) ? $headsep : '1.0cm' --@]}
 
  \setlength{\headsep}{[@-- defined($headsep) ? $headsep : '1.0cm' --@]}
 
  \setlength{\footskip}{1.0cm}    % bottom of footer from bottom of text
 
  \setlength{\footskip}{1.0cm}    % bottom of footer from bottom of text
+
 
 
  %\addtolength{\textwidth}{2.1in}      % width of text
 
  %\addtolength{\textwidth}{2.1in}      % width of text
 
  \setlength{\textwidth}{19.5cm}
 
  \setlength{\textwidth}{19.5cm}
Line 107: Line 103:
 
  \setlength{\oddsidemargin}{-0.9cm}  % odd page left margin
 
  \setlength{\oddsidemargin}{-0.9cm}  % odd page left margin
 
  \setlength{\evensidemargin}{-0.9cm}  % even page left margin
 
  \setlength{\evensidemargin}{-0.9cm}  % even page left margin
+
 
 
  \LTchunksize=40
 
  \LTchunksize=40
  
$coupon is a local variable containing the entire generated content of the [[Invoice_coupon_template|invoice payment coupon]]. If for some reason there isn't a payment coupon (the customer doesn't owe any money, or this is a quotation or statement rather than an invoice) then it's an empty string.
+
$coupon is a local variable containing the entire generated content of the [#Coupon_template invoice payment coupon]. If for some reason there isn't a payment coupon (the customer doesn't owe any money, or this is a quotation or statement rather than an invoice) then it's an empty string.
  
 
\footrule is the macro used to draw a line at the top of the footer. Here, we redefine it so that it doesn't appear on the first page if there's a payment coupon (because the coupon will make its own line, at a different position).
 
\footrule is the macro used to draw a line at the top of the footer. Here, we redefine it so that it doesn't appear on the first page if there's a payment coupon (because the coupon will make its own line, at a different position).
  
\extracouponspace is a config setting for how much space to reserve for the coupon. We will "shorten" the first page's \textheight by that much, which moves the starting position of the header up.
+
$couponlocation is config option that sets where the coupon should be located (top or bottom), this defaults to bottom.
 +
 
 +
\extracouponspace is a config setting for how much space to reserve for the coupon. We will "shorten" the first page's content by that much, which moves the starting position of the header up or down depending on coupon location.
  
 
  \renewcommand{\headrulewidth}{0pt}
 
  \renewcommand{\headrulewidth}{0pt}
 
  \renewcommand{\footrulewidth}{1pt}
 
  \renewcommand{\footrulewidth}{1pt}
+
 
 
  \renewcommand{\footrule}{
 
  \renewcommand{\footrule}{
 
  [@--
 
  [@--
   $coupon ? '\ifthenelse{\equal{\thepage}{1}}' : '';
+
   $coupon ? '\ifthenelse{\equal{\thepage}{1}}' : ''<nowiki>;</nowiki>''
 
  --@]
 
  --@]
 
   {
 
   {
Line 129: Line 127:
 
   }
 
   }
 
  }
 
  }
 +
\newcommand{\extracouponspace}{[@-- defined($extracouponspace) ? $extracouponspace : '2.7in' --@]}
  
\newcommand{\extracouponspace}{[@-- defined($extracouponspace) ? $extracouponspace : '2.7in' --@]}
+
Positioning mailing addresses. In current code I'd recommend using the "textpos" package instead. Also a command here to output the dollar sign (since '$' has special properties in LaTeX). The escaping functions in Template_Mixin.pm know about this, and will substitute \dollar throughout.
  
Positioning mailing addresses. This is a bad way to do it and I'd recommend using the "textpos" package instead. Also a command here to output the dollar sign (since '$' has special properties in LaTeX). The escaping functions in Template_Mixin.pm know about this, and will substitute \dollar throughout.
 
 
 
  % Adjust the inset of the mailing address
 
  % Adjust the inset of the mailing address
 
  \newcommand{\addressinset}[1][]{\hspace{1.0cm}}
 
  \newcommand{\addressinset}[1][]{\hspace{1.0cm}}
+
 
 
  % Adjust the inset of the return address and logo
 
  % Adjust the inset of the return address and logo
 
  \newcommand{\returninset}[1][]{\hspace{-0.25cm}}
 
  \newcommand{\returninset}[1][]{\hspace{-0.25cm}}
+
 
 
  % New command for address lines i.e. skip them if blank
 
  % New command for address lines i.e. skip them if blank
 
  \newcommand{\addressline}[1]{\ifthenelse{\equal{#1}{}}{}{#1\\}}
 
  \newcommand{\addressline}[1]{\ifthenelse{\equal{#1}{}}{}{#1\\}}
+
 
 
  % Inserts dollar symbol
 
  % Inserts dollar symbol
 
  \newcommand{\dollar}[1][]{\symbol{36}}
 
  \newcommand{\dollar}[1][]{\symbol{36}}
  
 
=== Headers/footers ===
 
=== Headers/footers ===
 
 
"fancyhdr" allows separate left, right, and center headers and footers (the first argument to \fancyhead or \fancyfoot). We use left and right headers and a center footer.
 
"fancyhdr" allows separate left, right, and center headers and footers (the first argument to \fancyhead or \fancyfoot). We use left and right headers and a center footer.
  
Line 160: Line 156:
 
The center footer. On page 1, if there's a coupon, the coupon is the footer. In that case, we move the start position up by \extracouponspace (using \vspace) so that it starts above the normal footer position.
 
The center footer. On page 1, if there's a coupon, the coupon is the footer. In that case, we move the start position up by \extracouponspace (using \vspace) so that it starts above the normal footer position.
  
The footer to display below the coupon (or if there isn't one) is configurable as '''invoice_latexfooter'''. The footer to show on subsequent pages is '''invoice_latexsmallfooter'''. By default they're both just the company name.
+
The footer to display below the coupon (or if there isn't one) is configurable as '''invoice_latexfooter'''. The footer to show on subsequent pages is '''invoice_latexsmallfooter'''. By default they're both just the company name.
  
 
  % Define fancy header/footer for first and subsequent pages
 
  % Define fancy header/footer for first and subsequent pages
Line 175: Line 171:
 
       '}';
 
       '}';
 
   }
 
   }
   '';
+
   ''<nowiki>;</nowiki>''
 
  --@] [@-- $smallerfooter ? '\scriptsize{' : '\small{' --@]
 
  --@] [@-- $smallerfooter ? '\scriptsize{' : '\small{' --@]
 
  [@-- $footer --@]
 
  [@-- $footer --@]
Line 187: Line 183:
 
  }
 
  }
  
The right side footer, showing the page number. "emt()" localizes text to the customer's language and escapes for LaTeX. "~" is a non-breaking space character.
+
The right side footer, showing the page number. "emt()" localizes text to the customer's language and then escapes it for LaTeX. "~" is a non-breaking space character. It's suppressed on the first page due to the coupon.
  
 
  \fancyfoot[R]{
 
  \fancyfoot[R]{
Line 205: Line 201:
  
 
The second column contains the logo graphic. See "texdoc graphicx" for a full explanation of this command; it can do many useful things including scaling and repositioning the graphic. For example, if you want the logo to be scaled to be 0.5 inch tall, you can do this: "\includegraphics[height=0.5in]{[@-- $logo_file --@]}".
 
The second column contains the logo graphic. See "texdoc graphicx" for a full explanation of this command; it can do many useful things including scaling and repositioning the graphic. For example, if you want the logo to be scaled to be 0.5 inch tall, you can do this: "\includegraphics[height=0.5in]{[@-- $logo_file --@]}".
+
 
 
  \fancyhead[L]{
 
  \fancyhead[L]{
 
   \ifthenelse{\equal{\thepage}{1}}
 
   \ifthenelse{\equal{\thepage}{1}}
Line 238: Line 234:
 
   { % First page
 
   { % First page
 
     \begin{tabular}{ccc}
 
     \begin{tabular}{ccc}
     [@-- join(' & ', ( $no_date  ? '' : emt('Invoice date') ),
+
     [@-- join(' & ', ( $no_date  ? '': emt('Invoice date') ),''
                       ( $no_number ? '' : emt('Invoice #')    ),
+
                       ( $no_number ? '': emt('Invoice #')    ),''
 
                       emt('Customer #')
 
                       emt('Customer #')
 
               )
 
               )
Line 263: Line 259:
  
 
  \pagestyle{fancy}
 
  \pagestyle{fancy}
 
 
  %% Font options are:
 
  %% Font options are:
 
  %%  bch Bitsream Charter
 
  %%  bch Bitsream Charter
Line 271: Line 266:
 
  %%  ptm Times
 
  %%  ptm Times
 
  %%  pcr Courier
 
  %%  pcr Courier
+
 
 
  \renewcommand{\familydefault}{phv}
 
  \renewcommand{\familydefault}{phv}
  
 
=== Line item table macros ===
 
=== Line item table macros ===
 +
This section is Freeside-specific. The main invoice line item table is an 8-column longtable. Normally the columns are:
  
This section is Freeside-specific. The main invoice line item table is an 8-column longtable. Normally the columns are:
 
 
* Package number
 
* Package number
 
* Description (6 columns)
 
* Description (6 columns)
Line 284: Line 279:
  
 
If '''invoice-unitprice''' is on, it's:
 
If '''invoice-unitprice''' is on, it's:
 +
 
* Package number
 
* Package number
 
* Description (4 columns)
 
* Description (4 columns)
Line 293: Line 289:
  
 
  % Commands for freeside table header...
 
  % Commands for freeside table header...
+
 
 
  \newcommand{\FSdescriptionlength} { [@-- $unitprices ? '8.2cm' : '12.8cm' --@] }
 
  \newcommand{\FSdescriptionlength} { [@-- $unitprices ? '8.2cm' : '12.8cm' --@] }
 
  \newcommand{\FSdescriptioncolumncount} { [@-- $unitprices ? '4' : '6' --@] }
 
  \newcommand{\FSdescriptioncolumncount} { [@-- $unitprices ? '4' : '6' --@] }
  \newcommand{\FSunitcolumns}{ [@--  
+
  \newcommand{\FSunitcolumns}{ [@--
 
   $unitprices
 
   $unitprices
 
   ? '\makebox[2.5cm][r]{\textbf{~~' . emt('Unit Price') . '}} &' .
 
   ? '\makebox[2.5cm][r]{\textbf{~~' . emt('Unit Price') . '}} &' .
     '\makebox[1.4cm]{\textbf{~' . emt('Quantity') . '}} & '  
+
     '\makebox[1.4cm]{\textbf{~' . emt('Quantity') . '}} & '
   : '' --@] }
+
   : ''--@] }''
  
 
\FShead renders the main table header. Using \makebox inside a column forces it to a minimum width. \multicolumn{N}{X}{ ... } is like COLSPAN in HTML: makes a cell spanning N columns. The second argument is how to align the contents of the column (l, r, or c).
 
\FShead renders the main table header. Using \makebox inside a column forces it to a minimum width. \multicolumn{N}{X}{ ... } is like COLSPAN in HTML: makes a cell spanning N columns. The second argument is how to align the contents of the column (l, r, or c).
Line 319: Line 315:
 
   \hline
 
   \hline
 
  }
 
  }
+
 
 
  \newcommand{\FSusagehead}{
 
  \newcommand{\FSusagehead}{
 
   \hline
 
   \hline
Line 339: Line 335:
 
\FSextdesc will be called once for each element in the 'ext_description' element of the detail item. These contain things like service labels, discount details, prorate details, and manually created package details. This macro takes a single argument and places it in a big multicolumn below the description, in small font.
 
\FSextdesc will be called once for each element in the 'ext_description' element of the detail item. These contain things like service labels, discount details, prorate details, and manually created package details. This macro takes a single argument and places it in a big multicolumn below the description, in small font.
  
\FScalldetail is used only for call details. These are preprocessed by FS::TemplateItem_Mixin::details() ''into LaTeX table rows'' (pre-escaped, delimited with &amp;). So, the macro just skips the first column and outputs its argument.
+
\FScalldetail is used only for call details. These are preprocessed by FS::TemplateItem_Mixin::details() into LaTeX table rows (pre-escaped, delimited with &). So, the macro just skips the first column and outputs its argument.
  
 
\FStotaldesc is used for subtotal and total rows; it puts its first argument in a big multicolumn in the description space, and the second in the amount column.
 
\FStotaldesc is used for subtotal and total rows; it puts its first argument in a big multicolumn in the description space, and the second in the amount column.
Line 353: Line 349:
 
  [@-- $unitprices ? '  \multicolumn{1}{r}{\textbf{#3}} &'."\n".
 
  [@-- $unitprices ? '  \multicolumn{1}{r}{\textbf{#3}} &'."\n".
 
                     '  \multicolumn{1}{r}{\textbf{#4}} &'."\n"
 
                     '  \multicolumn{1}{r}{\textbf{#4}} &'."\n"
                   : ''
+
                   :
 
  --@]
 
  --@]
 
   \multicolumn{1}{r}{\textbf{#5}}\\
 
   \multicolumn{1}{r}{\textbf{#5}}\\
Line 361: Line 357:
 
   \multicolumn{1}{l}{\rule{0pt}{1.0ex}} &
 
   \multicolumn{1}{l}{\rule{0pt}{1.0ex}} &
 
   \multicolumn{6}{l}{
 
   \multicolumn{6}{l}{
     \truncate{12.8cm}{\small{[[User:Mark|Mark]] ([[User talk:Mark|talk]])#1}}
+
     \truncate{12.8cm}{\small{[[User:Mark|Mark]] ([[User talk:Mark|talk]])<nowiki>#1}}</nowiki>
 
   } \\
 
   } \\
 
  }
 
  }
Line 367: Line 363:
 
  \newcommand{\FScalldetail}[1]{
 
  \newcommand{\FScalldetail}[1]{
 
   \multicolumn{1}{l}{\rule{0pt}{1.0ex}} &
 
   \multicolumn{1}{l}{\rule{0pt}{1.0ex}} &
   [[User:Mark|Mark]] ([[User talk:Mark|talk]])#1
+
   [[User:Mark|Mark]] ([[User talk:Mark|talk]])<nowiki>#1</nowiki>
 
   \\
 
   \\
 
  }
 
  }
Line 378: Line 374:
 
   } & #2\\
 
   } & #2\\
 
  }
 
  }
 
 
  % ...usage class summary
 
  % ...usage class summary
 
  \newcommand{\FSusagedesc}[4]{
 
  \newcommand{\FSusagedesc}[4]{
Line 390: Line 385:
  
 
=== Main document ===
 
=== Main document ===
 
 
First, make a minipage for the customer's name and address. This starts at \addressinset + 5 mm (not sure why) and can be up to 7 cm wide. The \makebox does nothing (as above, a minipage is already a single box). The \addressline macro makes a line break only if the argument is non-empty, to avoid errors for breaking an empty line.
 
First, make a minipage for the customer's name and address. This starts at \addressinset + 5 mm (not sure why) and can be up to 7 cm wide. The \makebox does nothing (as above, a minipage is already a single box). The \addressline macro makes a line break only if the argument is non-empty, to avoid errors for breaking an empty line.
  
 
  \begin{document}
 
  \begin{document}
 
  % Headers and footers defined for the first page
 
  % Headers and footers defined for the first page
  \addressinset \rule{0.5cm}{0cm}  
+
  \addressinset \rule{0.5cm}{0cm}
 
  \makebox{
 
  \makebox{
 
  \begin{minipage}[t]{7.0cm}
 
  \begin{minipage}[t]{7.0cm}
Line 426: Line 420:
 
     $OUT .= '~\\\\';
 
     $OUT .= '~\\\\';
 
   }else{
 
   }else{
     $OUT .= '';
+
     $OUT .= ''<nowiki>;</nowiki>''
 
   }
 
   }
 
  --@]
 
  --@]
 
  \begin{flushright}
 
  \begin{flushright}
  [@-- $terms ? emt('Terms') . ': ' . emt($terms) : '' --@]\\
+
  [@-- $terms ? emt('Terms') . ': ' . emt($terms) : ''--@]\\''
 
  [@-- $po_line --@]\\
 
  [@-- $po_line --@]\\
 
  \end{flushright}
 
  \end{flushright}
Line 436: Line 430:
 
  \vspace{1.5cm}
 
  \vspace{1.5cm}
  
Insert the [[Summary]] if there is one. The last thing in the summary template is a page break.
+
Insert the [http://www.freeside.biz/mediawiki/index.php?title=Summary&action=edit&redlink=1 Summary] if there is one. The last thing in the summary template is a page break.
 +
 
 
  %
 
  %
 
  [@-- $summary --@]
 
  [@-- $summary --@]
Line 442: Line 437:
  
 
=== Sections ===
 
=== Sections ===
 
 
Start creating sections, according to the @sections array. Section entries contain the following keys:
 
Start creating sections, according to the @sections array. Section entries contain the following keys:
  
Line 454: Line 448:
  
 
and optionally some flags:
 
and optionally some flags:
 +
 
* post_total (put the section at the end of the invoice)
 
* post_total (put the section at the end of the invoice)
 
* summarized (omit the section entirely)
 
* summarized (omit the section entirely)
Line 472: Line 467:
 
  [@--
 
  [@--
 
   foreach my $section ( grep { !$summary || $_->{description} ne $finance_section } @sections ) {
 
   foreach my $section ( grep { !$summary || $_->{description} ne $finance_section } @sections ) {
+
 
 
We're going to do many things if "!$summary". If there's a summary section, the pretotals and posttotals are redundant so skip them.
 
We're going to do many things if "!$summary". If there's a summary section, the pretotals and posttotals are redundant so skip them.
  
Line 555: Line 550:
 
Single-section invoices use "@total_items" for items that need to appear after the line items. These include the pre-tax subtotal, taxes, total charges, adjustments, and the balance due. Call \FStotaldesc for each of them. Then end the section foot.
 
Single-section invoices use "@total_items" for items that need to appear after the line items. These include the pre-tax subtotal, taxes, total charges, adjustments, and the balance due. Call \FStotaldesc for each of them. Then end the section foot.
  
       #if ($section == $sections[$#sections]) {
+
       <nowiki>#if ($section == $sections[$#sections]) {</nowiki>
 
         foreach my $line (grep {$_->{section}->{description} eq $section->{description}} @total_items) {
 
         foreach my $line (grep {$_->{section}->{description} eq $section->{description}} @total_items) {
 
           if ($section->{total_line_generator}) {
 
           if ($section->{total_line_generator}) {
Line 564: Line 559:
 
           }
 
           }
 
         }
 
         }
       #}
+
       <nowiki>#}</nowiki>
 
 
 
       $OUT .= '\hline';
 
       $OUT .= '\hline';
 
       $OUT .= '\endlastfoot';
 
       $OUT .= '\endlastfoot';
Line 589: Line 583:
 
       my $lastref = 0;
 
       my $lastref = 0;
 
       foreach my $line (
 
       foreach my $line (
         grep { ( scalar( @sections ) > 1  
+
         grep { ( scalar( @sections ) > 1
 
               ? $section->{'description'} eq $_->{'section'}->{'description'}
 
               ? $section->{'description'} eq $_->{'section'}->{'description'}
 
               : 1
 
               : 1
Line 596: Line 590:
 
       {
 
       {
 
         my $ext_description = $line->{'ext_description'};
 
         my $ext_description = $line->{'ext_description'};
 
+
 
         # Don't break-up small packages.
+
         <nowiki># Don't break-up small packages.</nowiki>
         my $rowbreak = @$ext_description < 5 ? '*' : '';
+
         my $rowbreak = @$ext_description < 5 ? '*' : ''<nowiki>;</nowiki>''
  
 
$lastref is set to the pkgnum of the previous line item. If the value of 'ref' no longer equals that, we're now showing a different package; separate with an \hline. After outputting this line item we'll set $lastref to the new pkgnum.
 
$lastref is set to the pkgnum of the previous line item. If the value of 'ref' no longer equals that, we're now showing a different package; separate with an \hline. After outputting this line item we'll set $lastref to the new pkgnum.
Line 624: Line 618:
 
                   '{' . $line->{'description'} . '}' ;
 
                   '{' . $line->{'description'} . '}' ;
 
           if ( $unitprices and length($line->{'unit_amount'}) ) {
 
           if ( $unitprices and length($line->{'unit_amount'}) ) {
             # then show the unit amount and quantity
+
             <nowiki># then show the unit amount and quantity</nowiki>
             $OUT .=  
+
             $OUT .=
 
                 '{\\dollar' . $line->{'unit_amount'} . '}'.
 
                 '{\\dollar' . $line->{'unit_amount'} . '}'.
 
                 '{'        . $line->{'quantity'}    . '}';
 
                 '{'        . $line->{'quantity'}    . '}';
 
           } else {
 
           } else {
             # leave those columns blank
+
             <nowiki># leave those columns blank</nowiki>
 
             $OUT .= '{}{}';
 
             $OUT .= '{}{}';
 
           }
 
           }
Line 636: Line 630:
 
         $lastref = $line->{'ref'} || 0;
 
         $lastref = $line->{'ref'} || 0;
  
Output ext_description lines. We assume that any of them that contain an unescaped "&amp;" are call details and should use \FScalldetail. Otherwise they use \FSextdesc.
+
Output ext_description lines. We assume that any of them that contain an unescaped "&" are call details and should use \FScalldetail. Otherwise they use \FSextdesc.
  
 
         foreach my $ext_desc (@$ext_description) {
 
         foreach my $ext_desc (@$ext_description) {
Line 647: Line 641:
 
           }
 
           }
 
         }
 
         }
 
 
       }
 
       }
  
Line 662: Line 655:
  
 
=== Notes ===
 
=== Notes ===
 
 
For the notes panel, create a minipage the full width of the text. (Unless $summary is in use; then the notes were on the summary page.)
 
For the notes panel, create a minipage the full width of the text. (Unless $summary is in use; then the notes were on the summary page.)
  
 
There's some tricky handling of vertical space here. We want the notes at the bottom of the page, regardless of where the last section ended, so the \vfill between them acts as an expandable space.
 
There's some tricky handling of vertical space here. We want the notes at the bottom of the page, regardless of where the last section ended, so the \vfill between them acts as an expandable space.
  
However, if the notes end up on the first page, we have to prevent them from overprinting the coupon. So if there's a coupon and the notes are on page 1, we insert a strut of height \extracouponspace below the notes. The \vfill will then expand so that the bottom of the ''strut'' is at the bottom of the page, keeping the coupon space clear.
+
However, if the notes end up on the first page, we have to prevent them from overprinting the coupon. So if there's a coupon and the notes are on page 1, we insert a strut of height \extracouponspace below the notes. The \vfill will then expand so that the bottom of the strut is at the bottom of the page, keeping the coupon space clear.
  
 
  --@]
 
  --@]
Line 673: Line 665:
 
  \begin{minipage}[t]{\textwidth}
 
  \begin{minipage}[t]{\textwidth}
 
   [@-- length($summary)
 
   [@-- length($summary)
           ? ''
+
           ?
 
         : ( $smallernotes
 
         : ( $smallernotes
 
               ? '\scriptsize{ '.$notes.' }'
 
               ? '\scriptsize{ '.$notes.' }'
Line 679: Line 671:
 
           )
 
           )
 
   --@]
 
   --@]
   [@-- $coupon ? '\ifthenelse{\equal{\thepage}{1}}{\rule{0pt}{\extracouponspace}}{}' : '' --@]
+
   [@-- $coupon ? '\ifthenelse{\equal{\thepage}{1}}{\rule{0pt}{\extracouponspace}}{}' : ''--@]''
 
  \end{minipage}
 
  \end{minipage}
 
  \end{document}
 
  \end{document}
Line 686: Line 678:
  
 
== The summary template ==
 
== The summary template ==
 
 
Start with a two-column table, left side for the notes and right side for the billing summary. On the left side, make a 6.4cm minipage, and put a 10cm vertical strut there, along with the notes.
 
Start with a two-column table, left side for the notes and right side for the billing summary. On the left side, make a 6.4cm minipage, and put a 10cm vertical strut there, along with the notes.
  
Line 722: Line 713:
 
  &\\
 
  &\\
  
@summary_subtotals contains one line for each invoice section, except for the sections that have special handling such as the adjustments and finance charges. Make a line for each of those subtotals. This is followed with \cline{2-2}, which puts a horizontal line under the second column only, and then a row for the total of new charges.
+
@summary_subtotals contains one line for each invoice section, except for the sections that have special handling such as the adjustments and finance charges. Make a line for each of those subtotals. \cline draws a horizontal line (like \hline) but only under specified columns: here, only the second column. After that, make rows for the total new charges (minus finance charges), the total amount owed on previous invoices ('true_previous_balance' – 'balance_adjustments'), and the finance charges.
  
 
  [@--
 
  [@--
Line 741: Line 732:
 
  \textbf{New Charges}&\textbf{\dollar[@-- $current_less_finance --@]}\\
 
  \textbf{New Charges}&\textbf{\dollar[@-- $current_less_finance --@]}\\
  
Then show the subtotal of the adjustment section, if there is one. This contains payments and credits applied to the ''current'' invoice.
+
Then show the subtotal of the adjustment section, if there is one. This contains payments and credits applied to the ''current'' invoice (not past invoices).
  
 
  [@--
 
  [@--
   #false laziness w/invoice_htmlsummary and above
+
   <nowiki>#false laziness w/invoice_htmlsummary and above</nowiki>
 
   foreach my $section ( grep $_->{adjust_section}, @sections ) {
 
   foreach my $section ( grep $_->{adjust_section}, @sections ) {
 
     $OUT .= '\textbf{'. ($section->{'description'} ? $section->{'description'} : 'Charges' ). '}';
 
     $OUT .= '\textbf{'. ($section->{'description'} ? $section->{'description'} : 'Charges' ). '}';

Latest revision as of 06:17, 20 February 2019

General notes

Templates for PDF invoices are in LaTeX, with Perl inclusions in [@-- --@] blocks. Each block can insert a string at its position by either assigning the string to the $OUT variable, or simply returning a string. For details, see Text::Template.

The main function for generating the LaTeX code is FS::Template_Mixin::print_generic. This is a method of the cust_bill object (and also of quotation objects), and takes several arguments described in the perldoc. Most of what this function does is set up the "%invoice_data" hash, which gets imported into local variable space when evaluating the template.

print_generic() actually constructs several templates: 'notes', 'coupon', 'footer', 'smallfooter', 'watermark', and sometimes 'summary'. Exactly which config variable is loaded for each of these is decided in Template_Mixin; look for the line that starts "my $tc". It's invoice_$format$part (where $format is either "latex" or "html" and $part is "notes", "coupon", etc.). Each template is evaluated, with %invoice_data as arguments, then put back into %invoice_data as "$notes", "$coupon", etc. The main template contains code fragments to include them.

To further complicate things, all of these elements have locale overrides AND invoice mode overrides. If print_generic() was called with a 'mode' argument, those templates will be fetched from the invoice mode + customer locale. If not, they'll be taken from the system config for the customer's agent and locale.

You can obtain the generated LaTeX code for a particular invoice with view/cust_bill-tex.cgi, which takes all the same parameters as view/cust_bill.cgi.

The template mechanics make it difficult to insert graphics other than the logo. During the rendering process, the content of the logo.eps config is written to a file with a random name, and then that name is passed as the 'logo_file' parameter. However, the \includegraphics command has access to the entire filesystem, so one option is to place static graphics somewhere under /home/freeside.

One useful tool for editing a template is Overleaf (https://www.overleaf.com/), which is a realtime LaTeX editor. This will require you to upload the logo EPS file with the correct randomly generated name (or edit the references to it) along with the modified longtable.sty file. Also, since it can't evaluate the Perl blocks, you have to work with the generated code rather than the template itself, and then manually edit your changes back into the template. It's still much easier than the cycle of "edit template, set config, download and examine PDF", especially since it will display LaTeX errors in a semi-useful way.

Positioning

This is not used in the standard template but has been used in custom templates. Often in an invoice there's a need to place something at a known position on the page, like a return address that needs to line up with an envelope window. LaTeX isn't designed to facilitate that but LaTeX can do absolutely anything, so: the "textpos" package.

"textpos" requires you to set up a grid system, like this:

\usepackage[absolute]{textpos}
\setlength{\TPHorizModule}{1in}
\setlength{\TPVertModule}{1in}

The environment for a fixed position on the page is "textblock". The syntax is odd; notice parentheses rather than braces, and numbers without units (because the \setlength commands defined the units).

\begin{textblock}{3.25}(0.625, 0.5)
  \footnotesize{
    This text is in a box starting 0.625in left and 0.5in down from the page
    origin, in a box 3.25in wide. It will fit inside an envelope window.
  }
\end{textblock}

The textblock behaves a lot like a minipage, and can contain almost anything; in particular, \includegraphics works. It's positioned after everything else on the page, which means there's no protection against overprinting anything.

The standard invoice template

Preamble

Set up the document class and paper size, load all packages we need. In general, all \usepackage commands should be at the start of the document.

  • "fancyhdr" supplies the \fancyhead and \fancyfoot commands to place left, right, and center headers and footers.
  • "lastpage" creates the LastPage reference for displaying "page X of Y" labels.
  • "ifthen" provides the \ifthenelse command.
  • "array" provides support for custom table column formats.
  • "longtable" provides tables that can span multiple pages. We ship a modified longtable.sty, which reserves vertical space on the first page to allow for the coupon. The amount of vertical space is in the "\LTextracouponspace" variable.
  • "afterpage" was removed from the template in May 2005 and we can remove the package at some point.
  • "multirow" is used to make a table cell in the coupon to hold the return address.
  • "bigstrut" appears not to be used.
  • "truncate" provides a command to apply a width limit to a line of text, so that anything past that gets replaced with an ellipsis. This is highly encouraged for things like package descriptions that could otherwise overflow their table cells.
  • "graphicx" provides the \includegraphics command for inserting the EPS of the logo.
  • "inputenc" and "fontenc" make LaTeX play nicely with UTF-8 characters.
  • "background" is used to create watermarks (such as are used for some past-due notices, or voided invoices). The \backgroundsetup command takes a TikZ argument list, which is extremely powerful. See "texdoc pgf" for all the horrifying details.

Useful things to add here:

  • "textpos": see above.
  • "geometry" to manage the margins and headers in a centralized way. It also takes the [showframe] option, which will draw lines to visualize the page layout.
  • "xcolor" if you want color.
  • "tabularx" and/or "tabulary" add more options for setting table column widths.
\documentclass[letterpaper]{article}
\usepackage{fancyhdr,lastpage,ifthen,array,longtable,afterpage,caption,multirow,bigstrut}
\usepackage[breakwords]{truncate} % to avoid overflowing boxes
\usepackage{graphicx}     % required for logo graphic
\usepackage[utf8]{inputenc}             % multilanguage support
\usepackage[T1]{fontenc}
[@-- if ( length($watermark) ) {
  $OUT .= '
\usepackage{background}
\backgroundsetup{
  placement=center,
  opacity=0.25,
  color=black,
  angle=0,
  contents=' . $watermark . '
}';
}
;
--@]

Set up dimensions. Most of these were determined empirically and really ought to use the "geometry" package. A few of the dimensions can be adjusted by config variables: \topmargin, \headsep, \textheight.

See LaTeX Page Layout and texdoc letter for how LaTeX thinks a letter-class page is structured. Note that everything is laid out statically from the top of the page. The header starts at (1in + \voffset + \topmargin), and is placed in a box with height \headheight, and then the document body starts at (1in + \voffset + \topmargin + \headheight + \headsep), and then the footer starts (\textheight + \footskip) below that.

If the header doesn't fit within \headheight, LaTeX will complain, and then adjust the headheight, pushing everything else down. There's also not actually any space reserved for the footer; it has to fit into the space left below the body container.

Commands for manipulating length variables: \newlength declares them (and all of these are predeclared in the document stylesheet); \setlength sets them to a value; \addtolength adds to them.

If you're adjusting margins, it can be helpful to add "\usepackage{layout}" to the preamble, and then "\newpage\layout" before the final "\end{document}". This will print a diagram showing the layout boxes and their dimensions.

\LTchunksize is an internal variable in longtable for how many rows to process at a time. There's probably no reason to change it.

\addtolength{\voffset}{-0.0cm}    % top margin to top of header
\addtolength{\hoffset}{-0.6cm}    % left margin on page
\addtolength{\topmargin}{[@-- defined($topmargin) ? $topmargin : '-1.00cm' --@]}
\setlength{\headheight}{2.0cm}    % height of header
\setlength{\headsep}{[@-- defined($headsep) ? $headsep : '1.0cm' --@]}
\setlength{\footskip}{1.0cm}    % bottom of footer from bottom of text
%\addtolength{\textwidth}{2.1in}      % width of text
\setlength{\textwidth}{19.5cm}
\setlength{\textheight}{[@-- defined($textheight) ? $textheight : '19.5cm' --@]}
\setlength{\oddsidemargin}{-0.9cm}  % odd page left margin
\setlength{\evensidemargin}{-0.9cm}   % even page left margin
\LTchunksize=40

$coupon is a local variable containing the entire generated content of the [#Coupon_template invoice payment coupon]. If for some reason there isn't a payment coupon (the customer doesn't owe any money, or this is a quotation or statement rather than an invoice) then it's an empty string.

\footrule is the macro used to draw a line at the top of the footer. Here, we redefine it so that it doesn't appear on the first page if there's a payment coupon (because the coupon will make its own line, at a different position).

$couponlocation is config option that sets where the coupon should be located (top or bottom), this defaults to bottom.

\extracouponspace is a config setting for how much space to reserve for the coupon. We will "shorten" the first page's content by that much, which moves the starting position of the header up or down depending on coupon location.

\renewcommand{\headrulewidth}{0pt}
\renewcommand{\footrulewidth}{1pt}
\renewcommand{\footrule}{
[@--
  $coupon ? '\ifthenelse{\equal{\thepage}{1}}' : ;
--@]
  {
  }
  {
    \vbox to 0pt{\rule{\headwidth}{\footrulewidth}\vss}
  }
}
\newcommand{\extracouponspace}{[@-- defined($extracouponspace) ? $extracouponspace : '2.7in' --@]}

Positioning mailing addresses. In current code I'd recommend using the "textpos" package instead. Also a command here to output the dollar sign (since '$' has special properties in LaTeX). The escaping functions in Template_Mixin.pm know about this, and will substitute \dollar throughout.

% Adjust the inset of the mailing address
\newcommand{\addressinset}[1][]{\hspace{1.0cm}}
% Adjust the inset of the return address and logo
\newcommand{\returninset}[1][]{\hspace{-0.25cm}}
% New command for address lines i.e. skip them if blank
\newcommand{\addressline}[1]{\ifthenelse{\equal{#1}{}}{}{#1\\}}
% Inserts dollar symbol
\newcommand{\dollar}[1][]{\symbol{36}}

Headers/footers

"fancyhdr" allows separate left, right, and center headers and footers (the first argument to \fancyhead or \fancyfoot). We use left and right headers and a center footer.

These commands are supposed to remove the default "plain" style page footer, but we don't use that page style so it's probably not needed.

% Remove plain style header/footer
\fancypagestyle{plain}{
  \fancyhead{}
}
\fancyhf{}

The center footer. On page 1, if there's a coupon, the coupon is the footer. In that case, we move the start position up by \extracouponspace (using \vspace) so that it starts above the normal footer position.

The footer to display below the coupon (or if there isn't one) is configurable as invoice_latexfooter. The footer to show on subsequent pages is invoice_latexsmallfooter. By default they're both just the company name.

% Define fancy header/footer for first and subsequent pages
\fancyfoot[C]{
  \ifthenelse{\equal{\thepage}{1}}
  { % First page
[@--
  if ($coupon) {
    $OUT .= '\vspace{-\extracouponspace}';
    $OUT .= '\rule[0.5em]{\textwidth}{\footrulewidth}\\\\';
    $OUT .= $coupon;
    $OUT .= '\vspace{'.
      (defined($couponfootsep) ? $couponfootsep : '0.2in') .
      '}';
  }
  ;
--@] [@-- $smallerfooter ? '\scriptsize{' : '\small{' --@]
[@-- $footer --@]
    }[@-- $coupon ? '\vspace{\extracouponspace}' :  --@]
  }
  { % ... pages
    [@-- $smallerfooter ? '\scriptsize{' : '\small{' --@]
[@-- $smallfooter --@]
    }
  }
}

The right side footer, showing the page number. "emt()" localizes text to the customer's language and then escapes it for LaTeX. "~" is a non-breaking space character. It's suppressed on the first page due to the coupon.

\fancyfoot[R]{
  \ifthenelse{\equal{\thepage}{1}}
  { % First page
  }
  { % ... pages
    \small{\thepage~[@-- emt('of') --@]~\pageref{LastPage}}
  }
}

The left side header, containing the return address and logo on the first page (and nothing on later pages). \returninset is just a horizontal adjustment. \makebox{} tells LaTeX to put its contents into a single box. That's probably unnecessary because the only thing in the box is a table, which is already a single box.

\begin{tabular}{ll} creates a two-column table. In the first column is a "minipage", which is just a fixed-width container environment (5.5cm here) with its bottom edge aligned to the table cell it's in. In here we put the evaluated "returnaddress" template.

Tables in LaTeX always use & to separate columns and \\ to separate rows. A row must always contain as many columns as in the column spec; otherwise LaTeX will guess about what you mean and will usually get it wrong. \\ at the end of the last row is optional.

The second column contains the logo graphic. See "texdoc graphicx" for a full explanation of this command; it can do many useful things including scaling and repositioning the graphic. For example, if you want the logo to be scaled to be 0.5 inch tall, you can do this: "\includegraphics[height=0.5in]{[@-- $logo_file --@]}".

\fancyhead[L]{
  \ifthenelse{\equal{\thepage}{1}}
  { % First page
    \returninset
    \makebox{
      \begin{tabular}{ll}
        \begin{minipage}[b]{5.5cm}
[@-- $returnaddress --@]
        \end{minipage} &
        \includegraphics{[@-- $logo_file --@]}\\
      \end{tabular}
    }
  }
  { % ... pages
    %\includegraphics{[@-- $logo_file --@]} % Uncomment if you want the logo on all pages.
  }
}

The right side header, containing the invoice date, invnum, and custnum in a three-column table with each column centered ({ccc}). "$no_date" and "$no_number" are both enabled by arguments to print_generic() and used when printing statements.

Note that "$date" is already formatted using time2str_local('long'), so something like "Feb 1st, 2017". The template can call time2str() to format other dates; this is an alias to time2str_local so it will use the customer's language settings and LaTeX-escape the result.

This is followed by a horizontal line spanning the table (\hline), then a row where the middle cell contains the $notice_name (usually "Invoice") in \huge font, in small caps (\textsc). The \rule{0pt}{5ex} creates a vertical strut (a zero-width rule) which increases the row height to 5ex. Then another \hline.

Note that \hline should never be followed by \\ and will cause errors if it is.

On subsequent pages we show a smaller version without the notice name.

\fancyhead[R]{
  \ifthenelse{\equal{\thepage}{1}}
  { % First page
    \begin{tabular}{ccc}
    [@-- join(' & ', ( $no_date   ?  : emt('Invoice date') ),
                     ( $no_number ?  : emt('Invoice #')    ),
                     emt('Customer #')
             )
    --@]\\
    \vspace{0.2cm}
    \textbf{[@-- $date --@]} & \textbf{[@-- $invnum --@]} & \textbf{[@-- $custnum --@]} \\\hline
    \rule{0pt}{5ex} &~~ \huge{\textsc{[@-- emt($notice_name) --@]}} & \\
    \vspace{-0.2cm}
     & & \\\hline
    \end{tabular}
  }
  { % ... pages
    \small{
      \begin{tabular}{lll}
      [@-- join(' & ', emt('Invoice date'), emt('Invoice #'), emt('Customer #') ) --@]\\
      \textbf{[@-- $date --@]} & \textbf{[@-- $invnum --@]} & \textbf{[@-- $custnum --@]}\\
      \end{tabular}
    }
  }
}

Enable the fancyhdr page style and set the font family.

\pagestyle{fancy}
%% Font options are:
%%  bch Bitsream Charter
%%  put Utopia
%%  phv Adobe Helvetica
%%  pnc New Century Schoolbook
%%  ptm Times
%%  pcr Courier
\renewcommand{\familydefault}{phv}

Line item table macros

This section is Freeside-specific. The main invoice line item table is an 8-column longtable. Normally the columns are:

  • Package number
  • Description (6 columns)
  • Price

(The 6 columns are used separately for showing CDRs, if there are any.)

If invoice-unitprice is on, it's:

  • Package number
  • Description (4 columns)
  • Unit price
  • Quantity
  • Price

According to that setting, create macros for how many columns and how much space to allocate to the description, and for the headers of the Unit Price and Unit Quantity columns.

% Commands for freeside table header...
\newcommand{\FSdescriptionlength} { [@-- $unitprices ? '8.2cm' : '12.8cm' --@] }
\newcommand{\FSdescriptioncolumncount} { [@-- $unitprices ? '4' : '6' --@] }
\newcommand{\FSunitcolumns}{ [@--
  $unitprices
  ? '\makebox[2.5cm][r]{\textbf{~~' . emt('Unit Price') . '}} &' .
    '\makebox[1.4cm]{\textbf{~' . emt('Quantity') . '}} & '
  :  --@] }

\FShead renders the main table header. Using \makebox inside a column forces it to a minimum width. \multicolumn{N}{X}{ ... } is like COLSPAN in HTML: makes a cell spanning N columns. The second argument is how to align the contents of the column (l, r, or c).

\truncate renders its argument into a space with a maximum width. It will break at word boundaries and insert an ellipsis afterward. Note that if we didn't do this, longtable would either squish the surrounding columns or push them off the page rather than word-wrap the description.

\FSusagehead is the same, but used for usage summary sections that show the number of calls and total minutes. The usage_class_summary config turns this on.

\newcommand{\FShead}{
  \hline
  \rule{0pt}{2.5ex}
  \makebox[1.4cm]{} &
  \multicolumn{\FSdescriptioncolumncount}{l}{
    \truncate{\FSdescriptionlength}{\textbf{[@-- emt('Description') --@]}}
  } &
  \FSunitcolumns
  \makebox[1.6cm][r]{\textbf{[@-- emt('Amount') --@]}} \\
  \hline
}
\newcommand{\FSusagehead}{
  \hline
  \rule{0pt}{2.5ex}
  \makebox[1.4cm]{} &
  \multicolumn{4}{l}{
    \truncate{\FSdescriptionlength}{\textbf{[@-- emt('Description') --@]}}
  } &
  \textbf{~~[@-- emt('Calls') --@]} &
  \textbf{~~[@-- emt('Duration') --@]} &
  \textbf{~~[@-- emt('Amount') --@]} \\
  \hline
}

Next, the commands to actually display line items.

\FSdesc will be called once for each element in the @detail_items array. This takes five arguments (that's the [5] in the declaration) which are the values to put in the pkgnum, description, unit price, quantity, and price columns. If unit prices are off then arguments 3 and 4 will be empty.

\FSextdesc will be called once for each element in the 'ext_description' element of the detail item. These contain things like service labels, discount details, prorate details, and manually created package details. This macro takes a single argument and places it in a big multicolumn below the description, in small font.

\FScalldetail is used only for call details. These are preprocessed by FS::TemplateItem_Mixin::details() into LaTeX table rows (pre-escaped, delimited with &). So, the macro just skips the first column and outputs its argument.

\FStotaldesc is used for subtotal and total rows; it puts its first argument in a big multicolumn in the description space, and the second in the amount column.

Finally, \FSusagedesc is for usage summary sections, and puts the total calls, duration, and amount in the correct columns.

% ...description...
\newcommand{\FSdesc}[5]{
  \multicolumn{1}{c}{\rule{0pt}{2.5ex}\textbf{#1}} &
  \multicolumn{[@-- $unitprices ? '4' : '6' --@]}{l}{
    \truncate{\FSdescriptionlength}{\textbf{#2}}
  } &
[@-- $unitprices ? '  \multicolumn{1}{r}{\textbf{#3}} &'."\n".
                   '  \multicolumn{1}{r}{\textbf{#4}} &'."\n"
                 :
--@]
  \multicolumn{1}{r}{\textbf{#5}}\\
}
% ...extended description...
\newcommand{\FSextdesc}[1]{
  \multicolumn{1}{l}{\rule{0pt}{1.0ex}} &
  \multicolumn{6}{l}{
    \truncate{12.8cm}{\small{Mark (talk)#1}}
  } \\
}
% ...call detail (multiple columns already)...
\newcommand{\FScalldetail}[1]{
  \multicolumn{1}{l}{\rule{0pt}{1.0ex}} &
  Mark (talk)#1
  \\
}
}
% ...and total line items (which use the full 12.8cm length, ignoring
% unitprice/quantity
\newcommand{\FStotaldesc}[2]{
  & \multicolumn{6}{l}{
    \truncate{12.8cm}{#1}
  } & #2\\
}
% ...usage class summary
\newcommand{\FSusagedesc}[4]{
  \multicolumn{1}{c}{\rule{0pt}{2.5ex}} &
  \multicolumn{4}{l}{\textbf{#1}} &
  \multicolumn{1}{r}{\textbf{#2}} &
  \multicolumn{1}{r}{\textbf{#3}} &
  \multicolumn{1}{r}{\textbf{#4}}
  \\
}

Main document

First, make a minipage for the customer's name and address. This starts at \addressinset + 5 mm (not sure why) and can be up to 7 cm wide. The \makebox does nothing (as above, a minipage is already a single box). The \addressline macro makes a line break only if the argument is non-empty, to avoid errors for breaking an empty line.

\begin{document}
% Headers and footers defined for the first page
\addressinset \rule{0.5cm}{0cm}
\makebox{
\begin{minipage}[t]{7.0cm}
\vspace{[@-- defined($addresssep) ? $addresssep : '0.25cm' --@]}
\textbf{[@-- $payname --@]}\\
\addressline{[@-- $company --@]}
\addressline{[@-- $address1 --@]}
\addressline{[@-- $address2 --@]}
\addressline{[@-- $city --@], [@-- $state --@]~~[@-- $zip --@]}
\addressline{[@-- $country --@]}
\end{minipage}}

Make another minipage for the service address, terms, and PO number. $ship_enable is true if the global invoice-ship_address or per-customer "invoice_ship_address" flag is on.

The initial \hfill spreads out the two minipages so that they go all the way to the margins. The 'terms' and 'po_line' should probably be in \addressline{} macros but currently aren't.

After that, skip some vertical space. (If you need more vertical space for an invoice format, the 1.5cm here can be cut.)

\hfill
\makebox{
\begin{minipage}[t]{6.4cm}
[@--
  if ($ship_enable) {
    $OUT .= '\textbf{' . emt('Service Address') . '}\\\\';
    $OUT .= "\\addressline{$ship_company}";
    $OUT .= "\\addressline{$ship_address1}";
    $OUT .= "\\addressline{$ship_address2}";
    $OUT .= "\\addressline{$ship_city, $ship_state~~$ship_zip}";
    $OUT .= "\\addressline{$ship_country}";
    $OUT .= '~\\\\';
  }else{
    $OUT .= ;
  }
--@]
\begin{flushright}
[@-- $terms ? emt('Terms') . ': ' . emt($terms) :  --@]\\
[@-- $po_line --@]\\
\end{flushright}
\end{minipage}}
\vspace{1.5cm}

Insert the Summary if there is one. The last thing in the summary template is a page break.

%
[@-- $summary --@]
%

Sections

Start creating sections, according to the @sections array. Section entries contain the following keys:

  • description
  • category (pkg_category.categoryname, for package sections by category)
  • location (hashref of location fields, for package sections by location)
  • pretotal (formatted with \FStotaldesc, before detail lines)
  • subtotal (formatted with \FStotaldesc, at the end)
  • posttotal (line to display after the end of the section; used for "Balance Due" message and a few other things)
  • sort_weight (sort order, but also does complicated things)

and optionally some flags:

  • post_total (put the section at the end of the invoice)
  • summarized (omit the section entirely)
  • usage_section (use \FSusagedesc instead of \FSdesc, see above)
  • no_subtotal (omit the subtotal line)

If invoice_sections is enabled: the $multisection flag in print_generic() is turned on, and @sections is filled by calling FS::Template_Mixin::_items_sections, which makes a section for each package category. Line items for packages go into the package category's section. invoice_sections_method can be used to divide sections by location rather than by category. In addition, sections are created for previous invoices, taxes, and "adjustments" (credits and payments to this invoice).

If not: a single section is created, with empty description, containing previous invoices, current charges, and adjustments (after the total). previous_balance-section will make a previous invoices section anyway.

Additional sections are created in some cases. svc_phone_sections creates a section for each phone line (see RT#6592; note that this customer is no longer active). voip-cust_accountcode_cdr creates a section for each accountcode (see RT#12181). discount-show_available creates a section with information on available discounts. usage_class_summary creates a section with usage subtotals.

finance_pkgclass specifies the package class containing "finance charges", which is handled specially. That class's category name is passed to the template as $finance_section, and the sum of charges in that category is passed as $finance_amount. That amount is broken out from the "Current charges" line in the summary section. On a multisection summary-format invoice, the finance charges will also not be shown in the body of the invoice. I don't know why.

Loop over all sections (except for the finance charges, maybe). Inside the loop we'll do everything by appending to $OUT.

\section*{}
[@--
  foreach my $section ( grep { !$summary || $_->{description} ne $finance_section } @sections ) {

We're going to do many things if "!$summary". If there's a summary section, the pretotals and posttotals are redundant so skip them.

   if ($section->{'pretotal'} && !$summary) {
     $OUT .= '\begin{flushright}';
     $OUT .= '\large\textsc{'. $section->{'pretotal'}. '}\\\\';
     $OUT .= '\\end{flushright}';
   }

Start a new page if this is a "post_total" section. As far as I know these are used only for usage details. I think the 'summarized' flag is no longer used for anything.

The section heading is going to be a caption placed inside the main table. \captionsetup sets properties for the caption (left justified, bold, small caps, Large size). If this is the first page, also tell longtable (via \LTextracouponspace) to shorten the effective length of the page so that it doesn't overprint the coupon.

   $OUT .= '\pagebreak' if $section->{'post_total'};
   unless ($section->{'summarized'} ) {
     $OUT .= '\captionsetup{singlelinecheck=false,justification=raggedright,font={Large,sc,bf}}';
     $OUT .= '\ifthenelse{\equal{\thepage}{1}}{\setlength{\LTextracouponspace}{\extracouponspace}}{\setlength{\LTextracouponspace}{0pt}}'
       if $coupon;

Start the longtable that will contain the section's line items. Eight columns, pkgnum (centered), description and optional unitprice/quantity (left), then price (right).

\caption* produces a table caption without prefixing it with "Table 1". The caption is the section description, or 'Charges' if it's null (except that if packages are sectioned by location, we put one together from the location fields).

     $OUT .= '\begin{longtable}{cllllllr}';
     $OUT .= '\caption*{ ';
     if ($section->{'location'}) {
       $OUT .= $section->{'location'}{'label_prefix'}. ': '
         if length($section->{'location'}{'label_prefix'});
       $OUT .= $section->{'location'}{'address1'};
       $OUT .= ', ' . $section->{'location'}{'address2'}
         if length($section->{'location'}{'address2'});
       $OUT .= ', ' .
               $section->{'location'}{'city'} . ', ' .
               $section->{'location'}{'state'} . '~' .
               $section->{'location'}{'zip'};
     } elsif ( $section->{'description'} ) {
       $OUT .= ($section->{'description'});
     } else {
       $OUT .= emt('Charges');
     }
     $OUT .= '}\\\\';

The point of a longtable is that it can span multiple pages, including printing head/foot rows at the top/bottom of each page automatically. The protocol for setting these up is to specify the first head, per-page head, per-page foot, and last foot, then the rest of the table contents. Here, we set the first head to be \FShead (or \FSusagehead). \endfirsthead means we're done specifying it.

"header_generator" and other foo_generators are from an old mechanism that injected callbacks into the template data. This is strongly deprecated.

     if ($section->{header_generator}) {
       $OUT .= &{$section->{header_generator}}();
     } elsif ( $section->{usage_section} ) {
       $OUT .= '\FSusagehead';
     } else {
       $OUT .= '\FShead';
     }
     $OUT .= '\endfirsthead';

The per-page head: a row with a "Continued" message, then \FShead again. The per-page foot is the same.

     $OUT .= '\multicolumn{7}{r}{\rule{0pt}{2.5ex}'.emt('Continued from previous page').'}\\\\';
     if ($section->{header_generator}) {
       $OUT .= &{$section->{header_generator}}();
     } elsif ( $section->{usage_section} ) {
       $OUT .= '\FSusagehead';
     } else {
       $OUT .= '\FShead';
     }
     $OUT .= '\endhead';
     $OUT .= '\multicolumn{7}{r}{\rule{0pt}{2.5ex}'.emt('Continued on next page...').'}\\\\';
     $OUT .= '\endfoot';
     $OUT .= '\hline';

The final foot for the section. For multisection invoices this contains the section subtotal:

     if (scalar(@sections) > 1 and !$section->{no_subtotal}) {
       if ($section->{total_generator}) {
         $OUT .= &{$section->{total_generator}}($section);
       } else {
         $OUT .= '\FStotaldesc{' . $section->{'description'} . ' Total}' .
                 '{' . $section->{'subtotal'} . '}' . "\n";
       }
     }

Single-section invoices use "@total_items" for items that need to appear after the line items. These include the pre-tax subtotal, taxes, total charges, adjustments, and the balance due. Call \FStotaldesc for each of them. Then end the section foot.

     #if ($section == $sections[$#sections]) {
       foreach my $line (grep {$_->{section}->{description} eq $section->{description}} @total_items) {
         if ($section->{total_line_generator}) {
           $OUT .= &{$section->{total_line_generator}}($line);
         } else {
           $OUT .= '\FStotaldesc{' . $line->{'total_item'} . '}' .
                   '{' . $line->{'total_amount'} . '}' . "\n";
         }
       }
     #}
     $OUT .= '\hline';
     $OUT .= '\endlastfoot';

Line items are passed in @detail_items. A line item entry can contain:

  • section (reference)
  • ref (pkgnum)
  • description
  • ext_description (arrayref of additional lines)
  • quantity
  • amount
  • duration (for usage summary sections)
  • pkgpart
  • unit_amount (unit price)
  • locationnum
  • svc_label (the primary service's label)

Line items are created in several places in Template_Mixin and TemplateItem_Mixin, most importantly _items_cust_bill_pkg.

@detail_items is a single array; we find the lines belonging to this section by checking their 'section' element. If there's only one section, process all the lines.

     my $lastref = 0;
     foreach my $line (
       grep { ( scalar( @sections ) > 1
              ? $section->{'description'} eq $_->{'section'}->{'description'}
              : 1
            ) }
       @detail_items )
     {
       my $ext_description = $line->{'ext_description'};
       # Don't break-up small packages.
       my $rowbreak = @$ext_description < 5 ? '*' : ;

$lastref is set to the pkgnum of the previous line item. If the value of 'ref' no longer equals that, we're now showing a different package; separate with an \hline. After outputting this line item we'll set $lastref to the new pkgnum.

If this is a usage summary section, convert the duration to minutes/seconds and call \FSusagedesc, passing the description, quantity (number of calls), duration, and total price.

If it's a normal section, call \FSdesc, passing the description, unit price, quantity, and amount. If unit prices aren't in use, or there isn't one on this line item, then leave unit price and quantity blank.

"$rowbreak" is a flag for whether to allow a page break. In general we want to keep line items and their ext_description lines together, unless there are a lot of description lines. So if there are fewer than 5 of them, we set $rowbreak = '*', which after \FSdesc{} is evaluated, results in the table row delimiter being "\\*". This tells LaTeX to avoid putting a page break immediately after this row. Probably we should set $rowbreak = on the last description line to explicitly allow page breaks between packages.

       $OUT .= "\\hline\n" if (($line->{'ref'} || 0) ne $lastref);
       if ($section->{description_generator}) {
         $OUT .= &{$section->{description_generator}}($line);
       } elsif ($section->{usage_section}) {
         my $minutes = sprintf('%d', $line->{'duration'} / 60);
         my $seconds = $line->{'duration'} % 60;
         $OUT .= '\FSusagedesc
           {' . $line->{'description'} . '}
           {' . $line->{'quantity'} . '}
           {' . $minutes . 'm ' . $seconds . 's' . '}
           {' . $line->{'amount'} . '}';
       } else {
         $OUT .= '\FSdesc'.
                 '{}'.
                 '{' . $line->{'description'} . '}' ;
         if ( $unitprices and length($line->{'unit_amount'}) ) {
           # then show the unit amount and quantity
           $OUT .=
               '{\\dollar' . $line->{'unit_amount'} . '}'.
               '{'         . $line->{'quantity'}    . '}';
         } else {
           # leave those columns blank
           $OUT .= '{}{}';
         }
         $OUT .= '{\\dollar' . $line->{'amount'} . "}${rowbreak}\n";
       }
       $lastref = $line->{'ref'} || 0;

Output ext_description lines. We assume that any of them that contain an unescaped "&" are call details and should use \FScalldetail. Otherwise they use \FSextdesc.

       foreach my $ext_desc (@$ext_description) {
         if ($section->{extended_description_generator}) {
           $OUT .= &{$section->{extended_description_generator}}($ext_desc);
         } elsif ( $ext_desc !~ /[^\\]&/ ) {
           $OUT .= '\FSextdesc{' . $ext_desc . "}$rowbreak\n";
         } else { # call detail
           $OUT .= '\FScalldetail{' . $ext_desc . "}$rowbreak\n";
         }
       }
     }

End the longtable. If there is a posttotal item in this section (the final Balance Due / Credit Balance message, previous invoice aging, a few other things) then show it right-justified, in the same formatting used for the caption.

      $OUT .= '\end{longtable}';
    }
    if ($section->{'posttotal'}) {
      $OUT .= '\begin{flushright}';
      $OUT .= '\normalfont\large\bfseries\textsc{'. $section->{'posttotal'}. '}\\\\';
      $OUT .= '\\end{flushright}';
    }
  }

Notes

For the notes panel, create a minipage the full width of the text. (Unless $summary is in use; then the notes were on the summary page.)

There's some tricky handling of vertical space here. We want the notes at the bottom of the page, regardless of where the last section ended, so the \vfill between them acts as an expandable space.

However, if the notes end up on the first page, we have to prevent them from overprinting the coupon. So if there's a coupon and the notes are on page 1, we insert a strut of height \extracouponspace below the notes. The \vfill will then expand so that the bottom of the strut is at the bottom of the page, keeping the coupon space clear.

--@]
\vfill
\begin{minipage}[t]{\textwidth}
  [@-- length($summary)
         ?
        : ( $smallernotes
              ? '\scriptsize{ '.$notes.' }'
              : $notes
          )
  --@]
  [@-- $coupon ? '\ifthenelse{\equal{\thepage}{1}}{\rule{0pt}{\extracouponspace}}{}' :  --@]
\end{minipage}
\end{document}

That's the end of the invoice.

The summary template

Start with a two-column table, left side for the notes and right side for the billing summary. On the left side, make a 6.4cm minipage, and put a 10cm vertical strut there, along with the notes.

\begin{tabular}{ll}
\begin{minipage}{6.4cm}
\begin{tabular}{m{0cm}m{6.4cm}}
\rule{0cm}{10cm}&\begin{minipage}{6cm}[@-- $notes --@]\end{minipage}\\
\end{tabular}
\end{minipage} &

On the right side, place a 2cm horizontal strut followed by a 12.8cm minipage. This will contain the billing summary.

\rule{2cm}{0cm}
\begin{minipage}{12.8cm}

The billing summary itself is a two-column table, one for labels and one for amounts. The section headings are just labels with no amounts, with lines underneath.

'true_previous_balance' is, as far as we can determine it, the balance that was printed on the customer's previous invoice. It's the sum of all the customer's transactions (invoices, credits, payments, refunds) dated before that invoice, plus the invoice amount.

'balance_adjustments' is the sum of payments and credits received after the previous invoice, but applied to invoices before this current invoice. 'true_previous_balance' - 'balance_adjustments' is the total amount owed on all past invoices.

\begin{tabular}{lr}
\hline
&\\
\textbf{\underline{Summary of Previous Balance and Payments}} & \\
&\\
\textbf{Previous Balance}&\textbf{\dollar[@-- $true_previous_balance --@]}\\
\textbf{Payments}&\textbf{\dollar[@-- $balance_adjustments --@]}\\
\cline{2-2}
\textbf{Balance Outstanding}&\textbf{\dollar[@-- sprintf('%.2f', $true_previous_balance -$balance_adjustments) --@]}\\
&\\
\hline
&\\
\textbf{\underline{Summary of New Charges}} & \\
&\\

@summary_subtotals contains one line for each invoice section, except for the sections that have special handling such as the adjustments and finance charges. Make a line for each of those subtotals. \cline draws a horizontal line (like \hline) but only under specified columns: here, only the second column. After that, make rows for the total new charges (minus finance charges), the total amount owed on previous invoices ('true_previous_balance' – 'balance_adjustments'), and the finance charges.

[@--
  foreach my $section (@summary_subtotals) {
    $OUT .= '\textbf{'. ($section->{'description'} ? $section->{'description'} : 'Charges' ). '}';
    $OUT .= '&\textbf{'. $section->{'subtotal'}. '}\\\\';
  }
  $OUT .= '\cline{2-2}';
--@]
\textbf{New Charges Total}&\textbf{\dollar[@-- $current_less_finance --@]}\\
&\\
\hline
&\\
\textbf{\underline{Invoice Summary}} & \\
& \\
\textbf{Previous Past Due Charges}&\textbf{\dollar[@-- sprintf('%.2f', $true_previous_balance - $balance_adjustments) --@]}\\
\textbf{Finance charges on overdue amount}&\textbf{\dollar[@-- $finance_amount --@]}\\
\textbf{New Charges}&\textbf{\dollar[@-- $current_less_finance --@]}\\

Then show the subtotal of the adjustment section, if there is one. This contains payments and credits applied to the current invoice (not past invoices).

[@--
  #false laziness w/invoice_htmlsummary and above
  foreach my $section ( grep $_->{adjust_section}, @sections ) {
    $OUT .= '\textbf{'. ($section->{'description'} ? $section->{'description'} : 'Charges' ). '}';
    $OUT .= '&\textbf{'. $section->{'subtotal'}. '}\\\\';
  }
--@]

Finally, the customer's balance including the current invoice, and then close the table and minipage, and force a page break so the line items can start on a new page.

\cline{2-2}
\textbf{Total Amount Due}&\textbf{\dollar[@-- sprintf('%.2f', $balance) --@]}\\
&\\
\hline
\end{tabular}
\end{minipage} \\
\end{tabular}
\newpage


Invoice modes