diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | examples/invoice-2024-001.yml | 55 | ||||
-rw-r--r-- | examples/quote-2024-001.yml | 55 | ||||
-rwxr-xr-x | invoice-gen | 14 | ||||
-rw-r--r-- | templates/company.gohtml | 10 | ||||
-rw-r--r-- | templates/invoice.gohtml | 118 | ||||
-rw-r--r-- | templates/styles.css | 159 | ||||
-rw-r--r-- | templates/template.gohtml | 55 | ||||
-rw-r--r-- | templates/terms.gohtml | 7 | ||||
-rw-r--r-- | templates/translations.yml | 79 |
10 files changed, 554 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f47bc6f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +examples/*.html +examples/*.pdf diff --git a/examples/invoice-2024-001.yml b/examples/invoice-2024-001.yml new file mode 100644 index 0000000..d032cc8 --- /dev/null +++ b/examples/invoice-2024-001.yml @@ -0,0 +1,55 @@ +--- + +number: 2024-001 +date: 2024-07-09 +due_date: 2024-08-01 +type: invoice + +notes: + en: Thank you for your business! + fr: Merci pour votre confiance ! + +author: + name: NAME + address: ADDRESS + email: EMAIL + website: WEBSITE + phone: "+123456789" + +bank: + name: BANK + iban: FFFFFFF + swift: PPXXXX + +tax: + vat: + en: No applicable VAT number. + fr: Pas de numéro de TVA applicable. + siret: abcde + +client: + name: NAME + address: ADDRESS + email: EMAIL + +items: + - name: + en: Programming + fr: Programmation + comment: + en: Lorem ipsum + fr: Ipsum lorem + rate: 1 + qty: 1 + sum: 1 + + - name: + en: System administration + fr: Administration de systèmes informatiques + comment: null + rate: 2.5 + qty: 10 + sum: 25 + +total: 26 +currency: EUR diff --git a/examples/quote-2024-001.yml b/examples/quote-2024-001.yml new file mode 100644 index 0000000..48ba442 --- /dev/null +++ b/examples/quote-2024-001.yml @@ -0,0 +1,55 @@ +--- + +number: 2024-001 +date: 2024-07-09 +due_date: 2024-08-01 +type: quote + +notes: + en: Thank you for your business! + fr: Merci pour votre confiance ! + +author: + name: NAME + address: ADDRESS + email: EMAIL + website: WEBSITE + phone: "+123456789" + +bank: + name: BANK + iban: FFFFFFF + swift: PPXXXX + +tax: + vat: + en: No applicable VAT number. + fr: Pas de numéro de TVA applicable. + siret: abcde + +client: + name: NAME + address: ADDRESS + email: EMAIL + +items: + - name: + en: Programming + fr: Programmation + comment: + en: Lorem ipsum + fr: Ipsum lorem + rate: 1 + qty: 1 + sum: 1 + + - name: + en: System administration + fr: Administration de systèmes informatiques + comment: null + rate: 2.5 + qty: 10 + sum: 25 + +total: 26 +currency: EUR diff --git a/invoice-gen b/invoice-gen new file mode 100755 index 0000000..b82050c --- /dev/null +++ b/invoice-gen @@ -0,0 +1,14 @@ +#!/bin/sh + +set -xe + +test -f "${1}" +filename="$(echo "${1}" | rev | cut -d "." -f 2- | rev)" +template="templates/template.gohtml" + +gomplate \ + -c data="${1}" \ + -c translations=templates/translations.yml \ + -f "${template}" \ + | tee "${filename}.html" \ + | weasyprint - "${filename}.pdf" diff --git a/templates/company.gohtml b/templates/company.gohtml new file mode 100644 index 0000000..f3a9cf6 --- /dev/null +++ b/templates/company.gohtml @@ -0,0 +1,10 @@ +<ul> + <li>{{ .data.author.name }}</li> + <li>{{ .data.author.address }}</li> + <li><a href="{{ .data.author.website }}">{{ .data.author.website }}</a></li> + <li><a href="mailto:{{ .data.author.email }}">{{ .data.author.email }}</a></li> + <li><a href="tel:{{ .data.author.phone }}">{{ .data.author.phone }}</a></li> +</ul> + +<section class="terms"> +</section> diff --git a/templates/invoice.gohtml b/templates/invoice.gohtml new file mode 100644 index 0000000..fe7fece --- /dev/null +++ b/templates/invoice.gohtml @@ -0,0 +1,118 @@ +<header> + <article class="company"> + <h2>{{ index .translations.invoice_from .ctx.lang }}</h2> + {{ tpl (file.Read "templates/company.gohtml") + (dict + "ctx" .ctx + "data" .data + "translations" .translations) }} + </article> + + <article class="metadata"> + <table> + <tr> + + {{ if (eq .data.type "invoice") }} + <td><h2>{{ index .translations.invoice .ctx.lang }}</h2></td> + {{ else }} + <td><h2>{{ index .translations.quote .ctx.lang }}</h2></td> + {{ end }} + <td><h2>#{{ .data.number }}</h2></td> + </tr> + <tr> + + {{ if (eq .data.type "invoice") }} + <td>{{ index .translations.invoice_issued_at .ctx.lang }}</td> + {{ else }} + <td>{{ index .translations.quote_issued_at .ctx.lang }}</td> + {{ end }} + + <td>{{ .data.date.Format "2006-01-02" }}</td> + </tr> + + {{ if (eq .data.type "invoice") }} + <tr> + <td>{{ index .translations.invoice_due_at .ctx.lang }}</td> + <td>{{ .data.due_date.Format "2006-01-02" }}</td> + </tr> + {{ end }} + </table> + </article> +</header> + +<section> + <article class="client"> + <h2>{{ index .translations.invoice_to .ctx.lang }}</h2> + <ul> + <li>{{ .data.client.name }}</li> + <li>{{ .data.client.address }}</li> + <li>{{ .data.client.email }}</li> + </ul> + </article> +</section> + +<section> + <article class="items"> + <table> + <thead> + <tr> + <th>{{ index .translations.item .ctx.lang }}</th> + <th>{{ index .translations.rate .ctx.lang }}</th> + <th>{{ index .translations.quantity .ctx.lang }}</th> + <th>{{ index .translations.amount .ctx.lang }}</th> + </tr> + </thead> + <tbody> + {{ $ctx := .ctx }} + {{ range .data.items }} + <tr> + <td> + {{ index .name $ctx.lang }} + {{ if (has .comment $ctx.lang) }} + <p class="comment">{{ index .comment $ctx.lang }}</p> + {{ end }} + </td> + <td>{{ .rate }}</td> + <td>{{ .qty }}</td> + <td>{{ .sum }}</td> + </tr> + {{ end }} + </tbody> + <tfoot> + <tr> + <td colspan="3">{{ index .translations.total_in .ctx.lang }} {{ .data.currency }}</td> + <td>{{ .data.total }}</td> + </tr> + </tfoot> + </table> + </article> +</section> + +<section> + {{ if .data.notes }} + <article class="notes"> + <p>{{ index .data.notes .ctx.lang }}</p> + </article> + {{ end }} +</section> + +<footer> + {{ if (eq .data.type "invoice") }} + <article class="bank"> + <h2>{{ index .translations.bank_details .ctx.lang }}</h2> + <table> + <tr><td>{{ index .translations.bank_name .ctx.lang }}</td><td>{{ .data.bank.name }}</td></tr> + <tr><td>SWIFT</td><td>{{ .data.bank.swift }}</td></tr> + <tr><td>IBAN</td><td>{{ .data.bank.iban }}</td></tr> + </table> + </article> + {{ end }} + + <article class="tax"> + <h2>{{ index .translations.tax_details .ctx.lang }}</h2> + <table> + <tr><td>SIRET</td><td>{{ .data.tax.siret }}</td></tr> + <tr><td>{{ index .translations.tax_vat .ctx.lang }}</td><td>{{ index .data.tax.vat .ctx.lang }}</td></tr> + </table> + </article> +</footer> diff --git a/templates/styles.css b/templates/styles.css new file mode 100644 index 0000000..cb0b736 --- /dev/null +++ b/templates/styles.css @@ -0,0 +1,159 @@ +@page { + size: A4 portrait; + margin: 16mm; + + @bottom-right-corner { + content: counter(page) '/' counter(pages); + margin-left: 1em; + } +} + +@media print { + footer { + position: absolute; + bottom: 0; + } +} + +@media screen { + html { + margin: 4em auto 4em auto; + width: 60em; + } + + body > section:not(:first-child) { + margin-top: 12em; + } +} + +/* */ + +* { + margin: 0; + padding: 0; + font-family: monospace, monospace; + font-size: 14px; + color: #111; +} + +h1, h2, h3 { + margin: 0; + color: #444; +} + +h2 { + font-size: 1.2em; +} + +ul li { + list-style: none; +} + +a, a:visited { + color: #000; +} + +article { + margin-top: 2em; + margin-bottom: 2em; +} + +header article { + margin: 0; +} + +header { + display: flex; +} + +footer { + width: 100%; +} + +body > section { + break-before: always; +} + +/* table */ + +table * { + vertical-align: top; +} + +table { + border-collapse: collapse; +} + +table { + width: 100% +} + +/* items */ + +article.items table { + width: 100%; + margin: 4em 0 4em 0; +} + +article.items table tr th:last-child, +article.items table tr td:last-child { + text-align: right; +} + +article.items table thead tr th:first-child { + width: 60%; +} + +article.items table tfoot, table thead { + border-bottom: solid 1px black; +} + +article.items table tfoot { + border-top: solid 1px black; +} + +article.items table tfoot td { + font-weight: 700; +} + +/* metadata */ + +article.metadata { + width: 40%; +} + +article.metadata table { + width: 100%; +} + +article.metadata table tr td:first-child { + width: 50%; +} + +/* */ + +article.tax table tr td:first-child, +article.bank table tr td:first-child { + width: 20%; +} + + +article.company { + width: 60%; +} + +article.bank { + padding-top: 1em; + border-top: solid 1px #444; +} + +article.annex p { + background-color: #000; + color: #fff; + font-weight: 700; +} + +p.comment { + color: #444; + margin-left: .5em; +} diff --git a/templates/template.gohtml b/templates/template.gohtml new file mode 100644 index 0000000..911bfdd --- /dev/null +++ b/templates/template.gohtml @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"/> + <style> + {{ tpl (file.Read "templates/styles.css") }} + </style> +</head> + +<body> + <section> + {{ tpl (file.Read "templates/invoice.gohtml") + (dict + "ctx" (dict "lang" "fr") + "data" .data + "translations" .translations) }} + </section> + + <section> + <article class="annex"> + <p>annexe 01: facture en anglais / invoice</p> + </article> + {{ tpl (file.Read "templates/invoice.gohtml") + (dict + "ctx" (dict "lang" "en") + "data" .data + "translations" .translations) }} + </section> + + <!--- + <section> + <article class="annex"> + <p>annexe 02: conditions général de vente</p> + </article> + {{ tpl (file.Read "templates/terms.gohtml") + (dict + "ctx" (dict "lang" "fr") + "data" .data + "translations" .translations) }} + </section> + + <section> + <article class="annex"> + <p>annexe 03: conditions générales de vente en anglais / terms and conditions</p> + </article> + {{ tpl (file.Read "templates/terms.gohtml") + (dict + "ctx" (dict "lang" "en") + "data" .data + "translations" .translations) }} + </section> + ---> + +</body> +</html> diff --git a/templates/terms.gohtml b/templates/terms.gohtml new file mode 100644 index 0000000..34ac778 --- /dev/null +++ b/templates/terms.gohtml @@ -0,0 +1,7 @@ +<article class="company"> + {{ tpl (file.Read "templates/company.gohtml") + (dict + "ctx" .ctx + "data" .data + "translations" .translations) }} +</article> diff --git a/templates/translations.yml b/templates/translations.yml new file mode 100644 index 0000000..7a9a1f2 --- /dev/null +++ b/templates/translations.yml @@ -0,0 +1,79 @@ +--- + +# invoice + +invoice: + en: Invoice + fr: Facture + +invoice_issued_at: + en: issued at + fr: émise le + +invoice_due_at: + en: due at + fr: échéance le + +# quote + +quote: + en: Quote + fr: Devis + +quote_issued_at: + en: issued at + fr: émis le + +# company + +invoice_from: + en: Invoice from + fr: Entreprise + +# client + +invoice_to: + en: Invoice to + fr: Client + +# item + +item: + en: Item + fr: Item + +rate: + en: Rate + fr: Taux + +quantity: + en: Quantity + fr: Quantité + +amount: + en: Amount + fr: Montant + +total_in: + en: Total in + fr: Total en + +# bank + +bank_details: + en: Bank details + fr: Coordonnées bancaires + +bank_name: + en: bank + fr: banque + +# tax + +tax_details: + en: Tax details + fr: Taxes + +tax_vat: + en: VAT + fr: TVA |