forked from erictapen/typst-invoice
-
Notifications
You must be signed in to change notification settings - Fork 0
/
lib.typ
146 lines (123 loc) · 3.84 KB
/
lib.typ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#import "@preview/tablex:0.0.8": gridx, hlinex
#import "@preview/cades:0.3.0": qr-code
#import "@preview/ibanator:0.1.0": iban
// Generates an invoice
#let invoice(
// The invoice number
invoice-nr,
// The date on which the invoice was created
invoice-date,
// A list of items
items,
// Name and postal address of the author
author,
// Name and postal address of the recipient
recipient,
// Name and bank account details of the entity receiving the money
bank-account,
// Optional VAT
vat: 0.19,
// Check if the german § 19 UStG applies
kleinunternehmer: false,
) = {
set text(lang: "de", region: "DE")
set page(paper: "a4", margin: (x: 20%, y: 20%, top: 20%, bottom: 20%))
// Typst can't format numbers yet, so we use this from here:
// https://github.com/typst/typst/issues/180#issuecomment-1484069775
let format_currency(number, locale: "de") = {
let precision = 2
assert(precision > 0)
let s = str(calc.round(number, digits: precision))
let after_dot = s.find(regex("\..*"))
if after_dot == none {
s = s + "."
after_dot = "."
}
for i in range(precision - after_dot.len() + 1){
s = s + "0"
}
// fake de locale
if locale == "de" {
s.replace(".", ",")
} else {
s
}
}
set text(number-type: "old-style")
smallcaps[
*#author.name* •
#author.street •
#author.zip #author.city
]
v(1em)
[
#set par(leading: 0.40em)
#set text(size: 1.2em)
#recipient.name \
#recipient.street \
#recipient.zip
#recipient.city
]
v(4em)
grid(columns: (1fr, 1fr), align: bottom, heading[
Rechnung \##invoice-nr
], [
#set align(right)
#author.city, *#invoice-date.display("[day].[month].[year]")*
])
let total = items.map((item) => item.price).sum()
let items = items.enumerate().map(
((id, item)) => ([#str(id + 1).], [#item.description], [#format_currency(item.price)€],),
).flatten()
[
#set text(number-type: "lining")
#gridx(
columns: (auto, 10fr, auto), align: ((column, row) => if column == 1 { left } else { right }), hlinex(stroke: (thickness: 0.5pt)), [*Pos.*], [*Beschreibung*], [*Preis*], hlinex(), ..items, hlinex(), [], [
#set align(end)
Summe:
], [#format_currency((1.0 - vat) * total)€], hlinex(start: 2), [], [
#set text(number-type: "old-style")
#set align(end)
#str(vat * 100)% Mehrwertsteuer:
], [#format_currency(vat * total)€], hlinex(start: 2), [], [
#set align(end)
*Gesamt:*
], [*#format_currency(total)€*], hlinex(start: 2),
)
]
v(3em)
[
#set text(size: 0.8em)
Vielen Dank für die Zusammenarbeit. Die Rechnungssumme überweisen Sie bitte
innerhalb von 14 Tagen ohne Abzug auf mein unten genanntes Konto unter Nennung
der Rechnungsnummer.
#if kleinunternehmer [
Gemäß § 19 UStG wird keine Umsatzsteuer berechnet.
]
]
v(1em)
// This is the content of an https://en.wikipedia.org/wiki/EPC_QR_code version 002
// Eventually this could be put into its own package?
let epc-qr-content = (
"BCD\n" + "002\n" + "1\n" + "SCT\n" + bank-account.bic + "\n" + bank-account.name + "\n" + bank-account.iban + "\n" + "EUR" + format_currency(total, locale: "en") + "\n" + "\n" + invoice-nr + "\n" + "\n" + "\n"
)
grid(columns: (1fr, 1fr), gutter: 1em, align: top, [
#set par(leading: 0.40em)
#set text(number-type: "lining")
Kontoinhaberin: #bank-account.name \
Kreditinstitut: #bank-account.bank \
IBAN: *#iban(bank-account.iban)* \
BIC: #bank-account.bic
], qr-code(epc-qr-content, height: 4em))
[
Steuernummer: #author.tax_nr
#v(1em)
Mit freundlichen Grüßen
#if author.signature == none [
#v(1em)
] else [
#image(author.signature.path, width: author.signature.width)
]
#author.name
]
}