feat(services): EAN 13 generation

This commit is contained in:
Nicolai Ort 2024-12-03 17:34:11 +01:00
parent 323c0b0ff9
commit 8812bf2410
Signed by: niggl
GPG Key ID: 13AFA55AF62F269F
7 changed files with 84 additions and 12 deletions

3
go.mod
View File

@ -13,6 +13,7 @@ require (
require ( require (
github.com/KyleBanks/depth v1.2.1 // indirect github.com/KyleBanks/depth v1.2.1 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect github.com/andybalholm/brotli v1.1.1 // indirect
github.com/boombuler/barcode v1.0.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect github.com/go-openapi/spec v0.21.0 // indirect
@ -21,6 +22,7 @@ require (
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/compress v1.17.11 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/makiuchi-d/gozxing v0.1.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect
@ -32,5 +34,6 @@ require (
github.com/valyala/tcplisten v1.0.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/sys v0.27.0 // indirect golang.org/x/sys v0.27.0 // indirect
golang.org/x/tools v0.27.0 // indirect golang.org/x/tools v0.27.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

6
go.sum
View File

@ -2,6 +2,8 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/boombuler/barcode v1.0.2 h1:79yrbttoZrLGkL/oOI8hBrUKucwOL0oOjUgEguGMcJ4=
github.com/boombuler/barcode v1.0.2/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
@ -28,6 +30,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/makiuchi-d/gozxing v0.1.1 h1:xxqijhoedi+/lZlhINteGbywIrewVdVv2wl9r5O9S1I=
github.com/makiuchi-d/gozxing v0.1.1/go.mod h1:eRIHbOjX7QWxLIDJoQuMLhuXg9LAuw6znsUtRkNw9DU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@ -68,6 +72,8 @@ golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=
golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q=
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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@ -41,13 +41,20 @@ func GenerateContract(c *fiber.Ctx) error {
contract.Runners = duplicatedRunners contract.Runners = duplicatedRunners
generator := services.DefaultTemplater{} generator := services.DefaultTemplater{}
template, err := templates.GetTemplate(contract.Locale, "contract") templateString, err := templates.GetTemplate(contract.Locale, "contract")
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Template not found", "error": "Template not found",
}) })
} }
template, err := generator.StringToTemplate(templateString)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(),
})
}
result, err := generator.Execute(template, &services.ContractTemplateOptions{ result, err := generator.Execute(template, &services.ContractTemplateOptions{
Runners: contract.Runners, Runners: contract.Runners,
CurrencySymbol: "€", CurrencySymbol: "€",

View File

@ -2,13 +2,21 @@ package services
import ( import (
"bytes" "bytes"
"encoding/base64"
"errors"
"html/template" "html/template"
"image/png"
"math"
"strconv"
"git.odit.services/lfk/document-server/models" "git.odit.services/lfk/document-server/models"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/ean"
) )
type Templater interface { type Templater interface {
Execute(template *template.Template, data interface{}) (string, error) Execute(template *template.Template, data interface{}) (string, error)
StringToTemplate(templateString string) (*template.Template, error)
} }
type DefaultTemplater struct { type DefaultTemplater struct {
@ -23,10 +31,59 @@ type ContractTemplateOptions struct {
SponsoringHeader string `json:"base_url"` SponsoringHeader string `json:"base_url"`
} }
func (t *DefaultTemplater) Execute(template *template.Template, data interface{}) (string, error) { func idToEan13(id int) (string, error) {
multiply := [2]int{1, 3}
idStr := strconv.Itoa(id)
if len(idStr) > 12 {
return "", errors.New("id too long")
}
for len(idStr) < 12 {
idStr = "0" + idStr
}
total := 0
for i, char := range idStr {
num, err := strconv.Atoi(string(char))
if err != nil {
return "", err
}
total += num * multiply[i%2]
}
checkSum := (int(math.Ceil(float64(total)/10)) * 10) - total
return idStr + strconv.Itoa(checkSum), nil
}
func (t *DefaultTemplater) GenerateBarcode(code int) (string, error) {
encodedEan, err := idToEan13(code)
eanCode, err := ean.Encode(encodedEan)
if err != nil {
return "", err
}
scaledCode, err := barcode.Scale(eanCode, 100, 50)
if err != nil {
return "", err
}
var buf bytes.Buffer
err = png.Encode(&buf, scaledCode)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(buf.Bytes()), nil
}
func (t *DefaultTemplater) StringToTemplate(templateString string) (*template.Template, error) {
return template.New("template").Funcs(template.FuncMap{
"barcode": t.GenerateBarcode,
}).Parse(templateString)
}
func (t *DefaultTemplater) Execute(baseTemplate *template.Template, data interface{}) (string, error) {
resultBuffer := new(bytes.Buffer) resultBuffer := new(bytes.Buffer)
err := template.Execute(resultBuffer, data) err := baseTemplate.Execute(resultBuffer, data)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -56,7 +56,7 @@
</div> </div>
<div class="column"> <div class="column">
<img style="vertical-align: revert; margin-top: auto; object-fit: cover; max-height: 2cm;" <img style="vertical-align: revert; margin-top: auto; object-fit: cover; max-height: 2cm;"
src="{{/* TODO: BARCODE*/}}" /> src="data:image/png;base64,{{ barcode .ID }}" />
</div> </div>
</div> </div>
<div class="columns" style="padding-top: 1rem;"> <div class="columns" style="padding-top: 1rem;">

View File

@ -55,7 +55,7 @@
</div> </div>
<div class="column"> <div class="column">
<img style="vertical-align: revert; margin-top: auto; object-fit: cover; max-height: 2cm;" <img style="vertical-align: revert; margin-top: auto; object-fit: cover; max-height: 2cm;"
src="{{/* TODO: BARCODE*/}}" /> src="data:image/png;base64,{{ barcode .ID}}" />
</div> </div>
</div> </div>
<div class="columns" style="padding-top: 1rem;"> <div class="columns" style="padding-top: 1rem;">

View File

@ -3,7 +3,6 @@ package templates
import ( import (
_ "embed" _ "embed"
"fmt" "fmt"
"html/template"
) )
//go:embed card/en.html //go:embed card/en.html
@ -21,22 +20,22 @@ var ContractTemplateDe string
//go:embed images/sponsoringheader.base64 //go:embed images/sponsoringheader.base64
var ImageSponsoringHeaderBase64 string var ImageSponsoringHeaderBase64 string
func GetTemplate(locale, templateName string) (*template.Template, error) { func GetTemplate(locale, templateName string) (string, error) {
switch templateName { switch templateName {
case "card": case "card":
switch locale { switch locale {
case "en": case "en":
return template.New("card").Parse(CardTemplateEn) return CardTemplateEn, nil
case "de": case "de":
return template.New("card").Parse(CardTemplateDe) return CardTemplateDe, nil
} }
case "contract": case "contract":
switch locale { switch locale {
case "en": case "en":
return template.New("contract").Parse(ContractTemplateEn) return ContractTemplateEn, nil
case "de": case "de":
return template.New("contract").Parse(ContractTemplateDe) return ContractTemplateDe, nil
} }
} }
return nil, fmt.Errorf("template not found with name %s and locale %s", templateName, locale) return "", fmt.Errorf("template not found with name %s and locale %s", templateName, locale)
} }