commit 6bcb596e813d458f8f568119f415463eefb3c1d2 Author: Joerg Lehmann Date: Tue Apr 6 20:12:09 2021 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c3d551d --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +wo-bisch-invoice +makefont/makefont +qr-images/* +output/* +yaml/* diff --git a/README.md b/README.md new file mode 100644 index 0000000..fdfb729 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +mkinvoice will generate a PDF invoice from a YAML-file + +It is the by-result of learning the Golang language, but with the +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 + +Usage (Example): +$ ./mkinvoice yaml/123456.yml + +Joerg Lehmann, December 2020 diff --git a/example.yaml b/example.yaml new file mode 100644 index 0000000..8f246f1 --- /dev/null +++ b/example.yaml @@ -0,0 +1,27 @@ +--- +metadata: + invoice_nr: 123456789 + invoice_date: 11. Dezember 2020 + vat: 7.7 + account: CH92 0023 5235 5662 3601 G + vat_number: CHE-105.687.350 MWST +sender_address: + name: nbit Informatik GmbH + street: Kirchweg + streetnumber: 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 + 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: + - text: Monat Dezember 2020, Stunden gemäss beigelegtem Zeitnachweis diff --git a/fonts/DejaVuSans-Bold.json b/fonts/DejaVuSans-Bold.json new file mode 100644 index 0000000..0af7b13 --- /dev/null +++ b/fonts/DejaVuSans-Bold.json @@ -0,0 +1 @@ +{"Tp":"TrueType","Name":"DejaVuSans-Bold","Desc":{"Ascent":760,"Descent":-240,"CapHeight":760,"Flags":32,"FontBBox":{"Xmin":-1069,"Ymin":-415,"Xmax":1975,"Ymax":1175},"ItalicAngle":0,"StemV":120,"MissingWidth":600},"Up":-20,"Ut":44,"Cw":[600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,348,456,521,838,696,1002,872,306,457,457,523,838,380,415,380,365,696,696,696,696,696,696,696,696,696,696,400,400,838,838,838,580,1000,774,762,734,830,683,683,821,837,372,372,775,637,995,837,850,733,850,770,720,682,812,774,1103,771,724,725,457,365,457,838,500,500,675,716,593,716,678,435,716,712,343,343,665,343,1042,712,687,716,716,493,595,478,712,652,924,645,652,582,712,365,712,838,600,696,600,380,435,657,1000,500,500,500,1440,720,412,1167,600,725,600,600,380,380,657,657,639,500,1000,500,1000,595,412,1094,600,582,724,348,456,696,696,636,696,365,500,500,1000,564,646,838,415,1000,500,500,838,438,438,500,736,636,380,500,438,564,646,1035,1035,1035,580,774,774,774,774,774,774,1085,734,683,683,683,683,372,372,372,372,838,837,850,850,850,850,850,838,850,812,812,812,812,724,738,719,675,675,675,675,675,675,1048,593,678,678,678,678,343,343,343,343,687,712,687,687,687,687,687,838,687,712,712,712,712,652,716,652],"Enc":"cp1252","Diff":"","File":"DejaVuSans-Bold.z","Size1":0,"Size2":0,"OriginalSize":705684,"N":0,"DiffN":0} \ No newline at end of file diff --git a/fonts/DejaVuSans-Bold.z b/fonts/DejaVuSans-Bold.z new file mode 100644 index 0000000..9b49a15 Binary files /dev/null and b/fonts/DejaVuSans-Bold.z differ diff --git a/fonts/DejaVuSans.json b/fonts/DejaVuSans.json new file mode 100644 index 0000000..cfa92ef --- /dev/null +++ b/fonts/DejaVuSans.json @@ -0,0 +1 @@ +{"Tp":"TrueType","Name":"DejaVuSans","Desc":{"Ascent":760,"Descent":-240,"CapHeight":760,"Flags":32,"FontBBox":{"Xmin":-1021,"Ymin":-463,"Xmax":1793,"Ymax":1232},"ItalicAngle":0,"StemV":70,"MissingWidth":600},"Up":-20,"Ut":44,"Cw":[600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,318,401,460,838,636,950,780,275,390,390,500,838,318,361,318,337,636,636,636,636,636,636,636,636,636,636,337,337,838,838,838,531,1000,684,686,698,770,632,575,775,752,295,295,656,557,863,748,787,603,787,695,635,611,732,684,989,685,611,685,390,337,390,838,500,500,613,635,550,635,615,352,635,634,278,278,579,278,974,634,612,635,635,411,521,392,634,592,818,592,592,525,636,337,636,838,600,636,600,318,352,518,1000,500,500,500,1342,635,400,1070,600,685,600,600,318,318,518,518,590,500,1000,500,1000,521,400,1023,600,525,611,318,401,636,636,636,636,337,500,500,1000,471,612,838,361,1000,500,500,838,401,401,500,636,636,318,500,401,471,612,969,969,969,531,684,684,684,684,684,684,974,698,632,632,632,632,295,295,295,295,775,748,787,787,787,787,787,838,787,732,732,732,732,611,605,630,613,613,613,613,613,613,982,550,615,615,615,615,278,278,278,278,612,634,612,612,612,612,612,838,612,634,634,634,634,592,635,592],"Enc":"cp1252","Diff":"","File":"DejaVuSans.z","Size1":0,"Size2":0,"OriginalSize":757076,"N":0,"DiffN":0} \ No newline at end of file diff --git a/fonts/DejaVuSans.z b/fonts/DejaVuSans.z new file mode 100644 index 0000000..41ed2a0 Binary files /dev/null and b/fonts/DejaVuSans.z differ diff --git a/logos/Scissors_icon_black.svg b/logos/Scissors_icon_black.svg new file mode 100644 index 0000000..1be188c --- /dev/null +++ b/logos/Scissors_icon_black.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + diff --git a/logos/scissors-rotated.png b/logos/scissors-rotated.png new file mode 100644 index 0000000..bd14674 Binary files /dev/null and b/logos/scissors-rotated.png differ diff --git a/logos/scissors.png b/logos/scissors.png new file mode 100644 index 0000000..abc2ca9 Binary files /dev/null and b/logos/scissors.png differ diff --git a/logos/wo-bisch-logo-mit-schrift-12mm-400dpi.png b/logos/wo-bisch-logo-mit-schrift-12mm-400dpi.png new file mode 100644 index 0000000..b67f9f3 Binary files /dev/null and b/logos/wo-bisch-logo-mit-schrift-12mm-400dpi.png differ diff --git a/main.go b/main.go new file mode 100644 index 0000000..d673f12 --- /dev/null +++ b/main.go @@ -0,0 +1,506 @@ +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" + "path/filepath" + "strings" + + "github.com/stapelberg/qrbill" +) + +// Metadata type +type Metadata struct { + InvoiceNr string `yaml:"invoice_nr"` + InvoiceDate string `yaml:"invoice_date"` + Vat float64 `yaml:"vat"` + Account string `yaml:"account"` + 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"` +} + +// InvoiceItem type +type InvoiceItem struct { + Text string `yaml:"text"` + Quantity float64 `yaml:"quantity"` + PricePerUnit float64 `yaml:"price_per_unit"` +} + +// InvoiceData type +type InvoiceData struct { + Metadata Metadata `yaml:"metadata"` + SenderAddress Address `yaml:"sender_address"` + BillingAddress Address `yaml:"billing_address"` + InvoiceItems []InvoiceItem `yaml:"invoice_items"` +} + +/* global variable declaration */ +var pdf *gofpdf.Fpdf +var yPos float64 +var invoiceData InvoiceData +var progDir string +var currentPage int +var totalPages int +var totalItemLines int +var totalBruttoAmount 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 = 12 +const lineSpacing = 5 +const lineSpacingSmall = 4.5 +const lineSpacingPaymentSlipBig = 6 +const lineSpacingPaymentSlipBelowHeading = 2.8 +const lineSpacingPaymentSlipSmall = 3.175 +const addressTop = 50 +const metadataTopFirstPage = 70 +const metadataTopNotFirstPage = 30 +const tabstopLeft = 20 +const tabstopLeftAlt = 40 +const tabstopMetadata = 60 +const tabstopAddress = 120 +const tabstopLogo = 135 +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 itemsTopFirstPage = 105 +const itemsTopNotFirstPage = 50 +const totalsTop = 158 +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 { + p := message.NewPrinter(language.English) + s := strings.ReplaceAll(p.Sprintf("%.2f", f), ",", sep) + //fmt.Printf("--- s: @%s@\n", s) + return s +} + +func readInvoiceData(filename string) { + data, err := ioutil.ReadFile(filename) + if err != nil { + log.Fatal(err) + } + + //fmt.Printf("File contents: %s", data) + + err = yaml.Unmarshal([]byte(data), &invoiceData) + if err != nil { + log.Fatalf("error: %v", err) + } + + //fmt.Printf("--- t:\n%v\n\n", invoiceData) +} + +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(w, 11, 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") + 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", "", defaultFontSize) + writeText(tabstopLeft, yPos, 0, invoiceData.SenderAddress.Name) + pdf.SetFont("Dejavusans", "", defaultFontSize) + yPos = yPos + lineSpacing + writeText(tabstopLeft, yPos, 0, invoiceData.SenderAddress.Street+" "+invoiceData.SenderAddress.StreetNumber) + yPos = yPos + lineSpacing + writeText(tabstopLeft, yPos, 0, invoiceData.SenderAddress.Zip+" "+invoiceData.SenderAddress.City) + if firstPage { + yPos = yPos + lineSpacing + yPos = yPos + lineSpacing + writeText(tabstopLeft, yPos, 0, "Telefon") + writeText(tabstopLeftAlt, yPos, 0, invoiceData.SenderAddress.TelNo) + yPos = yPos + lineSpacing + writeText(tabstopLeft, yPos, 0, "E-Mail") + writeText(tabstopLeftAlt, yPos, 0, invoiceData.SenderAddress.Email) + yPos = yPos + lineSpacing + writeText(tabstopLeft, yPos, 0, "MwSt. Nr.") + writeText(tabstopLeftAlt, yPos, 0, invoiceData.Metadata.VatNumber) + } + + opt.ImageType = "png" + opt.ReadDpi = true + pdf.ImageOptions("logos/wo-bisch-logo-mit-schrift-12mm-400dpi.png", tabstopLogo, logoTop, 0, logoHeight, false, opt, 0, "") + pdf.SetFont("Dejavusans", "", defaultFontSize) +} + +func printAddress() { + yPos = addressTop + writeText(tabstopAddress, yPos, 0, invoiceData.BillingAddress.Name) + yPos = yPos + lineSpacing + writeText(tabstopAddress, yPos, 0, invoiceData.BillingAddress.Street+" "+invoiceData.BillingAddress.StreetNumber) + yPos = yPos + lineSpacing + writeText(tabstopAddress, yPos, 0, invoiceData.BillingAddress.Zip+" "+invoiceData.BillingAddress.City) +} + +func printMetadata(firstPage bool) { + yPos = metadataTopNotFirstPage + if firstPage { + yPos = metadataTopFirstPage + 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 + if firstPage { + pdf.SetFont("Dejavusans-Bold", "", defaultFontSize) + writeText(tabstopLeft, yPos, 0, "Rechnungsdatum:") + 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) + writeText(tabstopMetadata, yPos, 0, fmt.Sprintf("%d von %d", currentPage, totalPages)) +} + +func printItemsHeader() { + if currentPage == 1 { + yPos = itemsTopFirstPage + } else { + yPos = itemsTopNotFirstPage + } + 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 maybeNewPage() { + if yPos > maxYPos { + printPageHeader(false) + printMetadata(false) + printItemsHeader() + } +} + +func printItems() { + for _, i := range invoiceData.InvoiceItems { + if i.Quantity != 0 { + writeText(tabstopQuantity, yPos, widthQuantity, fmt.Sprintf("%g", i.Quantity), "TR") + writeText(tabstopPricePerUnit, yPos, widthPricePerUnit, fmt.Sprintf("%.2f", i.PricePerUnit), "TR") + itemBruttoAmount := round5rappen(i.Quantity * i.PricePerUnit) + totalBruttoAmount = totalBruttoAmount + itemBruttoAmount + writeText(tabstopPrice, yPos, widthPrice, floatToString(itemBruttoAmount, "'"), "TR") + } + if i.Text != "" { + lines := pdf.SplitText(i.Text, widthItemText) + for _, il := range lines { + totalItemLines = totalItemLines + 1 + writeText(tabstopLeft, yPos, 0, strings.ReplaceAll(il, "_", " ")) + yPos = yPos + lineSpacing + maybeNewPage() + } + } else { + totalItemLines = totalItemLines + 1 + yPos = yPos + lineSpacing + maybeNewPage() + } + } +} + +func printTotals() { + yPos = totalsTop + pdf.Line(tabstopRight-widthPrice, yPos+(lineSpacing/2), tabstopRight, yPos+(lineSpacing/2)) + pdf.SetFont("Dejavusans-Bold", "", defaultFontSize) + yPos = yPos + lineSpacing + writeText(tabstopLeft, yPos, 0, "Summe (inkl. MwSt.)") + pdf.SetFont("Dejavusans", "", defaultFontSize) + writeText(tabstopPrice, yPos, widthPrice, floatToString(totalBruttoAmount, "'"), "TR") + yPos = yPos + lineSpacing + pdf.SetFont("Dejavusans-Bold", "", defaultFontSize) + writeText(tabstopLeft, yPos, 0, fmt.Sprintf("davon MwSt. %.1f%%", invoiceData.Metadata.Vat)) + totalNettoAmount := totalBruttoAmount / (1 + (invoiceData.Metadata.Vat / 100)) + mwstAmount := round5rappen(totalBruttoAmount - totalNettoAmount) + pdf.SetFont("Dejavusans", "", defaultFontSize) + 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") + totalInvoiceAmount = totalBruttoAmount + writeText(tabstopPrice, yPos, widthPrice, floatToString(totalBruttoAmount, "'"), "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) +} + +func printQR() { + var opt gofpdf.ImageOptions + + var b []byte + var err error + qrch := qrchFromInvoiceData(invoiceData) + bill, err := qrch.Encode() + + code, err := bill.EncodeToImage() + 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 + } + + opt.ImageType = "png" + opt.ReadDpi = false + + pdf.ImageOptions("qr-images/"+invoiceData.Metadata.InvoiceNr+".png", tabstopPaymentSlipMiddle-2, paymentSlipTop+13, 56, 56, false, opt, 0, "") +} + +func CreateInvoice() { + totalBruttoAmount = 0 + totalItemLines = 0 + readInvoiceData(os.Args[1]) + setupInvoice() + printPageHeader(true) + printAddress() + printMetadata(true) + printItemsHeader() + printItems() + printTotals() + printPaymentSlip() + printQR() +} + +func main() { + dir, err := filepath.Abs(filepath.Dir(os.Args[0])) + if err != nil { + log.Fatal(err) + } + progDir = dir + + if len(os.Args) != 2 { + fmt.Printf("usage: mkinvoice \n") + os.Exit(1) + } + + // First Run to get total number of pages + CreateInvoice() + totalPages = pdf.PageNo() + fmt.Printf("Total Pages is: %d\n", totalPages) + fmt.Printf("Total Item Lines is: %d\n", totalItemLines) + + // Second Run + CreateInvoice() + + err = pdf.OutputFileAndClose(filepath.Join(progDir, "output", invoiceData.Metadata.InvoiceNr+".pdf")) + if err == nil { + fmt.Printf("Successfully created invoice\n") + } else { + fmt.Printf("Error: %v\n", err) + } +} diff --git a/makefont/makefont.go b/makefont/makefont.go new file mode 100644 index 0000000..1a67522 --- /dev/null +++ b/makefont/makefont.go @@ -0,0 +1,22 @@ +package main + +import ( + "fmt" + "github.com/jung-kurt/gofpdf" +) + +func main() { + pdf := gofpdf.New("P", "mm", "A4", "") + + pdf.SetFontLocation("fonts") + errs := gofpdf.MakeFont("/usr/share/fonts/dejavu/DejaVuSans.ttf", "/home/joerg/go/src/github.com/jung-kurt/gofpdf/font/cp1252.map", "fonts", nil, true) + if errs != nil { + fmt.Println(errs) + } + errs2 := gofpdf.MakeFont("/usr/share/fonts/dejavu/DejaVuSans-Bold.ttf", "/home/joerg/go/src/github.com/jung-kurt/gofpdf/font/cp1252.map", "fonts", nil, true) + if errs2 != nil { + fmt.Println(errs) + } + pdf.AddFont("DejaVuSans", "", "DejaVuSans.json") + pdf.AddFont("DejaVuSans-Bold", "", "DejaVuSans-Bold.json") +}