Compare commits
No commits in common. "621597b52b3fbadaa097d31760eef2d5b19a50de" and "7471882878f7afdbc436bba7b70bb31546e860cc" have entirely different histories.
621597b52b
...
7471882878
|
|
@ -1,5 +1,6 @@
|
|||
mkinvoice
|
||||
makefont/makefont
|
||||
temp/*
|
||||
qr-images/*
|
||||
output/*
|
||||
yaml/*
|
||||
|
|
|
|||
|
|
@ -1,27 +1,26 @@
|
|||
---
|
||||
metadata:
|
||||
invoice_nr: 123456789
|
||||
invoice_date: 11. Dezember 2020
|
||||
invoice_nr: 6060900045
|
||||
invoice_info: Rechnung Nummer 6060900045
|
||||
invoice_date: 2. April 2020
|
||||
vat: 7.7
|
||||
account: CH92 0023 5235 5662 3601 G
|
||||
vat_number: CHE-105.687.350 MWST
|
||||
due_date: 2019-10-31
|
||||
sender_address:
|
||||
name: nbit Informatik GmbH
|
||||
street: Kirchweg
|
||||
streetnumber: 2
|
||||
street: Kirchweg 2
|
||||
zip: 3510
|
||||
city: Konolfingen
|
||||
tel_no: +41 31 792 00 40
|
||||
email: joerg.lehmann@nbit.ch
|
||||
billing_address:
|
||||
name: Coopers Group GmbH
|
||||
street: Seestrasse
|
||||
streetnumber: 72b
|
||||
street: Seestrasse 72b
|
||||
zip: 6052
|
||||
city: Hergiswil
|
||||
invoice_items:
|
||||
- text: Arbeitseinsatz von Jörg Lehmann als Linux Engineer bei Post_CH_AG gemäss IT Beratungsdienstleistungsvertrag vom 2.7.2020
|
||||
quantity: 123.4
|
||||
price_per_unit: 32.10
|
||||
- text: Arbeitseinsatz von Jörg Lehmann als Linux Engineer bei Post_CH_AG gemäss IT Beratungsdienstleistungsvertrag vom 28.1.2020
|
||||
quantity: 142
|
||||
price_per_unit: 115
|
||||
- text:
|
||||
- text: Monat Dezember 2020, Stunden gemäss beigelegtem Zeitnachweis
|
||||
- text: Monat Mai 2020, Stunden gemäss beigelegtem Zeitnachweis
|
||||
|
|
@ -5,10 +5,12 @@ purpose to generate PDF-Invoices for nbit Informatik GmbH
|
|||
|
||||
Font File can be generated with makefont
|
||||
|
||||
To generate the QRCode, following library is used:
|
||||
https://github.com/stapelberg/qrbill
|
||||
Payment Slip is generated using https://github.com/claudep/swiss-qr-bill
|
||||
|
||||
Installation / Upgrade
|
||||
$ pip install --user qrbill -U
|
||||
|
||||
Usage (Example):
|
||||
$ ./mkinvoice yaml/123456.yml
|
||||
|
||||
Joerg Lehmann, December 2020
|
||||
Joerg Lehmann, April 2020
|
||||
|
|
|
|||
16
go.mod
16
go.mod
|
|
@ -1,16 +0,0 @@
|
|||
module gitlab.com/drpuur/mkinvoice
|
||||
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/jung-kurt/gofpdf v1.16.2
|
||||
github.com/stapelberg/qrbill v0.1.9
|
||||
golang.org/x/text v0.3.7
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/ajstarks/svgo v0.0.0-20200725142600-7a3c8b57fecb // indirect
|
||||
github.com/makiuchi-d/gozxing v0.0.0-20200903113411-25f730ed83da // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
)
|
||||
32
go.sum
32
go.sum
|
|
@ -1,32 +0,0 @@
|
|||
github.com/ajstarks/svgo v0.0.0-20200725142600-7a3c8b57fecb h1:EVl3FJLQCzSbgBezKo/1A4ADnJ4mtJZ0RvnNzDJ44nY=
|
||||
github.com/ajstarks/svgo v0.0.0-20200725142600-7a3c8b57fecb/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/jung-kurt/gofpdf v1.16.2 h1:jgbatWHfRlPYiK85qgevsZTHviWXKwB1TTiKdz5PtRc=
|
||||
github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/YmFV8S2vmK0=
|
||||
github.com/makiuchi-d/gozxing v0.0.0-20200903113411-25f730ed83da h1:OgNu1PPD9EvZckyKDAc8DA4KymNXuc6vaCLsdOGyjOE=
|
||||
github.com/makiuchi-d/gozxing v0.0.0-20200903113411-25f730ed83da/go.mod h1:WoI7z45M7ZNA5BJxiJHaB+x7+k8S/3phW5Y13IR4yWY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
|
||||
github.com/stapelberg/qrbill v0.1.9 h1:HMNta6gG/ljRame+Q7C6AUb/eFnLygqpNNKjICmVBxc=
|
||||
github.com/stapelberg/qrbill v0.1.9/go.mod h1:+R3uLeV1AjSSHsJ7vdNDA/meZ/PSfQ06TGOO7yFp4EI=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
width="100"
|
||||
height="60"
|
||||
id="svg2">
|
||||
<defs
|
||||
id="defs4">
|
||||
<marker
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
id="Scissors"
|
||||
style="overflow:visible">
|
||||
<path
|
||||
d="M 9.0898857,-3.6061018 C 8.1198849,-4.7769976 6.3697607,-4.7358294 5.0623558,-4.2327734 l -8.2124046,3.0779029 c -2.3882933,-1.3067135 -4.7482873,-0.9325372 -4.7482873,-1.5687873 0,-0.4973164 0.4566662,-0.3883222 0.3883068,-1.6831941 -0.065635,-1.2432767 -1.3635771,-2.1630796 -2.5903987,-2.0816435 -1.227271,-0.00735 -2.499439,0.9331613 -2.510341,2.2300611 -0.09143,1.3063864 1.007209,2.5196896 2.306764,2.6052316 1.5223406,0.2266616 4.218258,-0.6955566 5.482945,1.57086006 -0.9422847,1.73825774 -2.6140244,1.74307674 -4.1255107,1.65607034 -1.2548743,-0.072235 -2.7620933,0.2873979 -3.3606483,1.5208605 -0.578367,1.1820862 -0.0112,2.8646022 1.316749,3.226412 1.3401912,0.4918277 3.1806689,-0.129711 3.4993722,-1.6707242 0.2456585,-1.187823 -0.5953659,-1.7459574 -0.2725074,-2.1771537 0.2436135,-0.32536 1.7907806,-0.1368452 4.5471053,-1.3748244 L 5.6763468,4.2330688 C 6.8000164,4.5467672 8.1730685,4.5362646 9.1684433,3.4313614 L -0.05164093,-0.05372222 9.0898857,-3.6061018 z m -18.3078016,-1.900504 c 1.294559,0.7227998 1.1888392,2.6835702 -0.1564272,3.0632889 -1.2165179,0.423661 -2.7710269,-0.7589694 -2.3831779,-2.0774648 0.227148,-1.0818519 1.653387,-1.480632 2.5396051,-0.9858241 z m 0.056264,8.0173649 c 1.3508301,0.4988648 1.1214429,2.7844356 -0.2522207,3.091609 -0.9110594,0.3163391 -2.2135494,-0.1387976 -2.3056964,-1.2121394 -0.177609,-1.305055 1.356085,-2.4841482 2.5579171,-1.8794696 z"
|
||||
id="schere"
|
||||
style="marker-start:none" />
|
||||
</marker>
|
||||
</defs>
|
||||
<g
|
||||
transform="translate(0,-992.36218)"
|
||||
id="layer1">
|
||||
<g
|
||||
transform="matrix(4.2610846,-1.2351263,1.2351263,4.2610846,-1337.7659,-2994.9736)"
|
||||
id="g4593">
|
||||
<path
|
||||
d="m 59.731665,956.40057 c -0.609784,-1.39286 -2.303491,-1.83556 -3.698902,-1.71226 l -8.742604,0.69566 c -1.935736,-1.91425 -4.307466,-2.2049 -4.132136,-2.81651 0.137044,-0.47806 0.545993,-0.24745 0.837105,-1.51102 0.279511,-1.21323 -0.714709,-2.45509 -1.916471,-2.71488 -1.177727,-0.34526 -2.659814,0.20827 -3.027677,1.45195 -0.447882,1.23061 0.273869,2.69969 1.499535,3.14003 1.400938,0.6374 4.246607,0.49379 4.837778,3.02096 -1.384807,1.41129 -2.993148,0.95525 -4.422137,0.4551 -1.186382,-0.41524 -2.734347,-0.48487 -3.649629,0.53589 -0.881717,0.97694 -0.800158,2.7506 0.376675,3.46434 1.15277,0.8421 3.093263,0.7518 3.824279,-0.64172 0.563472,-1.07414 -0.09119,-1.84242 0.337995,-2.16796 0.32384,-0.24563 1.759155,0.36194 4.749906,-0.0686 l 7.684693,5.46398 c 0.993719,0.61119 2.316503,0.97947 3.577814,0.19164 l -7.902726,-5.8909 9.766502,-0.89574 z m -17.075242,-6.87194 c 1.045256,1.05155 0.403306,2.90727 -0.994512,2.90157 -1.286163,0.072 -2.454591,-1.49318 -1.718425,-2.65375 0.516476,-0.97737 1.997384,-0.96769 2.712937,-0.24782 z m -2.155235,7.72245 c 1.161058,0.85179 0.310724,2.98566 -1.0944,2.9024 -0.962958,0.053 -2.089598,-0.7434 -1.882399,-1.80058 0.188897,-1.30347 1.988129,-2.01427 2.976799,-1.10182 z"
|
||||
id="path4599"
|
||||
style="marker-start:none" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.9 KiB |
281
mkinvoice.go
281
mkinvoice.go
|
|
@ -1,41 +1,39 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/jung-kurt/gofpdf"
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
"gopkg.in/yaml.v2"
|
||||
"image/png"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/stapelberg/qrbill"
|
||||
)
|
||||
|
||||
// Metadata type
|
||||
type Metadata struct {
|
||||
InvoiceNr string `yaml:"invoice_nr"`
|
||||
InvoiceInfo string `yaml:"invoice_info"`
|
||||
InvoiceDate string `yaml:"invoice_date"`
|
||||
Vat float64 `yaml:"vat"`
|
||||
Account string `yaml:"account"`
|
||||
DueDate string `yaml:"due_date"`
|
||||
VatNumber string `yaml:"vat_number"`
|
||||
}
|
||||
|
||||
// Address type
|
||||
type Address struct {
|
||||
Name string `yaml:"name"`
|
||||
Street string `yaml:"street"`
|
||||
StreetNumber string `yaml:"streetnumber"`
|
||||
Zip string `yaml:"zip"`
|
||||
City string `yaml:"city"`
|
||||
TelNo string `yaml:"tel_no"`
|
||||
Email string `yaml:"email"`
|
||||
Name string `yaml:"name"`
|
||||
Street string `yaml:"street"`
|
||||
Zip string `yaml:"zip"`
|
||||
City string `yaml:"city"`
|
||||
TelNo string `yaml:"tel_no"`
|
||||
Email string `yaml:"email"`
|
||||
}
|
||||
|
||||
// InvoiceItem type
|
||||
|
|
@ -65,19 +63,11 @@ var totalNetAmount float64
|
|||
var totalInvoiceAmount float64
|
||||
|
||||
const defaultFontSize = 9
|
||||
const smallFontSize = 7
|
||||
const paymentSlipTitleFontSize = 11
|
||||
const paymentSlipHeadingFontSize = 6
|
||||
const paymentSlipValuesFontSize = 8
|
||||
const marginTop = 7
|
||||
const logoTop = 6
|
||||
const logoHeight = 20
|
||||
const lineSpacing = 5
|
||||
const lineSpacingTextsmall = 3
|
||||
const lineSpacingSmall = 4.5
|
||||
const lineSpacingPaymentSlipBig = 6
|
||||
const lineSpacingPaymentSlipBelowHeading = 2.8
|
||||
const lineSpacingPaymentSlipSmall = 3.175
|
||||
const lineSpacingSmall = 3.5
|
||||
const addressTop = 50
|
||||
const metadataTopFirstPage = 70
|
||||
const metadataTopNotFirstPage = 30
|
||||
|
|
@ -96,29 +86,17 @@ const tabstopPricePerUnit = tabstopRight - widthPrice - widthPricePerUnit
|
|||
const tabstopPrice = tabstopRight - widthPrice
|
||||
const itemsTopFirstPage = 105
|
||||
const itemsTopNotFirstPage = 50
|
||||
const totalsTop = 158
|
||||
const totalsTop = 165
|
||||
const maxYPos = 265
|
||||
const paymentSlipTop = 193.5
|
||||
const paymentSlipWaehrung = 270
|
||||
const paymentSlipDashLeft = 60.5
|
||||
const tabstopPaymentSlipLeft = 5
|
||||
const tabstopPaymentSlipLeft2 = 17
|
||||
const tabstopPaymentSlipLeft3 = 40
|
||||
const tabstopPaymentSlipMiddle = 68
|
||||
const tabstopPaymentSlipMiddle2 = 80
|
||||
const tabstopPaymentSlipRight = 125
|
||||
const scissorsTop = 200
|
||||
const widthA4 = 210
|
||||
const lengthA4 = 297
|
||||
|
||||
func round5rappen(f float64) float64 {
|
||||
return (math.Round(f*20) / 20)
|
||||
}
|
||||
|
||||
func floatToString(f float64, sep string) string {
|
||||
func floatToString(f float64) string {
|
||||
p := message.NewPrinter(language.English)
|
||||
s := strings.ReplaceAll(p.Sprintf("%.2f", f), ",", sep)
|
||||
//fmt.Printf("--- s: @%s@\n", s)
|
||||
s := strings.ReplaceAll(p.Sprintf("%.2f", f), ",", "'")
|
||||
fmt.Printf("--- s: @%s@\n", s)
|
||||
return s
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +113,8 @@ func readInvoiceData(filename string) {
|
|||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
|
||||
//fmt.Printf("--- t:\n%v\n\n", invoiceData)
|
||||
fmt.Printf("--- t:\n%v\n\n", invoiceData)
|
||||
fmt.Printf("%s\n", invoiceData.BillingAddress.Name)
|
||||
}
|
||||
|
||||
func writeText(x float64, y float64, w float64, text string, alignStr ...string) {
|
||||
|
|
@ -148,20 +127,9 @@ func writeText(x float64, y float64, w float64, text string, alignStr ...string)
|
|||
pdf.CellFormat(w, 11, tr(text), "", 0, align, false, 0, "")
|
||||
}
|
||||
|
||||
func writeTextsmall(x float64, y float64, w float64, text string, alignStr ...string) {
|
||||
tr := pdf.UnicodeTranslatorFromDescriptor("")
|
||||
align := "LT"
|
||||
if len(alignStr) > 0 {
|
||||
align = alignStr[0]
|
||||
}
|
||||
pdf.SetXY(x, y)
|
||||
pdf.CellFormat(w, 6, tr(text), "", 0, align, false, 0, "")
|
||||
}
|
||||
|
||||
func setupInvoice() {
|
||||
pdf = gofpdf.New("P", "mm", "A4", "")
|
||||
pdf.SetMargins(0, 0, 0)
|
||||
pdf.SetAutoPageBreak(false, 0)
|
||||
pdf.SetFontLocation("fonts")
|
||||
pdf.AddFont("Dejavusans", "", "DejaVuSans.json")
|
||||
pdf.AddFont("Dejavusans-Bold", "", "DejaVuSans-Bold.json")
|
||||
|
|
@ -179,7 +147,7 @@ func printPageHeader(firstPage bool) {
|
|||
writeText(tabstopLeft, yPos, 0, invoiceData.SenderAddress.Name)
|
||||
pdf.SetFont("Dejavusans", "", defaultFontSize)
|
||||
yPos = yPos + lineSpacing
|
||||
writeText(tabstopLeft, yPos, 0, invoiceData.SenderAddress.Street+" "+invoiceData.SenderAddress.StreetNumber)
|
||||
writeText(tabstopLeft, yPos, 0, invoiceData.SenderAddress.Street)
|
||||
yPos = yPos + lineSpacing
|
||||
writeText(tabstopLeft, yPos, 0, invoiceData.SenderAddress.Zip+" "+invoiceData.SenderAddress.City)
|
||||
if firstPage {
|
||||
|
|
@ -205,7 +173,7 @@ func printAddress() {
|
|||
yPos = addressTop
|
||||
writeText(tabstopAddress, yPos, 0, invoiceData.BillingAddress.Name)
|
||||
yPos = yPos + lineSpacing
|
||||
writeText(tabstopAddress, yPos, 0, invoiceData.BillingAddress.Street+" "+invoiceData.BillingAddress.StreetNumber)
|
||||
writeText(tabstopAddress, yPos, 0, invoiceData.BillingAddress.Street)
|
||||
yPos = yPos + lineSpacing
|
||||
writeText(tabstopAddress, yPos, 0, invoiceData.BillingAddress.Zip+" "+invoiceData.BillingAddress.City)
|
||||
}
|
||||
|
|
@ -230,7 +198,7 @@ func printMetadata(firstPage bool) {
|
|||
pdf.SetFont("Dejavusans", "", defaultFontSize)
|
||||
writeText(tabstopMetadata, yPos, 0, invoiceData.Metadata.InvoiceDate)
|
||||
yPos = yPos + lineSpacing
|
||||
}
|
||||
}
|
||||
pdf.SetFont("Dejavusans-Bold", "", defaultFontSize)
|
||||
writeText(tabstopLeft, yPos, 0, "Seite:")
|
||||
pdf.SetFont("Dejavusans", "", defaultFontSize)
|
||||
|
|
@ -265,24 +233,13 @@ func maybeNewPage() {
|
|||
func printItems() {
|
||||
for _, i := range invoiceData.InvoiceItems {
|
||||
if i.Quantity != 0 {
|
||||
writeText(tabstopQuantity, yPos, widthQuantity, fmt.Sprintf("%g", i.Quantity), "TR")
|
||||
writeText(tabstopQuantity, yPos, widthQuantity, fmt.Sprintf("%.1f", i.Quantity), "TR")
|
||||
writeText(tabstopPricePerUnit, yPos, widthPricePerUnit, fmt.Sprintf("%.2f", i.PricePerUnit), "TR")
|
||||
itemNetAmount := round5rappen(i.Quantity * i.PricePerUnit)
|
||||
totalNetAmount = totalNetAmount + itemNetAmount
|
||||
writeText(tabstopPrice, yPos, widthPrice, floatToString(itemNetAmount, "'"), "TR")
|
||||
writeText(tabstopPrice, yPos, widthPrice, floatToString(itemNetAmount), "TR")
|
||||
}
|
||||
if strings.HasPrefix(i.Text, "@") {
|
||||
pdf.SetFont("Dejavusans", "", smallFontSize)
|
||||
textwithoutprefix := i.Text[1:]
|
||||
lines := pdf.SplitText(textwithoutprefix, widthItemText)
|
||||
for _, il := range lines {
|
||||
totalItemLines = totalItemLines + 1
|
||||
writeTextsmall(tabstopLeft, yPos, 0, strings.ReplaceAll(il, "_", " "))
|
||||
yPos = yPos + lineSpacingTextsmall
|
||||
maybeNewPage()
|
||||
}
|
||||
pdf.SetFont("Dejavusans", "", defaultFontSize)
|
||||
} else if i.Text != "" {
|
||||
if i.Text != "" {
|
||||
lines := pdf.SplitText(i.Text, widthItemText)
|
||||
for _, il := range lines {
|
||||
totalItemLines = totalItemLines + 1
|
||||
|
|
@ -300,186 +257,53 @@ func printItems() {
|
|||
|
||||
func printTotals() {
|
||||
yPos = totalsTop
|
||||
pdf.Line(tabstopRight-widthPrice, yPos+(lineSpacing/2), tabstopRight, yPos+(lineSpacing/2))
|
||||
pdf.Line(tabstopRight-widthPrice, yPos, tabstopRight, yPos)
|
||||
pdf.SetFont("Dejavusans-Bold", "", defaultFontSize)
|
||||
yPos = yPos + lineSpacing
|
||||
writeText(tabstopLeft, yPos, 0, "Summe")
|
||||
pdf.SetFont("Dejavusans", "", defaultFontSize)
|
||||
writeText(tabstopPrice, yPos, widthPrice, floatToString(totalNetAmount, "'"), "TR")
|
||||
writeText(tabstopLeft, yPos, 0, "Netto Betrag")
|
||||
writeText(tabstopPrice, yPos, widthPrice, floatToString(totalNetAmount), "TR")
|
||||
yPos = yPos + lineSpacing
|
||||
yPos = yPos + lineSpacing
|
||||
pdf.SetFont("Dejavusans-Bold", "", defaultFontSize)
|
||||
writeText(tabstopLeft, yPos, 0, fmt.Sprintf("MwSt. %.1f%%", invoiceData.Metadata.Vat))
|
||||
mwstAmount := round5rappen(totalNetAmount * invoiceData.Metadata.Vat / 100)
|
||||
pdf.SetFont("Dejavusans", "", defaultFontSize)
|
||||
writeText(tabstopPrice, yPos, widthPrice, floatToString(mwstAmount, "'"), "TR")
|
||||
writeText(tabstopPrice, yPos, widthPrice, floatToString(mwstAmount), "TR")
|
||||
yPos = yPos + lineSpacing
|
||||
yPos = yPos + lineSpacing
|
||||
pdf.SetFont("Dejavusans-Bold", "", defaultFontSize)
|
||||
writeText(tabstopLeft, yPos, 0, "Rechnungbetrag in CHF")
|
||||
writeText(tabstopLeft, yPos, 0, "Total Betrag sFr.")
|
||||
totalInvoiceAmount = totalNetAmount + mwstAmount
|
||||
writeText(tabstopPrice, yPos, widthPrice, floatToString(totalNetAmount+mwstAmount, "'"), "TR")
|
||||
yPos = yPos + lineSpacingSmall
|
||||
pdf.SetFont("Dejavusans", "", smallFontSize)
|
||||
writeText(tabstopLeft, yPos, 0, "30 Tage netto")
|
||||
}
|
||||
|
||||
func qrchFromInvoiceData(id InvoiceData) *qrbill.QRCH {
|
||||
return &qrbill.QRCH{
|
||||
CdtrInf: qrbill.QRCHCdtrInf{
|
||||
IBAN: id.Metadata.Account,
|
||||
Cdtr: qrbill.Address{
|
||||
AdrTp: qrbill.AddressType(string(qrbill.AddressTypeStructured)),
|
||||
Name: id.SenderAddress.Name,
|
||||
StrtNmOrAdrLine1: id.SenderAddress.Street,
|
||||
BldgNbOrAdrLine2: id.SenderAddress.StreetNumber,
|
||||
PstCd: id.SenderAddress.Zip,
|
||||
TwnNm: id.SenderAddress.City,
|
||||
Ctry: "CH",
|
||||
},
|
||||
},
|
||||
CcyAmt: qrbill.QRCHCcyAmt{
|
||||
Amt: fmt.Sprintf("%.2f", totalInvoiceAmount),
|
||||
Ccy: "CHF",
|
||||
},
|
||||
UltmtDbtr: qrbill.Address{
|
||||
AdrTp: qrbill.AddressType(string(qrbill.AddressTypeStructured)),
|
||||
Name: id.BillingAddress.Name,
|
||||
StrtNmOrAdrLine1: id.BillingAddress.Street,
|
||||
BldgNbOrAdrLine2: id.BillingAddress.StreetNumber,
|
||||
PstCd: id.BillingAddress.Zip,
|
||||
TwnNm: id.BillingAddress.City,
|
||||
Ctry: "CH",
|
||||
},
|
||||
RmtInf: qrbill.QRCHRmtInf{
|
||||
Tp: "NON", // Reference type
|
||||
Ref: "", // Reference
|
||||
AddInf: qrbill.QRCHRmtInfAddInf{
|
||||
Ustrd: "Rechnung Nummer " + id.Metadata.InvoiceNr,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func printPaymentSlip() {
|
||||
var opt gofpdf.ImageOptions
|
||||
|
||||
pdf.SetDashPattern([]float64{0.8, 0.8}, 0)
|
||||
pdf.Line(0, paymentSlipTop, widthA4, paymentSlipTop)
|
||||
pdf.Line(paymentSlipDashLeft, paymentSlipTop, paymentSlipDashLeft, lengthA4)
|
||||
opt.ImageType = "png"
|
||||
pdf.ImageOptions("logos/scissors-rotated.png", paymentSlipDashLeft-1.5, paymentSlipTop+10, 3, 3, false, opt, 0, "")
|
||||
|
||||
pdf.SetFont("Helvetica", "B", paymentSlipTitleFontSize)
|
||||
yPos = paymentSlipTop + 7
|
||||
writeText(tabstopPaymentSlipLeft, yPos, 0, "Empfangsschein")
|
||||
yPos = yPos + lineSpacingPaymentSlipBig
|
||||
pdf.SetFont("Helvetica", "B", paymentSlipHeadingFontSize)
|
||||
writeText(tabstopPaymentSlipLeft, yPos, 0, "Konto / Zahlbar an")
|
||||
yPos = yPos + lineSpacingPaymentSlipBelowHeading
|
||||
pdf.SetFont("Helvetica", "", paymentSlipValuesFontSize)
|
||||
writeText(tabstopPaymentSlipLeft, yPos, 0, invoiceData.Metadata.Account)
|
||||
yPos = yPos + lineSpacingPaymentSlipSmall
|
||||
writeText(tabstopPaymentSlipLeft, yPos, 0, invoiceData.SenderAddress.Name)
|
||||
yPos = yPos + lineSpacingPaymentSlipSmall
|
||||
writeText(tabstopPaymentSlipLeft, yPos, 0, invoiceData.SenderAddress.Street+" "+invoiceData.SenderAddress.StreetNumber)
|
||||
yPos = yPos + lineSpacingPaymentSlipSmall
|
||||
writeText(tabstopPaymentSlipLeft, yPos, 0, invoiceData.SenderAddress.Zip+" "+invoiceData.SenderAddress.City)
|
||||
yPos = yPos + lineSpacingPaymentSlipSmall
|
||||
yPos = yPos + lineSpacingPaymentSlipSmall
|
||||
pdf.SetFont("Helvetica", "B", paymentSlipHeadingFontSize)
|
||||
writeText(tabstopPaymentSlipLeft, yPos, 0, "Zahlbar durch")
|
||||
yPos = yPos + lineSpacingPaymentSlipBelowHeading
|
||||
pdf.SetFont("Helvetica", "", paymentSlipValuesFontSize)
|
||||
writeText(tabstopPaymentSlipLeft, yPos, 0, invoiceData.BillingAddress.Name)
|
||||
yPos = yPos + lineSpacingPaymentSlipSmall
|
||||
writeText(tabstopPaymentSlipLeft, yPos, 0, invoiceData.BillingAddress.Street+" "+invoiceData.BillingAddress.StreetNumber)
|
||||
yPos = yPos + lineSpacingPaymentSlipSmall
|
||||
writeText(tabstopPaymentSlipLeft, yPos, 0, invoiceData.BillingAddress.Zip+" "+invoiceData.BillingAddress.City)
|
||||
yPos = paymentSlipWaehrung
|
||||
pdf.SetFont("Helvetica", "B", paymentSlipHeadingFontSize)
|
||||
writeText(tabstopPaymentSlipLeft, yPos, 0, "Währung")
|
||||
writeText(tabstopPaymentSlipLeft2, yPos, 0, "Betrag")
|
||||
yPos = yPos + lineSpacingPaymentSlipBelowHeading
|
||||
pdf.SetFont("Helvetica", "", paymentSlipValuesFontSize)
|
||||
writeText(tabstopPaymentSlipLeft, yPos, 0, "CHF")
|
||||
writeText(tabstopPaymentSlipLeft2, yPos, 0, floatToString(totalInvoiceAmount, " "))
|
||||
yPos = yPos + lineSpacingPaymentSlipSmall
|
||||
yPos = yPos + lineSpacingPaymentSlipSmall
|
||||
pdf.SetFont("Helvetica", "B", paymentSlipHeadingFontSize)
|
||||
writeText(tabstopPaymentSlipLeft3, yPos, 0, "Annahmestelle")
|
||||
|
||||
pdf.SetFont("Helvetica", "B", paymentSlipTitleFontSize)
|
||||
yPos = paymentSlipTop + 7
|
||||
writeText(tabstopPaymentSlipMiddle, yPos, 0, "Zahlteil")
|
||||
yPos = paymentSlipWaehrung
|
||||
pdf.SetFont("Helvetica", "B", paymentSlipHeadingFontSize)
|
||||
writeText(tabstopPaymentSlipMiddle, yPos, 0, "Währung")
|
||||
writeText(tabstopPaymentSlipMiddle2, yPos, 0, "Betrag")
|
||||
yPos = yPos + lineSpacingPaymentSlipBelowHeading
|
||||
pdf.SetFont("Helvetica", "", paymentSlipValuesFontSize)
|
||||
writeText(tabstopPaymentSlipMiddle, yPos, 0, "CHF")
|
||||
writeText(tabstopPaymentSlipMiddle2, yPos, 0, floatToString(totalInvoiceAmount, " "))
|
||||
|
||||
yPos = paymentSlipTop + 7
|
||||
pdf.SetFont("Helvetica", "B", paymentSlipHeadingFontSize)
|
||||
writeText(tabstopPaymentSlipRight, yPos, 0, "Konto / Zahlbar an")
|
||||
yPos = yPos + lineSpacingPaymentSlipBelowHeading
|
||||
pdf.SetFont("Helvetica", "", paymentSlipValuesFontSize)
|
||||
writeText(tabstopPaymentSlipRight, yPos, 0, invoiceData.Metadata.Account)
|
||||
yPos = yPos + lineSpacingPaymentSlipSmall
|
||||
writeText(tabstopPaymentSlipRight, yPos, 0, invoiceData.SenderAddress.Name)
|
||||
yPos = yPos + lineSpacingPaymentSlipSmall
|
||||
writeText(tabstopPaymentSlipRight, yPos, 0, invoiceData.SenderAddress.Street+" "+invoiceData.SenderAddress.StreetNumber)
|
||||
yPos = yPos + lineSpacingPaymentSlipSmall
|
||||
writeText(tabstopPaymentSlipRight, yPos, 0, invoiceData.SenderAddress.Zip+" "+invoiceData.SenderAddress.City)
|
||||
yPos = yPos + (lineSpacingPaymentSlipSmall * 1.5)
|
||||
pdf.SetFont("Helvetica", "B", paymentSlipHeadingFontSize)
|
||||
writeText(tabstopPaymentSlipRight, yPos, 0, "Zusätzliche Informationen")
|
||||
yPos = yPos + lineSpacingPaymentSlipBelowHeading
|
||||
pdf.SetFont("Helvetica", "", paymentSlipValuesFontSize)
|
||||
writeText(tabstopPaymentSlipRight, yPos, 0, "Rechnung Nummer "+invoiceData.Metadata.InvoiceNr)
|
||||
yPos = yPos + (lineSpacingPaymentSlipSmall * 1.5)
|
||||
pdf.SetFont("Helvetica", "B", paymentSlipHeadingFontSize)
|
||||
writeText(tabstopPaymentSlipRight, yPos, 0, "Zahlbar durch")
|
||||
yPos = yPos + lineSpacingPaymentSlipBelowHeading
|
||||
pdf.SetFont("Helvetica", "", paymentSlipValuesFontSize)
|
||||
writeText(tabstopPaymentSlipRight, yPos, 0, invoiceData.BillingAddress.Name)
|
||||
yPos = yPos + lineSpacingPaymentSlipSmall
|
||||
writeText(tabstopPaymentSlipRight, yPos, 0, invoiceData.BillingAddress.Street+" "+invoiceData.BillingAddress.StreetNumber)
|
||||
yPos = yPos + lineSpacingPaymentSlipSmall
|
||||
writeText(tabstopPaymentSlipRight, yPos, 0, invoiceData.BillingAddress.Zip+" "+invoiceData.BillingAddress.City)
|
||||
writeText(tabstopPrice, yPos, widthPrice, floatToString(totalNetAmount+mwstAmount), "TR")
|
||||
}
|
||||
|
||||
func printQR() {
|
||||
var opt gofpdf.ImageOptions
|
||||
|
||||
var b []byte
|
||||
var err error
|
||||
qrch := qrchFromInvoiceData(invoiceData)
|
||||
bill, err := qrch.Encode()
|
||||
|
||||
code, err := bill.EncodeToImage()
|
||||
cmd := exec.Command(filepath.Join(progDir, "qrbill.sh"),
|
||||
"--account", invoiceData.Metadata.Account,
|
||||
"--amount", floatToString(totalInvoiceAmount),
|
||||
"--creditor-name", invoiceData.SenderAddress.Name,
|
||||
"--creditor-street", invoiceData.SenderAddress.Street,
|
||||
"--creditor-postalcode", invoiceData.SenderAddress.Zip,
|
||||
"--creditor-city", invoiceData.SenderAddress.City,
|
||||
"--extra-infos", invoiceData.Metadata.InvoiceInfo,
|
||||
"--debtor-name", invoiceData.BillingAddress.Name,
|
||||
"--debtor-street", invoiceData.BillingAddress.Street,
|
||||
"--debtor-postalcode", invoiceData.BillingAddress.Zip,
|
||||
"--debtor-city", invoiceData.BillingAddress.City,
|
||||
"--due-date", invoiceData.Metadata.DueDate,
|
||||
"--language", "de")
|
||||
cmd.Env = append(os.Environ(),
|
||||
"INVNO="+invoiceData.Metadata.InvoiceNr,
|
||||
)
|
||||
stdoutStderr, err := cmd.CombinedOutput()
|
||||
fmt.Printf("%s\n", stdoutStderr)
|
||||
if err != nil {
|
||||
log.Printf("%s", err)
|
||||
return
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := png.Encode(&buf, code); err != nil {
|
||||
log.Printf("%s", err)
|
||||
return
|
||||
}
|
||||
b = buf.Bytes()
|
||||
|
||||
err = ioutil.WriteFile("qr-images/"+invoiceData.Metadata.InvoiceNr+".png", b, 0644)
|
||||
if err != nil {
|
||||
// handle error
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("%s\n", stdoutStderr)
|
||||
|
||||
opt.ImageType = "png"
|
||||
opt.ReadDpi = false
|
||||
|
||||
pdf.ImageOptions("qr-images/"+invoiceData.Metadata.InvoiceNr+".png", tabstopPaymentSlipMiddle-2, paymentSlipTop+13, 56, 56, false, opt, 0, "")
|
||||
opt.ReadDpi = true
|
||||
pdf.ImageOptions("qr-images/"+invoiceData.Metadata.InvoiceNr+".png", 0, 200, -1, -1, false, opt, 0, "")
|
||||
}
|
||||
|
||||
func CreateInvoice() {
|
||||
|
|
@ -493,7 +317,6 @@ func CreateInvoice() {
|
|||
printItemsHeader()
|
||||
printItems()
|
||||
printTotals()
|
||||
printPaymentSlip()
|
||||
printQR()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
#!/bin/bash
|
||||
mydir="$(dirname $0)"
|
||||
if [ -z "${INVNO}" ]; then
|
||||
echo "ERROR: INVNO must be set as ENV variable"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "INVNO: ${INVNO}"
|
||||
|
||||
# should be called with following arguments (example)
|
||||
# INVNO must be set as ENV variable
|
||||
|
||||
# --account "CH92 0023 5235 5662 3601 G"
|
||||
# --amount 123.00
|
||||
# --creditor-name "nbit Informatik GmbH"
|
||||
# --creditor-street "Kirchweg 2"
|
||||
# --creditor-postalcode "3510"
|
||||
# --creditor-city "Konolfingen"
|
||||
# --extra-infos "Rechnung Nummer ${INVNO}"
|
||||
# --debtor-name "Wilhelm Tell"
|
||||
# --debtor-street "Marktgasse 28"
|
||||
# --debtor-postalcode "9400"
|
||||
# --debtor-city "Rorschach"
|
||||
# --due-date "2019-10-31"
|
||||
# --language "de"
|
||||
|
||||
qrbill "$@" --output ${mydir}/temp/${INVNO}.svg
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ERROR: cannot create qrbill image"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
#convert ${mydir}/temp/${INVNO}.svg ${mydir}/qr-images/${INVNO}.jpg
|
||||
#inkscape ${mydir}/temp/${INVNO}.svg --export-width=794 --export-height=397 --export-filename ${mydir}/qr-images/${INVNO}.png
|
||||
inkscape ${mydir}/temp/${INVNO}.svg --export-dpi=300 --export-filename ${mydir}/qr-images/${INVNO}.png
|
||||
#cairosvg ${mydir}/temp/${INVNO}.svg -o ${mydir}/qr-images/${INVNO}.png
|
||||
if [ $? -eq 0 ]; then
|
||||
echo blabla
|
||||
#rm ${mydir}/temp/${INVNO}.svg
|
||||
fi
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/bash
|
||||
mydir="$(dirname $0)"
|
||||
export INVNO=123456789
|
||||
${mydir}/qrbill.sh --account "CH92 0023 5235 5662 3601 G" \
|
||||
--amount 123.00 \
|
||||
--creditor-name "nbit Informatik GmbH" \
|
||||
--creditor-street "Kirchweg 2" \
|
||||
--creditor-postalcode "3510" \
|
||||
--creditor-city "Konolfingen" \
|
||||
--extra-infos "Rechnung Nummer 123456789" \
|
||||
--debtor-name "Wilhelm Tell" \
|
||||
--debtor-street "Marktgasse 28" \
|
||||
--debtor-postalcode "9400" \
|
||||
--debtor-city "Rorschach" \
|
||||
--due-date "2019-10-31" \
|
||||
--language "de"
|
||||
Loading…
Reference in New Issue