first workable version, still to be tuned...
This commit is contained in:
parent
58b0759c78
commit
24e895102b
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
metadata:
|
||||
invoice_nr: 6060900045
|
||||
invoice_info: Rechnung Nummer 6060900045
|
||||
invoice_date: 2. April 2020
|
||||
vat: 7.7
|
||||
account: CH92 0023 5235 5662 3601 G
|
||||
due_date: 2019-10-31
|
||||
sender_address:
|
||||
name: nbit Informatik GmbH
|
||||
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 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 28.1.2020
|
||||
quantity: 142
|
||||
price_per_unit: 115
|
||||
- text:
|
||||
- text: Monat Mai 2020, Stunden gemäss beigelegtem Zeitnachweis
|
||||
|
|
@ -5,4 +5,12 @@ purpose to generate PDF-Invoices for nbit Informatik GmbH
|
|||
|
||||
Font File can be generated with makefont
|
||||
|
||||
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, April 2020
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
230
mkinvoice.go
230
mkinvoice.go
|
|
@ -2,49 +2,52 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/jung-kurt/gofpdf"
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
"gopkg.in/yaml.v2"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/jung-kurt/gofpdf"
|
||||
"gopkg.in/yaml.v2"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Metadata type
|
||||
type Metadata struct {
|
||||
InvoiceNr string
|
||||
InvoiceInfo string
|
||||
InvoiceDdate string
|
||||
Vat float64
|
||||
Account string
|
||||
DueDate string
|
||||
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"`
|
||||
}
|
||||
|
||||
// Address type
|
||||
type Address struct {
|
||||
Name string
|
||||
Street string
|
||||
Zip string
|
||||
City string
|
||||
TelNo string
|
||||
Email string
|
||||
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
|
||||
type InvoiceItem struct {
|
||||
Text string
|
||||
Quantity float64
|
||||
PicePerUnit float64
|
||||
Text string `yaml:"text"`
|
||||
Quantity float64 `yaml:"quantity"`
|
||||
PricePerUnit float64 `yaml:"price_per_unit"`
|
||||
}
|
||||
|
||||
// InvoiceData type
|
||||
type InvoiceData struct {
|
||||
Metadata Metadata
|
||||
SenderAddress Address
|
||||
BillingAddress Address
|
||||
Item []InvoiceItem
|
||||
Metadata Metadata `yaml:"metadata"`
|
||||
SenderAddress Address `yaml:"sender_address"`
|
||||
BillingAddress Address `yaml:"billing_address"`
|
||||
InvoiceItems []InvoiceItem `yaml:"invoice_items"`
|
||||
}
|
||||
|
||||
/* global variable declaration */
|
||||
|
|
@ -52,21 +55,46 @@ var pdf *gofpdf.Fpdf
|
|||
var yPos float64
|
||||
var invoiceData InvoiceData
|
||||
var progDir string
|
||||
var currentPage int
|
||||
var totalPages int
|
||||
var totalNetAmount float64
|
||||
var totalInvoiceAmount float64
|
||||
|
||||
const defaultFontSize = 9
|
||||
const fontSizeSmall = 7
|
||||
const marginTop = 7
|
||||
const logoTop = 6
|
||||
const logoHeight = 20
|
||||
const lineSpacing = 5
|
||||
const lineSpacingSmall = 3.5
|
||||
const addressTop = 50
|
||||
const line1Top = 100
|
||||
const metadataTop = 70
|
||||
const tabstopLeft = 20
|
||||
const tabstopLeftAlt = 32
|
||||
const tabstopMetadata = 60
|
||||
const tabstopAddress = 120
|
||||
const tabstopCount = 10
|
||||
const tabstopPrice = 10
|
||||
const tabstopTotal = 170
|
||||
const tabstopLogo = 155
|
||||
const tabstopRight = 200
|
||||
const widthItemText = 96
|
||||
const widthQuantity = 28
|
||||
const widthPricePerUnit = 28
|
||||
const widthPrice = 28
|
||||
const tabstopQuantity = tabstopRight - widthPrice - widthPricePerUnit - widthQuantity
|
||||
const tabstopPricePerUnit = tabstopRight - widthPrice - widthPricePerUnit
|
||||
const tabstopPrice = tabstopRight - widthPrice
|
||||
const itemsTop = 105
|
||||
const totalsTop = 150
|
||||
|
||||
func round5rappen(f float64) float64 {
|
||||
return (math.Round(f*20) / 20)
|
||||
}
|
||||
|
||||
func floatToString(f float64) string {
|
||||
p := message.NewPrinter(language.English)
|
||||
s := strings.ReplaceAll(p.Sprintf("%.2f", f), ",", "'")
|
||||
fmt.Printf("--- s: @%s@\n", s)
|
||||
return s
|
||||
}
|
||||
|
||||
func readInvoiceData(filename string) {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
|
|
@ -85,14 +113,14 @@ func readInvoiceData(filename string) {
|
|||
fmt.Printf("%s\n", invoiceData.BillingAddress.Name)
|
||||
}
|
||||
|
||||
func writeText(x float64, y float64, text string, alignStr ...string) {
|
||||
func writeText(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(0, 11, tr(text), "", 0, align, false, 0, "")
|
||||
pdf.CellFormat(w, 11, tr(text), "", 0, align, false, 0, "")
|
||||
}
|
||||
|
||||
func setupInvoice() {
|
||||
|
|
@ -101,46 +129,115 @@ func setupInvoice() {
|
|||
pdf.SetFontLocation("fonts")
|
||||
pdf.AddFont("Dejavusans", "", "DejaVuSans.json")
|
||||
pdf.AddFont("Dejavusans-Bold", "", "DejaVuSans-Bold.json")
|
||||
pdf.SetFont("Dejavusans", "", 10)
|
||||
pdf.SetFont("Dejavusans", "", defaultFontSize)
|
||||
currentPage = 0
|
||||
}
|
||||
|
||||
func printPageHeader(firstPage bool) {
|
||||
var opt gofpdf.ImageOptions
|
||||
|
||||
currentPage = currentPage + 1
|
||||
pdf.AddPage()
|
||||
yPos = marginTop
|
||||
pdf.SetFont("Dejavusans-Bold", "", 10)
|
||||
writeText(tabstopLeft, yPos, "nbit Informatik GmbH")
|
||||
pdf.SetFont("Dejavusans", "", 10)
|
||||
yPos = yPos + lineSpacing
|
||||
writeText(tabstopLeft, yPos, "Kirchweg 2")
|
||||
yPos = yPos + lineSpacing
|
||||
writeText(tabstopLeft, yPos, "3510 Konolfingen")
|
||||
yPos = yPos + lineSpacing
|
||||
yPos = yPos + lineSpacing
|
||||
writeText(tabstopLeft, yPos, "Tel.")
|
||||
writeText(tabstopLeftAlt, yPos, "+41 31 792 00 40")
|
||||
yPos = yPos + lineSpacing
|
||||
writeText(tabstopLeft, yPos, "EMail")
|
||||
writeText(tabstopLeftAlt, yPos, "joerg.lehmann@nbit.ch")
|
||||
yPos = yPos + lineSpacing
|
||||
pdf.SetFont("Dejavusans-Bold", "", fontSizeSmall)
|
||||
writeText(tabstopLeft, yPos, 0, "nbit Informatik GmbH")
|
||||
pdf.SetFont("Dejavusans", "", fontSizeSmall)
|
||||
yPos = yPos + lineSpacingSmall
|
||||
writeText(tabstopLeft, yPos, 0, "Kirchweg 2")
|
||||
yPos = yPos + lineSpacingSmall
|
||||
writeText(tabstopLeft, yPos, 0, "3510 Konolfingen")
|
||||
yPos = yPos + lineSpacingSmall
|
||||
yPos = yPos + lineSpacingSmall
|
||||
writeText(tabstopLeft, yPos, 0, "Tel.")
|
||||
writeText(tabstopLeftAlt, yPos, 0, "+41 31 792 00 40")
|
||||
yPos = yPos + lineSpacingSmall
|
||||
writeText(tabstopLeft, yPos, 0, "EMail")
|
||||
writeText(tabstopLeftAlt, yPos, 0, "joerg.lehmann@nbit.ch")
|
||||
yPos = yPos + lineSpacingSmall
|
||||
|
||||
opt.ImageType = "png"
|
||||
opt.ReadDpi = true
|
||||
pdf.ImageOptions("logos/nbit-logo.png", tabstopLogo, logoTop, 0, logoHeight, false, opt, 0, "")
|
||||
pdf.ImageOptions("logos/nbit-logo-40x20mm-400dpi.png", tabstopLogo, logoTop, 0, logoHeight, false, opt, 0, "")
|
||||
pdf.SetFont("Dejavusans", "", defaultFontSize)
|
||||
}
|
||||
|
||||
func printAddress() {
|
||||
fmt.Printf("Blabla: %s\n", invoiceData.BillingAddress.Name)
|
||||
yPos = addressTop
|
||||
writeText(tabstopAddress, yPos, invoiceData.BillingAddress.Name)
|
||||
writeText(tabstopAddress, yPos, 0, invoiceData.BillingAddress.Name)
|
||||
yPos = yPos + lineSpacing
|
||||
writeText(tabstopAddress, yPos, invoiceData.BillingAddress.Street)
|
||||
yPos = yPos + lineSpacing
|
||||
writeText(tabstopAddress, yPos, invoiceData.BillingAddress.Zip+" "+invoiceData.BillingAddress.City)
|
||||
writeText(tabstopAddress, yPos, 0, invoiceData.BillingAddress.Street)
|
||||
yPos = yPos + lineSpacing
|
||||
writeText(tabstopAddress, yPos, 0, invoiceData.BillingAddress.Zip+" "+invoiceData.BillingAddress.City)
|
||||
}
|
||||
|
||||
pdf.Line(tabstopLeft, line1Top, tabstopRight, line1Top)
|
||||
func printMetadataFirstPage() {
|
||||
yPos = metadataTop
|
||||
pdf.SetFont("Dejavusans-Bold", "", 16)
|
||||
writeText(tabstopLeft, yPos, 0, "Rechnung")
|
||||
yPos = yPos + lineSpacing
|
||||
yPos = yPos + lineSpacing
|
||||
pdf.SetFont("Dejavusans-Bold", "", defaultFontSize)
|
||||
writeText(tabstopLeft, yPos, 0, "Rechnungsnummer:")
|
||||
pdf.SetFont("Dejavusans", "", defaultFontSize)
|
||||
writeText(tabstopMetadata, yPos, 0, invoiceData.Metadata.InvoiceNr)
|
||||
yPos = yPos + lineSpacing
|
||||
pdf.SetFont("Dejavusans-Bold", "", defaultFontSize)
|
||||
writeText(tabstopLeft, yPos, 0, "Rechnungsdatum:")
|
||||
pdf.SetFont("Dejavusans", "", defaultFontSize)
|
||||
writeText(tabstopMetadata, yPos, 0, invoiceData.Metadata.InvoiceDate)
|
||||
}
|
||||
|
||||
func printItemsHeader() {
|
||||
yPos = itemsTop
|
||||
pdf.SetFont("Dejavusans-Bold", "", defaultFontSize)
|
||||
writeText(tabstopLeft, yPos, 0, "Bezeichnung")
|
||||
writeText(tabstopQuantity, yPos, widthQuantity, "Menge", "TR")
|
||||
writeText(tabstopPricePerUnit, yPos, widthPricePerUnit, "Einheitspreis", "TR")
|
||||
writeText(tabstopPrice, yPos, widthPrice, "Preis", "TR")
|
||||
pdf.SetFont("Dejavusans", "", defaultFontSize)
|
||||
yPos = yPos + lineSpacing
|
||||
pdf.Line(tabstopLeft, yPos, tabstopRight, yPos)
|
||||
yPos = yPos + lineSpacing
|
||||
}
|
||||
|
||||
func printItems() {
|
||||
for _, i := range invoiceData.InvoiceItems {
|
||||
if i.Quantity != 0 {
|
||||
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")
|
||||
}
|
||||
if i.Text != "" {
|
||||
lines := pdf.SplitText(i.Text, widthItemText)
|
||||
for _, il := range lines {
|
||||
writeText(tabstopLeft, yPos, 0, strings.ReplaceAll(il, "_", " "))
|
||||
yPos = yPos + lineSpacing
|
||||
}
|
||||
} else {
|
||||
yPos = yPos + lineSpacing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printTotals() {
|
||||
yPos = totalsTop
|
||||
pdf.Line(tabstopRight-widthPrice, yPos, tabstopRight, yPos)
|
||||
pdf.SetFont("Dejavusans-Bold", "", defaultFontSize)
|
||||
yPos = yPos + lineSpacing
|
||||
writeText(tabstopLeft, yPos, 0, "Netto Betrag")
|
||||
writeText(tabstopPrice, yPos, widthPrice, floatToString(totalNetAmount), "TR")
|
||||
yPos = yPos + lineSpacing
|
||||
yPos = yPos + lineSpacing
|
||||
writeText(tabstopLeft, yPos, 0, fmt.Sprintf("MwSt. %.1f%%", invoiceData.Metadata.Vat))
|
||||
mwstAmount := round5rappen(totalNetAmount * invoiceData.Metadata.Vat / 100)
|
||||
writeText(tabstopPrice, yPos, widthPrice, floatToString(mwstAmount), "TR")
|
||||
yPos = yPos + lineSpacing
|
||||
yPos = yPos + lineSpacing
|
||||
writeText(tabstopLeft, yPos, 0, "Total Betrag sFr.")
|
||||
totalInvoiceAmount = totalNetAmount + mwstAmount
|
||||
writeText(tabstopPrice, yPos, widthPrice, floatToString(totalNetAmount+mwstAmount), "TR")
|
||||
}
|
||||
|
||||
func printQR() {
|
||||
|
|
@ -148,7 +245,7 @@ func printQR() {
|
|||
|
||||
cmd := exec.Command(filepath.Join(progDir, "qrbill.sh"),
|
||||
"--account", invoiceData.Metadata.Account,
|
||||
"--amount", "123.00",
|
||||
"--amount", floatToString(totalInvoiceAmount),
|
||||
"--creditor-name", invoiceData.SenderAddress.Name,
|
||||
"--creditor-street", invoiceData.SenderAddress.Street,
|
||||
"--creditor-postalcode", invoiceData.SenderAddress.Zip,
|
||||
|
|
@ -170,9 +267,22 @@ func printQR() {
|
|||
}
|
||||
fmt.Printf("%s\n", stdoutStderr)
|
||||
|
||||
opt.ImageType = "jpeg"
|
||||
opt.ImageType = "png"
|
||||
opt.ReadDpi = true
|
||||
pdf.ImageOptions("qr-images/"+invoiceData.Metadata.InvoiceNr+".jpg", 0, 200, 0, 0, false, opt, 0, "")
|
||||
pdf.ImageOptions("qr-images/"+invoiceData.Metadata.InvoiceNr+".png", 0, 200, 0, 0, false, opt, 0, "")
|
||||
}
|
||||
|
||||
func CreateInvoice() {
|
||||
totalNetAmount = 0
|
||||
readInvoiceData(os.Args[1])
|
||||
setupInvoice()
|
||||
printPageHeader(true)
|
||||
printAddress()
|
||||
printMetadataFirstPage()
|
||||
printItemsHeader()
|
||||
printItems()
|
||||
printTotals()
|
||||
printQR()
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
|
@ -187,11 +297,13 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
readInvoiceData(os.Args[1])
|
||||
setupInvoice()
|
||||
printPageHeader(true)
|
||||
printAddress()
|
||||
printQR()
|
||||
// First Run to get total number of pages
|
||||
CreateInvoice()
|
||||
totalPages = pdf.PageNo()
|
||||
fmt.Printf("Total Pages is: %d\n", totalPages)
|
||||
|
||||
// Second Run
|
||||
CreateInvoice()
|
||||
|
||||
err = pdf.OutputFileAndClose(filepath.Join(progDir, "output", invoiceData.Metadata.InvoiceNr+".pdf"))
|
||||
if err == nil {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,10 @@ if [ $? -ne 0 ]; then
|
|||
exit 2
|
||||
fi
|
||||
|
||||
convert ${mydir}/temp/${INVNO}.svg ${mydir}/qr-images/${INVNO}.jpg
|
||||
#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
|
||||
#cairosvg ${mydir}/temp/${INVNO}.svg -o ${mydir}/qr-images/${INVNO}.png
|
||||
if [ $? -eq 0 ]; then
|
||||
rm ${mydir}/temp/${INVNO}.svg
|
||||
echo blabla
|
||||
#rm ${mydir}/temp/${INVNO}.svg
|
||||
fi
|
||||
|
|
|
|||
Loading…
Reference in New Issue