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 }