summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--examples/invoice-2024-001.yml55
-rw-r--r--examples/quote-2024-001.yml55
-rwxr-xr-xinvoice-gen14
-rw-r--r--templates/company.gohtml10
-rw-r--r--templates/invoice.gohtml118
-rw-r--r--templates/styles.css159
-rw-r--r--templates/template.gohtml55
-rw-r--r--templates/terms.gohtml7
-rw-r--r--templates/translations.yml79
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
remember that computers suck.