feat: better PDF

This commit is contained in:
2025-08-24 00:24:40 +02:00
parent 0267e6e578
commit 5b664234e9
18 changed files with 540 additions and 228 deletions

144
report/invoice/invoice.go Normal file
View File

@@ -0,0 +1,144 @@
package invoice
import (
"bytes"
"encoding/base64"
"fmt"
"html/template"
"strings"
"git.schreifuchs.ch/lou-taylor/accounting/model"
"github.com/skip2/go-qrcode"
)
// Invoice contains all necessary information for the generation of an invoice.
type Invoice struct {
ReceiverIBAN string `yaml:"receiver_iban" default:"CH44 3199 9123 0008 8901 2"`
IsQrIBAN bool `yaml:"is_qr_iban" default:"true"`
ReceiverName string `yaml:"receiver_name" default:"Robert Schneider AG"`
ReceiverStreet string `yaml:"receiver_street" default:"Rue du Lac"`
ReceiverNumber string `yaml:"receiver_number" default:"12"`
ReceiverZIPCode string `yaml:"receiver_zip_code" default:"2501"`
ReceiverPlace string `yaml:"receiver_place" default:"Biel"`
ReceiverCountry string `yaml:"receiver_country" default:"CH"`
PayeeName string `yaml:"payee_name" default:"Pia-Maria Rutschmann-Schnyder"`
PayeeStreet string `yaml:"payee_street" default:"Grosse Marktgasse"`
PayeeNumber string `yaml:"payee_number" default:"28"`
PayeeZIPCode string `yaml:"payee_zip_code" default:"9400"`
PayeePlace string `yaml:"payee_place" default:"Rorschach"`
PayeeCountry string `yaml:"payee_country" default:"CH"`
Reference string `yaml:"reference" default:"21 00000 00003 13947 14300 09017"`
AdditionalInfo string `yaml:"additional_info" default:"Rechnung Nr. 3139 für Gartenarbeiten"`
Amount string `yaml:"amount" default:"3 949.75"`
Currency string `yaml:"currency" default:"CHF"`
}
func New(amount string, receiver model.Entity, payee *model.Entity) Invoice {
i := Invoice{
ReceiverIBAN: receiver.IBAN,
IsQrIBAN: false,
ReceiverName: receiver.Name,
ReceiverStreet: receiver.Address.Street,
ReceiverNumber: receiver.Address.Number,
ReceiverZIPCode: receiver.Address.ZIPCode,
ReceiverPlace: receiver.Address.Place,
ReceiverCountry: receiver.Address.Country,
Reference: generateReference(),
Amount: amount,
Currency: "CHF",
}
if payee != nil {
i.PayeeName = payee.Name
i.PayeeStreet = payee.Address.Street
i.PayeeNumber = payee.Address.Number
i.PayeeZIPCode = payee.Address.ZIPCode
i.PayeePlace = payee.Address.Place
i.PayeeCountry = payee.Address.Country
}
return i
}
func (i Invoice) GetQR() (src template.URL, err error) {
content, err := i.qrContent()
if err != nil {
return
}
qr, err := qrcode.New(content, qrcode.Medium)
if err != nil {
return
}
qr.DisableBorder = true
png, err := qr.PNG(512)
if err != nil {
return
}
b64 := base64.StdEncoding.EncodeToString(png)
src = template.URL(fmt.Sprintf(`data:image/png;base64,%s`, b64))
return
}
// noPayee returns true if no fields of the payee are set
func (i Invoice) noPayee() bool {
return i.PayeeName == "" && i.PayeeStreet == "" && i.PayeeZIPCode == "" && i.PayeePlace == ""
}
func (i Invoice) qrContent() (string, error) {
qrTpl := `SPC\r
0200\r
1\r
{{ .iban }}\r
S\r
{{ .inv.ReceiverName }}\r
{{ .inv.ReceiverStreet }}\r
{{ .inv.ReceiverNumber }}\r
{{ .inv.ReceiverZIPCode }}\r
{{ .inv.ReceiverPlace }}\r
{{ .inv.ReceiverCountry }}\r
\r
\r
\r
\r
\r
\r
\r
{{ .amount }}\r
{{ .inv.Currency }}\r
{{ .payeeAdrType }}\r
{{ .inv.PayeeName }}\r
{{ .inv.PayeeStreet }}\r
{{ .inv.PayeeNumber }}\r
{{ .inv.PayeeZIPCode }}\r
{{ .inv.PayeePlace }}\r
{{ .inv.PayeeCountry }}\r
{{ .refType }}\r
{{ .reference }}\r
{{ .inv.AdditionalInfo }}\r
EPD
`
refType := "QRR"
if !i.IsQrIBAN {
refType = "SCOR"
}
payeeAdrType := "S"
if i.noPayee() {
payeeAdrType = ""
}
data := map[string]any{
"inv": i,
"iban": strings.ReplaceAll(i.ReceiverIBAN, " ", ""),
"amount": strings.ReplaceAll(i.Amount, " ", ""),
"payeeAdrType": payeeAdrType,
"reference": strings.ReplaceAll(i.Reference, " ", ""),
"refType": refType,
}
tpl, err := template.New("qr-content").Parse(qrTpl)
if err != nil {
return "", fmt.Errorf("error while creating qr template: %s", err)
}
var buf bytes.Buffer
if err := tpl.Execute(&buf, data); err != nil {
return "", fmt.Errorf("error while applying data to qr template: %s", err)
}
rsl := strings.ReplaceAll(buf.String(), "\\r", "\r")
return rsl, nil
}