BFH Quote
Download
BFH Quote Template
A LaTeX template for quotations in the BFH corporate design. It is based on
the bfhletter package of bfh-ci and KOMA-Script.
The quote positions are kept in a separate Lua data file. The package
quotetable.sty reads that file, typesets the position table and calculates
all line amounts, the subtotal, VAT and the total. You never edit the table
itself, only the list of positions.
Requirements
LuaLaTeX (the table code is written in Lua; pdfLaTeX/XeLaTeX won’t work)
TeX Live or MiKTeX with:
bfh-ci,koma-script,tabularray,siunitx,luacode,xcolor,babel,microtypeOptional:
latexmkfor one-command builds
Files
File |
Purpose |
|---|---|
|
The quotation letter. Edit quote details, customer and texts here. |
|
Sender data (name, function, phone, e-mail, address). |
|
Example data file: flat list of positions. |
|
Example data file: positions grouped by categories. |
|
Package that builds the position table from the data file. |
|
Build settings for |
Quick start
Enter your sender data in
BFH_upn.lco. You can rename the file, e.g. to your BFH short name (BFH_abc1.lco); then change\LoadLetterOption{BFH_upn}inbfhquote.texaccordingly.In
bfhquote.tex, fill in the blocks Quote configuration and Customer.Create a data file for the positions (copy one of the examples) and select it:
\QuoteLoadItems{positions-quote_02.lua}
Build:
latexmk # uses .latexmkrc, PDF ends up in _build/ # or lualatex bfhquote
Quote positions (data file)
The data file is a plain Lua file that returns a list. Each position has these fields:
Field |
Type |
Meaning |
|---|---|---|
|
text |
Position text; long texts wrap within the column. |
|
number |
Quantity, e.g. |
|
text |
Unit, e.g. |
|
number |
Price per unit, e.g. |
|
text, optional |
Key of a category (see below). |
The line amount is Quantity × UnitPrice, rounded to 2 decimals.
Flat list
return {
{ Description = "Some text", Quantity = 4, Unit = "pcs.", UnitPrice = 95.00 },
{ Description = "Work to do", Quantity = 10, Unit = "h", UnitPrice = 86.00 },
}
Positions are numbered 1, 2, 3, …
Grouped by categories
Define each category once with a short key and its heading (Title), then
assign positions to it by key:
return {
{ Category = "M", Title = "Material & Parts" },
{ Category = "L", Title = "Labour" },
{ Category = "M", Description = "Board", Quantity = 4, Unit = "pcs.", UnitPrice = 95.00 },
{ Category = "L", Description = "Assembly", Quantity = 10, Unit = "h", UnitPrice = 86.00 },
{ Category = "M", Description = "Case", Quantity = 4, Unit = "pcs.", UnitPrice = 354.00 },
{ Description = "Small Parts and Consumables", Quantity = 1, Unit = "lot", UnitPrice = 50.00 },
}
Result:
1 Material & Parts
1.1 Board
1.2 Case
2 Labour
2.1 Assembly
3 Small Parts and Consumables
Rules:
Categories are numbered in the order of their definitions. Positions are numbered in file order within their category, wherever they stand in the file.
Positions without
Categoryare top-level positions at their place in the file.Category heading rows are tinted and bold.
Typo protection: a position with an undefined key (e.g.
Category = "N") stops the build withquotetable: unknown category 'N'. The position is still printed, ungrouped. A key defined twice is also an error.A category without positions is left out; a warning appears in the log.
Text and numbers
Special characters in texts (
& % $ # _ { } ~ ^ \) are escaped automatically. Write"R&D", not"R\\&D".Write
QuantityandUnitPriceas numbers (95.00). A value that isn’t a number (e.g."95.-") counts as 0.Lua comments start with
--. Every entry ends with a comma.
Customising the table
All settings go into the preamble of bfhquote.tex, after \usepackage{quotetable}.
Currency and VAT are set in the Quote configuration block
(\QuoteCurrency, \QuoteVatRate in percent).
Colours. The defaults follow the BFH table colours; override any of them:
\colorlet{QuoteTableHead}{BFH-MediumBlue!35} % header row
\colorlet{QuoteTableFill}{BFH-MediumBlue!7} % position rows
\colorlet{QuoteGroupFill}{BFH-MediumBlue!20} % category heading rows
\colorlet{QuoteTotalFill}{BFH-MediumBlue!15} % total row
Column widths. The description column takes the remaining width:
\renewcommand\QuoteColPos{0.7cm} % position, flat list
\renewcommand\QuoteColPosGrouped{1.0cm} % position, grouped (1.10)
\renewcommand\QuoteColQty{1.0cm}
\renewcommand\QuoteColUnit{1.25cm}
\renewcommand\QuoteColPrice{1.9cm}
\renewcommand\QuoteColAmount{2.1cm}
\renewcommand\QuoteColSep{4pt} % space between columns
Font of category headings: \renewcommand\QuoteGroupFont{\bfseries}
Additional tabularray options for one table go into the optional argument:
\QuoteTable[row{even} = {bg=white}]
Language of the table texts
The column headings, Subtotal, VAT, Total and the page-break texts follow
the babel language of the document, just like \contentsname or \tablename.
Set the language in the class options of bfhquote.tex:
\documentclass[ngerman]{scrartcl} % or nswissgerman, french, italian, ...
Included translations:
Macro |
English |
German |
Swiss German |
French |
Italian |
|---|---|---|---|---|---|
|
Pos. |
Pos. |
Pos. |
Pos. |
Pos. |
|
Description |
Beschreibung |
Beschreibung |
Désignation |
Descrizione |
|
Qty. |
Menge |
Menge |
Qté |
Qtà |
|
Unit |
Einheit |
Einheit |
Unité |
Unità |
|
Unit price |
Einzelpreis |
Einzelpreis |
Prix unitaire |
Prezzo unitario |
|
Amount |
Betrag |
Betrag |
Montant |
Importo |
|
Subtotal |
Zwischensumme |
Zwischensumme |
Sous-total |
Subtotale |
|
VAT |
MwSt. |
MWST |
TVA |
IVA |
|
Total |
Gesamtbetrag |
Total |
Total |
Totale |
|
(Continued) |
(Fortsetzung) |
(Fortsetzung) |
(suite) |
(continua) |
|
Continued on next page |
Fortsetzung auf der nächsten Seite |
… |
Suite à la page suivante |
Continua alla pagina successiva |
Language names covered: english, british, UKenglish, american,
USenglish, canadian, australian, newzealand; german, ngerman;
austrian, naustrian (VAT: USt.); swissgerman, nswissgerman;
french, francais, acadian, canadien; italian. Without babel, or for
any other language, the English texts are used.
Change a text for one language the babel way, in the preamble:
\addto\captionsngerman{\renewcommand\QuoteDescriptionName{Leistung}}
\addto\captionsenglish{\renewcommand\QuoteQuantityName{Quantity}}
Without babel, use \renewcommand directly.
Add a language with \QuoteTableTranslation (after \usepackage{quotetable}):
\QuoteTableTranslation{spanish}{%
\def\QuotePosName{Pos.}%
\def\QuoteDescriptionName{Descripción}%
\def\QuoteQuantityName{Cant.}%
\def\QuoteUnitName{Unidad}%
\def\QuoteUnitPriceName{Precio unitario}%
\def\QuoteAmountName{Importe}%
\def\QuoteSubtotalName{Subtotal}%
\def\QuoteVatName{IVA}%
\def\QuoteTotalName{Total}%
\def\QuoteContinuedName{(continuación)}%
\def\QuoteContinuedNextName{Continúa en la página siguiente}%
}
The other texts of the letter (Quotation details, Payment terms, …) are
written directly in bfhquote.tex; translate them there.
Page breaks
The table may break across pages; the header row is repeated and the continuation is marked. Two places never break:
between a category heading and its first position
between the last position and the subtotal/VAT/total block
Build notes
.latexmkrcbuilds with LuaLaTeX into_build/. To get a copy of the PDF in_output/, enable one of the$success_cmdlines..latexmkrcalso enables--shell-escape. The template itself doesn’t need it; remove theset_tex_cmdsline if you don’t use packages likesvg.The data file is recorded as a dependency, so
latexmkrebuilds the PDF when only the positions change.
Troubleshooting
Message |
Cause / fix |
|---|---|
|
Compile with |
|
File name in |
|
A position uses a key that has no |
|
Remove the duplicate definition. |
Amount shows |
|