This commit is contained in:
@@ -11,29 +11,34 @@ import (
|
|||||||
type cmd struct {
|
type cmd struct {
|
||||||
cmd func([]string, any)
|
cmd func([]string, any)
|
||||||
config any
|
config any
|
||||||
|
fs *flag.FlagSet
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cmd) Run() {
|
func (c *cmd) Register(name string) {
|
||||||
|
c.fs = flag.NewFlagSet(name, flag.ExitOnError)
|
||||||
if c.config != nil {
|
if c.config != nil {
|
||||||
flag.Parse()
|
|
||||||
filler := flagsfiller.New()
|
filler := flagsfiller.New()
|
||||||
err := filler.Fill(flag.CommandLine, c.config)
|
err := filler.Fill(c.fs, c.config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
c.cmd(flag.Args()[1:], c.config)
|
|
||||||
} else {
|
|
||||||
c.cmd(os.Args[2:], c.config)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var commands = map[string]cmd{
|
func (c *cmd) Run(name string) {
|
||||||
|
c.fs.Parse(os.Args[2:])
|
||||||
|
c.cmd(c.fs.Args(), c.config)
|
||||||
|
}
|
||||||
|
|
||||||
|
var commands = map[string]*cmd{
|
||||||
"request": {
|
"request": {
|
||||||
request,
|
request,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
"create": {
|
"create": {
|
||||||
create,
|
create,
|
||||||
&createFlags{},
|
&createFlags{},
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
"git.schreifuchs.ch/lou-taylor/accounting/internal/email"
|
"git.schreifuchs.ch/lou-taylor/accounting/internal/email"
|
||||||
"git.schreifuchs.ch/lou-taylor/accounting/pkg/invoice"
|
"git.schreifuchs.ch/lou-taylor/accounting/pkg/invoice"
|
||||||
)
|
)
|
||||||
@@ -15,6 +16,7 @@ import (
|
|||||||
type createFlags struct {
|
type createFlags struct {
|
||||||
Email bool `flag:"email" help:"send invoice by email"`
|
Email bool `flag:"email" help:"send invoice by email"`
|
||||||
Output string `flag:"o" help:"output file"`
|
Output string `flag:"o" help:"output file"`
|
||||||
|
Label string `flag:"l" help:"filters for issues with the label"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func create(arguments []string, c any) {
|
func create(arguments []string, c any) {
|
||||||
@@ -33,6 +35,21 @@ func create(arguments []string, c any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
opts := invoice.DefaultOptions
|
opts := invoice.DefaultOptions
|
||||||
|
|
||||||
|
if flags.Label != "" {
|
||||||
|
opts.IssueState = gitea.StateAll
|
||||||
|
opts.IssueFilter = func(i *gitea.Issue) bool {
|
||||||
|
for _, l := range i.Labels {
|
||||||
|
if l.Name == flags.Label {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opts.Since = time.Now().AddDate(-1, 0, 0)
|
||||||
|
|
||||||
opts.Mindur = time.Duration(req.DurationThreshold)
|
opts.Mindur = time.Duration(req.DurationThreshold)
|
||||||
invoice, report, err := invoicer.Generate(req.Creditor, req.Debtor, req.HourlyRate, repos, &opts)
|
invoice, report, err := invoicer.Generate(req.Creditor, req.Debtor, req.HourlyRate, repos, &opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,20 +1,25 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"flag"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
for n, c := range commands {
|
||||||
|
c.Register(n)
|
||||||
|
}
|
||||||
if len(os.Args) < 2 {
|
if len(os.Args) < 2 {
|
||||||
|
flag.PrintDefaults()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for n, c := range commands {
|
for n, c := range commands {
|
||||||
if os.Args[1] == n {
|
if os.Args[1] == n {
|
||||||
c.Run()
|
c.Run(n)
|
||||||
break
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Println("cmd not found")
|
|
||||||
|
flag.PrintDefaults()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func getServices() (invoicer *invoice.Service, mailer *email.Service) {
|
|||||||
gitea.SetToken(cfg.Gitea.Token),
|
gitea.SetToken(cfg.Gitea.Token),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("could not connect to gitea: %v", err)
|
log.Fatalf("could not connect to gitea: %v", err)
|
||||||
return invoicer, mailer
|
return invoicer, mailer
|
||||||
}
|
}
|
||||||
gotenberg, err := pdf.New(cfg.PDF.Hostname)
|
gotenberg, err := pdf.New(cfg.PDF.Hostname)
|
||||||
@@ -49,12 +49,12 @@ func getServices() (invoicer *invoice.Service, mailer *email.Service) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("could not connect to gotenberg: %v", err)
|
log.Fatalf("could not connect to gotenberg: %v", err)
|
||||||
return invoicer, mailer
|
return invoicer, mailer
|
||||||
}
|
}
|
||||||
mailer, err = email.New(cfg.Email)
|
mailer, err = email.New(cfg.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("could not create mailer: %v", err)
|
log.Fatalf("could not create mailer: %v", err)
|
||||||
return invoicer, mailer
|
return invoicer, mailer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
709
index.html
709
index.html
@@ -1,709 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<title>Rechnung vom 24.08.2025</title>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
@page {
|
|
||||||
size: A4;
|
|
||||||
padding: 2cm;
|
|
||||||
}
|
|
||||||
@page:first {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
@media print {
|
|
||||||
.page,
|
|
||||||
.page-break {
|
|
||||||
break-after: page;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.first-page {
|
|
||||||
margin-left: 2cm;
|
|
||||||
margin-right: 2cm;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 12pt;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
section {
|
|
||||||
margin-bottom: 3em;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
margin-top: 0.25em;
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 2em;
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
margin-top: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
table,
|
|
||||||
th,
|
|
||||||
td {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
th,
|
|
||||||
td {
|
|
||||||
padding: 0.25em 0.5em;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
th {
|
|
||||||
background-color: #f2f2f2;
|
|
||||||
}
|
|
||||||
|
|
||||||
article {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.issue-title {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: end;
|
|
||||||
|
|
||||||
margin-top: 2em;
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
|
|
||||||
h3,
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
<style>
|
|
||||||
|
|
||||||
.c6ee15365-8f47-4dab-8bee-2a56a7916c57 {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 240px 1px 1fr;
|
|
||||||
grid-template-rows: 1px 1fr;
|
|
||||||
|
|
||||||
.separator-h {
|
|
||||||
grid-row: 1;
|
|
||||||
grid-column: 1 / span 3;
|
|
||||||
|
|
||||||
border-top: 1px solid black;
|
|
||||||
svg {
|
|
||||||
transform: rotate(-90deg);
|
|
||||||
margin-top: -52px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.separator-v {
|
|
||||||
grid-row: 2;
|
|
||||||
grid-column: 2;
|
|
||||||
|
|
||||||
border-right: 1px solid black;
|
|
||||||
svg {
|
|
||||||
margin-left: -32px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.receiver {
|
|
||||||
grid-row: 2;
|
|
||||||
grid-column: 1;
|
|
||||||
|
|
||||||
font-size: 12pt;
|
|
||||||
padding: 1em;
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
margin: 0;
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
h4 {
|
|
||||||
margin-bottom: 0.1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.payer {
|
|
||||||
grid-row: 2;
|
|
||||||
grid-column: 3;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 250px 1fr;
|
|
||||||
grid-template-rows: 2em 1fr;
|
|
||||||
padding: 1em;
|
|
||||||
gap: 1em;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
grid-row: 1;
|
|
||||||
grid-column: 1 / 2;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
margin-bottom: 0.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-code {
|
|
||||||
grid-row: 2;
|
|
||||||
grid-column: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
.qr-section-img {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
img {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
svg {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
z-index: 1;
|
|
||||||
width: 40px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-section-info {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
grid-template-rows: 1fr 1fr;
|
|
||||||
justify-items: center;
|
|
||||||
|
|
||||||
p,
|
|
||||||
h4 {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.payment-details {
|
|
||||||
grid-row: 2;
|
|
||||||
grid-column: 2;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header class="first-page" style="margin-top: 2cm">
|
|
||||||
<div class="company">
|
|
||||||
<h2>schreifuchs.ch</h2>
|
|
||||||
<p>
|
|
||||||
Kilchbergerweg 1 <br />
|
|
||||||
Zollikofen <br />
|
|
||||||
Niklas Breitenstein
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p></p>
|
|
||||||
</div>
|
|
||||||
<div class="invoice-info">
|
|
||||||
<p>
|
|
||||||
<strong>Rechnung:</strong> 20 25082 41117 00284 67114 43342 <br />
|
|
||||||
<strong>Datum:</strong> 24.08.2025 <br />
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<section class="client first-page">
|
|
||||||
<h2>Rechnung an:</h2>
|
|
||||||
<p>
|
|
||||||
Lou Taylor <br />
|
|
||||||
Alpenstrasse 22 <br />
|
|
||||||
Huttwil <br />
|
|
||||||
Loana Groux
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="page p1 first-page">
|
|
||||||
<article>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th style="min-width: 3.5em">FID</th>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Aufwand</th>
|
|
||||||
<th style="min-width: 5.5em">Preis</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td style="font-family: monospace">API-6</td>
|
|
||||||
<td>Multilingual</td>
|
|
||||||
<td>2.50 h</td>
|
|
||||||
<td>40.00 CHF</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td style="font-family: monospace">ACC-7</td>
|
|
||||||
<td>asdfasdf</td>
|
|
||||||
<td>1.72 h</td>
|
|
||||||
<td>27.45 CHF</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td style="font-family: monospace">ACC-6</td>
|
|
||||||
<td>ss</td>
|
|
||||||
<td>1.72 h</td>
|
|
||||||
<td>27.45 CHF</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td style="font-family: monospace">ACC-5</td>
|
|
||||||
<td>a</td>
|
|
||||||
<td>1.72 h</td>
|
|
||||||
<td>27.45 CHF</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td style="font-family: monospace">ACC-4</td>
|
|
||||||
<td>Hello ```info duration: 1h 43min ``` # adökfjaösldkjflaa ## ASDFADS ### adllglggl</td>
|
|
||||||
<td>1.72 h</td>
|
|
||||||
<td>27.45 CHF</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td style="font-family: monospace">ACC-3</td>
|
|
||||||
<td>ss</td>
|
|
||||||
<td>1.72 h</td>
|
|
||||||
<td>27.45 CHF</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr>
|
|
||||||
<th colspan="2" style="text-align: right">Summe:</th>
|
|
||||||
|
|
||||||
<td>11.08 h</td>
|
|
||||||
<td>177.35</td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</article>
|
|
||||||
<article class="c6ee15365-8f47-4dab-8bee-2a56a7916c57">
|
|
||||||
<div class="separator-h"><?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
width="64"
|
|
||||||
height="106.131"
|
|
||||||
viewBox="0 0 16.933333 28.080496"
|
|
||||||
version="1.1"
|
|
||||||
id="svg851"
|
|
||||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
|
||||||
sodipodi:docname="scissors.svg"
|
|
||||||
inkscape:export-xdpi="96"
|
|
||||||
inkscape:export-ydpi="96">
|
|
||||||
<defs
|
|
||||||
id="defs845" />
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="base"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1.0"
|
|
||||||
inkscape:pageopacity="0.0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:zoom="2.6582031"
|
|
||||||
inkscape:cx="-135.5963"
|
|
||||||
inkscape:cy="106.75833"
|
|
||||||
inkscape:document-units="mm"
|
|
||||||
inkscape:current-layer="layer1"
|
|
||||||
showgrid="false"
|
|
||||||
units="px"
|
|
||||||
fit-margin-top="0"
|
|
||||||
fit-margin-left="0"
|
|
||||||
fit-margin-right="0"
|
|
||||||
fit-margin-bottom="0"
|
|
||||||
inkscape:window-width="1920"
|
|
||||||
inkscape:window-height="1080"
|
|
||||||
inkscape:window-x="86"
|
|
||||||
inkscape:window-y="117"
|
|
||||||
inkscape:window-maximized="0" />
|
|
||||||
<metadata
|
|
||||||
id="metadata848">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
<dc:title></dc:title>
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<g
|
|
||||||
inkscape:label="Layer 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1"
|
|
||||||
transform="translate(-52.847684,-108.31232)">
|
|
||||||
<text
|
|
||||||
y="-57.540855"
|
|
||||||
x="117.23324"
|
|
||||||
class="st2 st3"
|
|
||||||
id="text18"
|
|
||||||
style="font-size:11.17903996px;font-family:ZapfDingbatsITC;stroke-width:0.26458332"
|
|
||||||
transform="rotate(90)">✂</text>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="receiver">
|
|
||||||
<div class="payment-details">
|
|
||||||
<div>
|
|
||||||
<h2>Empfangsschein</h2>
|
|
||||||
<h4 style="margin-top: 0">Konto / Zahlbar an</h4>
|
|
||||||
<p>
|
|
||||||
CH06 0079 0042 5877 0443 7 <br />
|
|
||||||
schreifuchs.ch <br />
|
|
||||||
Kilchbergerweg 1 <br />
|
|
||||||
Zollikofen
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4>Referenz</h4>
|
|
||||||
<p>20 25082 41117 00284 67114 43342</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4>Zahlbar durch</h4>
|
|
||||||
<p>
|
|
||||||
Lou Taylor <br />
|
|
||||||
Alpenstrasse 22 <br />
|
|
||||||
Huttwil
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="separator-v"><?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
width="64"
|
|
||||||
height="106.131"
|
|
||||||
viewBox="0 0 16.933333 28.080496"
|
|
||||||
version="1.1"
|
|
||||||
id="svg851"
|
|
||||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
|
||||||
sodipodi:docname="scissors.svg"
|
|
||||||
inkscape:export-xdpi="96"
|
|
||||||
inkscape:export-ydpi="96">
|
|
||||||
<defs
|
|
||||||
id="defs845" />
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="base"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1.0"
|
|
||||||
inkscape:pageopacity="0.0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:zoom="2.6582031"
|
|
||||||
inkscape:cx="-135.5963"
|
|
||||||
inkscape:cy="106.75833"
|
|
||||||
inkscape:document-units="mm"
|
|
||||||
inkscape:current-layer="layer1"
|
|
||||||
showgrid="false"
|
|
||||||
units="px"
|
|
||||||
fit-margin-top="0"
|
|
||||||
fit-margin-left="0"
|
|
||||||
fit-margin-right="0"
|
|
||||||
fit-margin-bottom="0"
|
|
||||||
inkscape:window-width="1920"
|
|
||||||
inkscape:window-height="1080"
|
|
||||||
inkscape:window-x="86"
|
|
||||||
inkscape:window-y="117"
|
|
||||||
inkscape:window-maximized="0" />
|
|
||||||
<metadata
|
|
||||||
id="metadata848">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
<dc:title></dc:title>
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<g
|
|
||||||
inkscape:label="Layer 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1"
|
|
||||||
transform="translate(-52.847684,-108.31232)">
|
|
||||||
<text
|
|
||||||
y="-57.540855"
|
|
||||||
x="117.23324"
|
|
||||||
class="st2 st3"
|
|
||||||
id="text18"
|
|
||||||
style="font-size:11.17903996px;font-family:ZapfDingbatsITC;stroke-width:0.26458332"
|
|
||||||
transform="rotate(90)">✂</text>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="payer">
|
|
||||||
<h2>Zahlteil</h2>
|
|
||||||
<div class="qr-code">
|
|
||||||
|
|
||||||
<div class="qr-section-img">
|
|
||||||
<image src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIAAQMAAADOtka5AAAABlBMVEX///8AAABVwtN+AAAFNElEQVR42uydMZLySgyERREQcgSO4qPB0TgKRyDcwGW9stRqybsEL/2LdrRrzEeimpFbLY15Xpv5Y/H3ZT3l/z/nzcyWl13yo9fV15Ob2e19cXezBV8UQIAEPMzMzpu5P83iw7v72+a1fxSA/Xqb7aTllR8JIAAAC+LuHk/td5eMzYg7Owfydf2xHfDEHdtjcw9SAQQ4AmLd+onHn5Z/5AIGdgB6bRNAgM+AuOLx2BLXeBwb4IMhuS97AgjwCYC9MbfEJN08wu3uL6s7b3yvnvm7uQrw3YCRre9L2v/+41O6L8D3AmZKfstM/P68Ie2KSMSSdom1LfZG+3gJ8NWAlA3ybW9fpTKTssv+1H1KC/yR4/4pgAABiLgrkcnxbleZVMtOe0rljocj/PLXTAABAjCFqCQFIK/aAONObIl72m7xs72kCSBArlupiD8iErnZlWxwzi0xVzv3/CiTrFUAAQoQkViFFKPsjWJLigSIu1Q083uZiLkLIAAAlaRnIaVkp1abVsQmhKjcP0NIQJYmgABZtL297Vwp+dQGssZSO+HFCchIxI8IIEAA4sptEiKBMyU3S9nJsX9mto4L738CCLAZXuBib3QfskG+/0Wxd0Qrqnb42XhGAAEA2JOszJsq7p6VdlnGHWp0WMlAwjuiAAIQcKXrBN/LnZB/9N5orLHUwwII0PYBL7NAmk0Qm7hjWO18a4tcvgie3QUQgIDXWMCoDfTeePL2ymWStfX6ZwIIcK5sPfWDR9VYUj+Ypbk7FKleyR60YQogQNrhIhJpfytnU6rmEKLcX1ZCwvWYdgkgQJZIxtseFrCyyEE2SGmhyy+oFoMkgAApWtK1xCvyd6ZdtIGzjjeTLAEEMBTrKrjuE/JDQ9wKIaoUzfewqGwmgAAljbNGNwy5NpBDdjqsdrBBCSDAybklHnwEJU3VajcNKd7lFxpZBBAgrlmRqxrvevJulesFDB0q+VHUWAQQoGwkXhU56AclZMbaVj1zXMmwbWIhFECA2c8UVzVW2s17AfOtbJgpNpSigMYCAQQAgAlUtzF1q1y9HHarU3VhdiQKIICj6fZgn2z7m9feyA6nqrHY0iNPBBAgCyn9uMPZdGUX7phCQWflMH27AAKc2bLdb3urDR9K+ihzk6Qild+jVVwAAQydtoiyMiv1OCUqCs4u3vbKVRuKAAJUjaWsAfAoZZTZQVGoOyj2JjKzLQEEmB6B1i+NJLgG2lkQqtWhICOAAA0o2ZJzuZC298hAG9HaGf1JAAEIGDMnMDyAhhROyqme3fEMei4FEKB6vnMu4EigOFepxyqzi7f84FaDKgUQwH1avB/LHBXAQd3rYXgJ7rSfTgABMI2EXt058pbFuss6Ry6N2Se/2lAE+HYAJaUx+7ZkS/caFWD2IVv/NcZCgK8G9Hxuvv+x2FKN3cOQYlOswtEBAgjQEyZYWqHa1BOX/9To0OpUPhQBBGCHZS9XxlMpesBbVYZbbLA+zEQAAWowCRTNvydO0NkEH0GZvnlOhQACdESdObJ0zi717h6omQGYxsSzvFwAAQbArJsGGGVcyUar7mg1uA4figBfDzicQVHTTKuxEjthA9hqUGKDAAKMSV09hDJLK9XP3V7dXtJKR6+MTAABTm6HkwN5vgTPUCqLANe29qFwHooAAkwAzzJlPxOHKDPJon0AVbtDJAogQI0D5OxkP7ztfThvuY/HEUCAbR5mMnp2235ysFiyn8l9VF0EEKAHdSO4euQkLp6lXMO8lzGFYihZAvzjgP8CAAD//4aIC6aSJNHQAAAAAElFTkSuQmCC"></image>
|
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 20.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
version="1.1"
|
|
||||||
id="Ebene_2"
|
|
||||||
x="0px"
|
|
||||||
y="0px"
|
|
||||||
viewBox="0 0 128 128"
|
|
||||||
xml:space="preserve"
|
|
||||||
sodipodi:docname="ch-cross.svg"
|
|
||||||
width="128"
|
|
||||||
height="128"
|
|
||||||
inkscape:export-filename="/home/irvin/Work/repos/swiss-qr-invoice/assets/raw/ch-cross.png"
|
|
||||||
inkscape:export-xdpi="232.22856"
|
|
||||||
inkscape:export-ydpi="232.22856"
|
|
||||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
|
||||||
style="shape-rendering:crispEdges"><metadata
|
|
||||||
id="metadata17"><rdf:RDF><cc:Work
|
|
||||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
|
||||||
id="defs15" /><sodipodi:namedview
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1"
|
|
||||||
objecttolerance="10"
|
|
||||||
gridtolerance="10"
|
|
||||||
guidetolerance="10"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:window-width="1668"
|
|
||||||
inkscape:window-height="1021"
|
|
||||||
id="namedview13"
|
|
||||||
showgrid="false"
|
|
||||||
inkscape:document-units="px"
|
|
||||||
units="px"
|
|
||||||
inkscape:zoom="2.8284271"
|
|
||||||
inkscape:cx="-38.018193"
|
|
||||||
inkscape:cy="61.405051"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="31"
|
|
||||||
inkscape:window-maximized="0"
|
|
||||||
inkscape:current-layer="Ebene_2"><inkscape:grid
|
|
||||||
type="xygrid"
|
|
||||||
id="grid916" /></sodipodi:namedview>
|
|
||||||
<style
|
|
||||||
type="text/css"
|
|
||||||
id="style2">
|
|
||||||
.st0{fill:#FFFFFF;}
|
|
||||||
.st1{fill:none;stroke:#FFFFFF;stroke-width:1.4357;stroke-miterlimit:10;}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<g
|
|
||||||
id="g4736"
|
|
||||||
transform="matrix(4.8380969,0,0,4.8380389,-1.9834927e-5,9.0008541e-4)"><polygon
|
|
||||||
transform="matrix(1.3598365,0,0,1.3598365,-0.23403522,-0.23403522)"
|
|
||||||
style="fill:#000000;fill-opacity:1;stroke-width:0.73538256"
|
|
||||||
id="polygon4"
|
|
||||||
points="0.7,0.7 0.7,1.6 0.7,18.3 0.7,19.1 1.6,19.1 18.3,19.1 19.1,19.1 19.1,18.3 19.1,1.6 19.1,0.7 18.3,0.7 1.6,0.7 " /><rect
|
|
||||||
style="fill:#ffffff;fill-opacity:1;stroke-width:1"
|
|
||||||
id="rect6"
|
|
||||||
height="14.958201"
|
|
||||||
width="4.4874606"
|
|
||||||
class="st0"
|
|
||||||
y="5.2231627"
|
|
||||||
x="11.034758" /><rect
|
|
||||||
style="fill:#fffff9;fill-opacity:1;stroke-width:1"
|
|
||||||
id="rect8"
|
|
||||||
height="4.4874606"
|
|
||||||
width="14.958201"
|
|
||||||
class="st0"
|
|
||||||
y="10.526525"
|
|
||||||
x="5.7313952" /><polygon
|
|
||||||
transform="matrix(1.3598207,0,0,1.3598361,-0.2338788,-0.23403126)"
|
|
||||||
style="fill:none;stroke:#ffffff;stroke-width:1.05601561;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
id="polygon10"
|
|
||||||
points="1.6,0.7 0.7,0.7 0.7,1.6 0.7,18.3 0.7,19.1 1.6,19.1 18.3,19.1 19.1,19.1 19.1,18.3 19.1,1.6 19.1,0.7 18.3,0.7 "
|
|
||||||
class="st1" /></g>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="qr-section-info">
|
|
||||||
<h4>Währung</h4>
|
|
||||||
<h4>Betrag</h4>
|
|
||||||
<p>CHF</p>
|
|
||||||
<p>177.35</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="payment-details">
|
|
||||||
<div>
|
|
||||||
<h4 style="margin-top: 0">Konto / Zahlbar an</h4>
|
|
||||||
<p>
|
|
||||||
CH06 0079 0042 5877 0443 7 <br />
|
|
||||||
schreifuchs.ch <br />
|
|
||||||
Kilchbergerweg 1 <br />
|
|
||||||
Zollikofen
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4>Referenz</h4>
|
|
||||||
<p>20 25082 41117 00284 67114 43342</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4>Zahlbar durch</h4>
|
|
||||||
<p>
|
|
||||||
Lou Taylor <br />
|
|
||||||
Alpenstrasse 22 <br />
|
|
||||||
Huttwil
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h2 style="margin-top: 0">Details zu den Features</h2>
|
|
||||||
|
|
||||||
|
|
||||||
<article>
|
|
||||||
<div class="issue-title">
|
|
||||||
<h3>
|
|
||||||
<span style="font-family: monospace">API-6</span>: Multilingual
|
|
||||||
</h3>
|
|
||||||
<p>Fertiggestellt: 21.08.2025 18:05</p>
|
|
||||||
</div>
|
|
||||||
<p>The application must support English and German.</p>
|
|
||||||
|
|
||||||
<h5 id="todo-s">TODO’s</h5>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><input type="checkbox" checked disabled> Add multiple languages to Pages</li>
|
|
||||||
<li><input type="checkbox" checked disabled> Add multiple languages to Events</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<article>
|
|
||||||
<div class="issue-title">
|
|
||||||
<h3>
|
|
||||||
<span style="font-family: monospace">ACC-7</span>: asdfasdf
|
|
||||||
</h3>
|
|
||||||
<p>Fertiggestellt: 23.08.2025 14:56</p>
|
|
||||||
</div>
|
|
||||||
<h4 id="adökfjaösldkjflaa">adökfjaösldkjflaa</h4>
|
|
||||||
|
|
||||||
<h5 id="asdfads">ASDFADS</h5>
|
|
||||||
|
|
||||||
<h6 id="adllglggl">adllglggl</h6>
|
|
||||||
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<article>
|
|
||||||
<div class="issue-title">
|
|
||||||
<h3>
|
|
||||||
<span style="font-family: monospace">ACC-6</span>: ss
|
|
||||||
</h3>
|
|
||||||
<p>Fertiggestellt: 23.08.2025 14:56</p>
|
|
||||||
</div>
|
|
||||||
<h4 id="adökfjaösldkjflaa">adökfjaösldkjflaa</h4>
|
|
||||||
|
|
||||||
<h5 id="asdfads">ASDFADS</h5>
|
|
||||||
|
|
||||||
<h6 id="adllglggl">adllglggl</h6>
|
|
||||||
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<article>
|
|
||||||
<div class="issue-title">
|
|
||||||
<h3>
|
|
||||||
<span style="font-family: monospace">ACC-5</span>: a
|
|
||||||
</h3>
|
|
||||||
<p>Fertiggestellt: 23.08.2025 14:56</p>
|
|
||||||
</div>
|
|
||||||
<h4 id="adökfjaösldkjflaa">adökfjaösldkjflaa</h4>
|
|
||||||
|
|
||||||
<h5 id="asdfads">ASDFADS</h5>
|
|
||||||
|
|
||||||
<h6 id="adllglggl">adllglggl</h6>
|
|
||||||
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<article>
|
|
||||||
<div class="issue-title">
|
|
||||||
<h3>
|
|
||||||
<span style="font-family: monospace">ACC-4</span>: Hello ```info duration: 1h 43min ``` # adökfjaösldkjflaa ## ASDFADS ### adllglggl
|
|
||||||
</h3>
|
|
||||||
<p>Fertiggestellt: 23.08.2025 14:56</p>
|
|
||||||
</div>
|
|
||||||
<h4 id="adökfjaösldkjflaa">adökfjaösldkjflaa</h4>
|
|
||||||
|
|
||||||
<h5 id="asdfads">ASDFADS</h5>
|
|
||||||
|
|
||||||
<h6 id="adllglggl">adllglggl</h6>
|
|
||||||
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<article>
|
|
||||||
<div class="issue-title">
|
|
||||||
<h3>
|
|
||||||
<span style="font-family: monospace">ACC-3</span>: ss
|
|
||||||
</h3>
|
|
||||||
<p>Fertiggestellt: 23.08.2025 14:55</p>
|
|
||||||
</div>
|
|
||||||
<h4 id="adökfjaösldkjflaa">adökfjaösldkjflaa</h4>
|
|
||||||
|
|
||||||
<h5 id="asdfads">ASDFADS</h5>
|
|
||||||
|
|
||||||
<h6 id="adllglggl">adllglggl</h6>
|
|
||||||
|
|
||||||
</article>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
BIN
invoice.pdf
BIN
invoice.pdf
Binary file not shown.
@@ -32,7 +32,11 @@ func FromGiteas(is []*gitea.Issue, mindur time.Duration) []Issue {
|
|||||||
func FromGitea(i gitea.Issue) Issue {
|
func FromGitea(i gitea.Issue) Issue {
|
||||||
issue := Issue{Issue: i}
|
issue := Issue{Issue: i}
|
||||||
|
|
||||||
issue.Duration, _ = ExtractDuration(i.Body)
|
var err error
|
||||||
|
issue.Duration, err = ExtractDuration(i.Body)
|
||||||
|
if err != nil {
|
||||||
|
issue.Duration = time.Second * 0
|
||||||
|
}
|
||||||
|
|
||||||
return issue
|
return issue
|
||||||
}
|
}
|
||||||
@@ -43,7 +47,7 @@ func ExtractDuration(text string) (duration time.Duration, err error) {
|
|||||||
block := reBlock.FindStringSubmatch(text)
|
block := reBlock.FindStringSubmatch(text)
|
||||||
if len(block) < 2 {
|
if len(block) < 2 {
|
||||||
err = fmt.Errorf("no info block found")
|
err = fmt.Errorf("no info block found")
|
||||||
return
|
return duration, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now extract the duration line from inside that block
|
// Now extract the duration line from inside that block
|
||||||
@@ -51,7 +55,7 @@ func ExtractDuration(text string) (duration time.Duration, err error) {
|
|||||||
match := reDuration.FindStringSubmatch(block[1])
|
match := reDuration.FindStringSubmatch(block[1])
|
||||||
if len(match) < 2 {
|
if len(match) < 2 {
|
||||||
err = fmt.Errorf("no duration found inside info block")
|
err = fmt.Errorf("no duration found inside info block")
|
||||||
return
|
return duration, err
|
||||||
}
|
}
|
||||||
dur := strings.TrimSpace(match[1])
|
dur := strings.TrimSpace(match[1])
|
||||||
dur = strings.ReplaceAll(dur, "min", "m")
|
dur = strings.ReplaceAll(dur, "min", "m")
|
||||||
|
|||||||
@@ -82,10 +82,14 @@
|
|||||||
<article>
|
<article>
|
||||||
<div class="issue-title">
|
<div class="issue-title">
|
||||||
<h3>
|
<h3>
|
||||||
<span style="font-family: monospace">{{ .Shorthand }}</span>: {{
|
<a href="{{ .HTMLURL }}" style="font-family: monospace"
|
||||||
.Title }}
|
>{{ .Shorthand }}</a
|
||||||
|
>: {{ .Title }}
|
||||||
</h3>
|
</h3>
|
||||||
<p>Fertiggestellt: {{ .Closed | time }}</p>
|
<p>
|
||||||
|
{{ if .Closed }} Fertiggestellt: {{ .Closed | time }} {{else}} not
|
||||||
|
closed {{ end }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{{ .CleanBody | md | oh 3 }}
|
{{ .CleanBody | md | oh 3 }}
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -35,11 +35,11 @@ func (r Report) ToHTML() (html string, err error) {
|
|||||||
|
|
||||||
err = tmpl.Execute(buf, tmpler{r, style})
|
err = tmpl.Execute(buf, tmpler{r, style})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return html, err
|
||||||
}
|
}
|
||||||
|
|
||||||
html = buf.String()
|
html = buf.String()
|
||||||
return
|
return html, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Report) applyRate(dur time.Duration) string {
|
func (r Report) applyRate(dur time.Duration) string {
|
||||||
|
|||||||
Reference in New Issue
Block a user