Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Add XMP metadata to ZUGFeRD #27

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Normalize opt name,add example
apardods committed Dec 20, 2024
commit 905792a51ba7888df36ff674454b729757edb94b
2 changes: 2 additions & 0 deletions cmd/gobl.html/convert.go
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ import (

type convertOpts struct {
*rootOpts
Zugferd bool
}

func convert(o *rootOpts) *convertOpts {
@@ -24,6 +25,7 @@ func (c *convertOpts) cmd() *cobra.Command {
Short: "Convert a GOBL JSON into an HTML file",
RunE: c.runE,
}
cmd.Flags().BoolVar(&c.Zugferd, "zugferd", false, "Add Zugferd XMP metadata to the PDF")
return cmd
}

179 changes: 179 additions & 0 deletions examples/invoice-de-zugferd.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
{
"$schema": "https://gobl.org/draft-0/envelope",
"head": {
"uuid": "8a51fd30-2a27-11ee-be56-0242ac120002",
"dig": {
"alg": "sha256",
"val": "583130bdfd84c31d23f7bcdf778363ffdbc7eb132faf1180efb1be1fc438e069"
}
},
"doc": {
"$schema": "https://gobl.org/draft-0/bill/invoice",
"$regime": "DE",
"$addons": [
"eu-en16931-v2017"
],
"uuid": "019231f1-9128-71fc-b0e2-820af3036d1b",
"type": "standard",
"series": "SAMPLE",
"code": "001",
"issue_date": "2024-02-13",
"currency": "EUR",
"supplier": {
"name": "Provide One GmbH",
"tax_id": {
"country": "DE",
"code": "111111125"
},
"people": [
{
"name": {
"given": "John",
"surname": "Doe"
}
}
],
"addresses": [
{
"num": "16",
"street": "Dietmar-Hopp-Allee",
"locality": "Walldorf",
"code": "69190",
"country": "DE"
}
],
"emails": [
{
"addr": "[email protected]"
}
],
"telephones": [
{
"num": "+49100200300"
}
]
},
"customer": {
"name": "Sample Consumer",
"tax_id": {
"country": "DE",
"code": "282741168"
},
"addresses": [
{
"num": "25",
"street": "Werner-Heisenberg-Allee",
"locality": "München",
"code": "80939",
"country": "DE"
}
],
"emails": [
{
"addr": "[email protected]"
}
]
},
"lines": [
{
"i": 1,
"quantity": "20",
"item": {
"name": "Development services",
"price": "90.00",
"unit": "h"
},
"sum": "1800.00",
"taxes": [
{
"cat": "VAT",
"rate": "standard",
"percent": "19%"
}
],
"total": "1800.00"
}
],
"ordering": {
"code": "XR-2024-2",
"seller": {
"name": "Salescompany ltd.",
"tax_id": {
"country": "NO",
"code": "123456789MVA"
},
"identities": [
{
"label": "CompanyID",
"code": "123456789"
},
{
"label": "0088",
"code": "1238764941386"
}
],
"people": [
{
"name": {
"given": "Antonio Salesmacher"
}
}
],
"addresses": [
{
"street": "Main street 34",
"street_extra": "Suite 123",
"locality": "Big city",
"region": "RegionA",
"code": "303",
"country": "NO"
}
],
"emails": [
{
"addr": "[email protected]"
}
],
"telephones": [
{
"num": "46211230"
}
]
}
},
"payment": {
"terms": {
"detail": "lorem ipsum"
}
},
"tax": {
"ext": {
"untdid-document-type": "380"
}
},
"totals": {
"sum": "1800.00",
"total": "1800.00",
"taxes": {
"categories": [
{
"code": "VAT",
"rates": [
{
"key": "standard",
"base": "1800.00",
"percent": "19%",
"amount": "342.00"
}
],
"amount": "342.00"
}
],
"sum": "342.00"
},
"tax": "342.00",
"total_with_tax": "2142.00",
"payable": "2142.00"
}
}
}
152 changes: 152 additions & 0 deletions examples/out/invoice-de-zugferd.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<html data-theme="light">
<head>
<title>GOBL HTML Generator</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:[email protected]&amp;display=swap" rel="stylesheet" />
<link rel="stylesheet" href="styles/envelope.css" />
<link rel="stylesheet" href="styles/invoice.css" />
</head>
<body>
<article class="envelope">
<footer class="print">
<span class="page">Page <span class="page-number">1</span> of <span class="pages-number">1</span></span>
<div class="gobl-logo">
<a href="https://gobl.org">
<img
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI1LjQuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkNhcGFfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiCgkgdmlld0JveD0iMCAwIDE4MC42IDIxOC44IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAxODAuNiAyMTguODsiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8Zz4KCTxwYXRoIGQ9Ik0xNzIsMTI3LjVWMzIuNmMwLTEuNC0wLjctMi44LTEuOS0zLjZjLTEuMi0wLjgtMi43LTAuOS00LTAuM2wtMzguOCwxNi44di0yN2MwLTEuNC0wLjctMi44LTEuOS0zLjYKCQljLTEuMi0wLjgtMi43LTAuOS00LTAuNEw4Mi42LDMxLjN2LTI3YzAtMS40LTAuNy0yLjgtMS45LTMuNmMtMS4yLTAuOC0yLjctMC45LTQtMC40TDExLDI4LjdjLTEuNiwwLjctMi42LDIuMi0yLjYsMy45djk0LjkKCQljMCwxLjQsMC43LDIuOCwxLjksMy42YzAuNywwLjUsMS41LDAuNywyLjMsMC43YzAuNiwwLDEuMS0wLjEsMS43LTAuM2wzOC44LTE2Ljh2MjdjMCwxLjQsMC43LDIuOCwxLjksMy42CgkJYzAuNywwLjUsMS41LDAuNywyLjMsMC43YzAuNiwwLDEuMS0wLjEsMS43LTAuM2wzOC44LTE2Ljh2MjdjMCwxLjQsMC43LDIuOCwxLjksMy42YzAuNywwLjUsMS41LDAuNywyLjMsMC43CgkJYzAuNiwwLDEuMS0wLjEsMS43LTAuM2w2NS42LTI4LjRDMTcxLDEzMC44LDE3MiwxMjkuMiwxNzIsMTI3LjV6IE0xNjMuNSwxMjQuN2wtNTcuMSwyNC43di0yNC4ybDE4LjMtNy45YzEuNi0wLjcsMi42LTIuMiwyLjYtMy45CgkJVjY0bC04LjUsMy43djQyLjhsLTEyLjQsNS40bC04LjUsMy43bC0zNi4yLDE1LjZWMTExbDE4LjMtNy45YzEuNi0wLjcsMi42LTIuMiwyLjYtMy45VjQ5LjhsLTguNSwzLjd2NDIuOGwtMTIuNCw1LjRsLTguNSwzLjcKCQlMMTcsMTIxVjM1LjRsNTcuMS0yNC43VjM1bC0xOC4zLDcuOWMtMS42LDAuNy0yLjYsMi4yLTIuNiwzLjl2NDkuM2w4LjUtMy43VjQ5LjZsMTIuNC01LjRsOC41LTMuN2wzNi4yLTE1LjZ2MjQuMmwtMTguMyw3LjkKCQljLTEuNiwwLjctMi42LDIuMi0yLjYsMy45djQ5LjNsOC41LTMuN1Y2My44bDEyLjQtNS40bDguNS0zLjdsMzYuMi0xNS42VjEyNC43eiIvPgoJPGc+CgkJPGc+CgkJCTxwYXRoIGQ9Ik00NC4yLDE4Ny40Yy0yLjMtNy4xLTkuNC0xMS40LTIwLjEtMTEuNEM5LjEsMTc2LDAsMTg0LjcsMCwxOTcuNGMwLDEzLDkuMSwyMS41LDIzLDIxLjVjMTYsMCwyMi45LTguNiwyMi45LTIwLjUKCQkJCWMwLTEuMiwwLTIuNS0wLjMtMy44SDIzLjJ2N2gxMi41YzAuMiw1LjYtNS4yLDkuNC0xMS45LDkuNGMtOC42LDAtMTMtNS4zLTEzLTEzLjZjMC04LjMsNC41LTEzLjgsMTIuMy0xMy44CgkJCQljNC42LDAsOC40LDEuOSw5LjksNi4yTDQ0LjIsMTg3LjR6Ii8+CgkJCTxwYXRoIGQ9Ik03NC42LDIxMC45Yy03LjYsMC0xMi40LTUuMy0xMi40LTEzLjVjMC04LjMsNC44LTEzLjUsMTIuNC0xMy41YzcuNiwwLDEyLjQsNS4yLDEyLjQsMTMuNQoJCQkJQzg3LDIwNS43LDgyLjIsMjEwLjksNzQuNiwyMTAuOXogTTc0LjYsMjE4LjhjMTQsMCwyMy4xLTguMywyMy4xLTIxLjRjMC0xMy4xLTkuMS0yMS40LTIzLjEtMjEuNHMtMjMuMSw4LjMtMjMuMSwyMS40CgkJCQlDNTEuNiwyMTAuNSw2MC43LDIxOC44LDc0LjYsMjE4Ljh6Ii8+CgkJCTxwYXRoIGQ9Ik0xMjUsMjEwLjdoLTExLjJ2LTEwLjNoMTEuM2M0LjIsMCw2LjMsMiw2LjMsNS4xQzEzMS40LDIwOC40LDEyOS40LDIxMC43LDEyNSwyMTAuN3ogTTExMy44LDE4NC4yaDEwLjYKCQkJCWM0LjMsMCw1LjksMi4xLDUuOSw0LjZjMCwyLjYtMS40LDQuOS01LjksNC45aC0xMC42VjE4NC4yeiBNMTMzLjQsMTk2LjVjNS0xLjIsNy42LTQuNyw3LjYtOWMwLTUuOS00LjMtMTAuMy0xMy44LTEwLjNoLTIzLjYKCQkJCXY0MC41aDI0LjFjOS45LDAsMTQuNC01LjEsMTQuNC0xMS41QzE0Mi4xLDIwMC45LDEzOS4xLDE5Ny40LDEzMy40LDE5Ni41eiIvPgoJCQk8cG9seWdvbiBwb2ludHM9IjE0OS4yLDE3Ny4yIDE0OS4yLDIxNy43IDE4MC42LDIxNy43IDE4MC42LDIwOS41IDE1OS40LDIwOS41IDE1OS40LDE3Ny4yIAkJCSIvPgoJCTwvZz4KCTwvZz4KPC9nPgo8L3N2Zz4K"
alt="GOBL"
/>
</a>
</div>
</footer>
<article class="invoice">
<header class="regular">
<div class="details">
<section class="title">
<div class="hero"><div class="alias">Provide One GmbH</div></div>
<h1 class="type">Invoice</h1>
<h2 class="code">SAMPLE-001</h2>
</section>
<section class="summary">
<h2 class="title">Summary</h2>
<ul>
<li class="issue-date"><span class="label">Issue Date</span> <span class="value">2024-02-13</span></li>
<li class="currency"><span class="label">Currency</span> <span class="value">Euro (EUR)</span></li>
<li class="ordering-code"><span class="label">Order</span> <span class="value">XR-2024-2</span></li>
</ul>
</section>
</div>
<div class="contacts">
<section class="supplier">
<h2>Supplier</h2>
<div class="org-party">
<div class="name">Provide One GmbH</div>
<div class="person">John Doe</div>
<div class="org-address"><span>Dietmar-Hopp-Allee 16, Walldorf, 69190 (Germany)</span></div>
<div class="telephone">Tel: +49100200300</div>
<div class="emails">Email: [email protected]</div>
<div class="tax-id">TIN: (DE) 111111125</div>
</div>
</section>
<section class="customer">
<h2>Customer</h2>
<div class="org-party">
<div class="name">Sample Consumer</div>
<div class="org-address"><span>Werner-Heisenberg-Allee 25, München, 80939 (Germany)</span></div>
<div class="emails">Email: [email protected]</div>
<div class="tax-id">TIN: (DE) 282741168</div>
</div>
</section>
</div>
</header>
<section class="lines">
<h2>Lines</h2>
<table>
<thead>
<tr>
<th class="ref">#</th>
<th class="description">Description</th>
<th class="quantity">Qty.</th>
<th class="unit">Unit</th>
<th class="price">Price</th>
<th class="tax">VAT</th>
<th class="total">Total</th>
</tr>
</thead>
<tbody>
<tr>
<td class="ref">1</td>
<td class="description"><span>Development services</span></td>
<td class="quantity">20</td>
<td class="unit">h</td>
<td class="price">€90,00</td>
<td class="tax">19%</td>
<td class="total">€1.800,00</td>
</tr>
</tbody>
</table>
</section>
<div class="totals">
<section class="totals">
<h2>Totals</h2>
<table>
<tbody>
<tr class="sum strong">
<th>Sum</th>
<td>€1.800,00</td>
</tr>
<tr class="tax">
<th>Tax</th>
<td>€342,00</td>
</tr>
<tr class="payable strong">
<th>Total to pay</th>
<td>€2.142,00</td>
</tr>
</tbody>
</table>
</section>
<section class="taxes">
<h2>Taxes</h2>
<table>
<thead>
<tr>
<th class="category">Tax</th>
<th class="base">Base</th>
<th class="rate">Rate</th>
<th class="amount">Amount</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="1" class="category">VAT</td>
<td class="base">€1.800,00</td>
<td class="rate">19%</td>
<td class="amount">€342,00</td>
</tr>
</tbody>
</table>
</section>
</div>
<section class="payment">
<h2 class="title">Payment</h2>
<ul class="terms"></ul>
</section>
<div class="extensions"></div>
</article>
<footer class="screen">
<div class="gobl-logo">
<a href="https://gobl.org">
<img
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI1LjQuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkNhcGFfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiCgkgdmlld0JveD0iMCAwIDE4MC42IDIxOC44IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAxODAuNiAyMTguODsiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8Zz4KCTxwYXRoIGQ9Ik0xNzIsMTI3LjVWMzIuNmMwLTEuNC0wLjctMi44LTEuOS0zLjZjLTEuMi0wLjgtMi43LTAuOS00LTAuM2wtMzguOCwxNi44di0yN2MwLTEuNC0wLjctMi44LTEuOS0zLjYKCQljLTEuMi0wLjgtMi43LTAuOS00LTAuNEw4Mi42LDMxLjN2LTI3YzAtMS40LTAuNy0yLjgtMS45LTMuNmMtMS4yLTAuOC0yLjctMC45LTQtMC40TDExLDI4LjdjLTEuNiwwLjctMi42LDIuMi0yLjYsMy45djk0LjkKCQljMCwxLjQsMC43LDIuOCwxLjksMy42YzAuNywwLjUsMS41LDAuNywyLjMsMC43YzAuNiwwLDEuMS0wLjEsMS43LTAuM2wzOC44LTE2Ljh2MjdjMCwxLjQsMC43LDIuOCwxLjksMy42CgkJYzAuNywwLjUsMS41LDAuNywyLjMsMC43YzAuNiwwLDEuMS0wLjEsMS43LTAuM2wzOC44LTE2Ljh2MjdjMCwxLjQsMC43LDIuOCwxLjksMy42YzAuNywwLjUsMS41LDAuNywyLjMsMC43CgkJYzAuNiwwLDEuMS0wLjEsMS43LTAuM2w2NS42LTI4LjRDMTcxLDEzMC44LDE3MiwxMjkuMiwxNzIsMTI3LjV6IE0xNjMuNSwxMjQuN2wtNTcuMSwyNC43di0yNC4ybDE4LjMtNy45YzEuNi0wLjcsMi42LTIuMiwyLjYtMy45CgkJVjY0bC04LjUsMy43djQyLjhsLTEyLjQsNS40bC04LjUsMy43bC0zNi4yLDE1LjZWMTExbDE4LjMtNy45YzEuNi0wLjcsMi42LTIuMiwyLjYtMy45VjQ5LjhsLTguNSwzLjd2NDIuOGwtMTIuNCw1LjRsLTguNSwzLjcKCQlMMTcsMTIxVjM1LjRsNTcuMS0yNC43VjM1bC0xOC4zLDcuOWMtMS42LDAuNy0yLjYsMi4yLTIuNiwzLjl2NDkuM2w4LjUtMy43VjQ5LjZsMTIuNC01LjRsOC41LTMuN2wzNi4yLTE1LjZ2MjQuMmwtMTguMyw3LjkKCQljLTEuNiwwLjctMi42LDIuMi0yLjYsMy45djQ5LjNsOC41LTMuN1Y2My44bDEyLjQtNS40bDguNS0zLjdsMzYuMi0xNS42VjEyNC43eiIvPgoJPGc+CgkJPGc+CgkJCTxwYXRoIGQ9Ik00NC4yLDE4Ny40Yy0yLjMtNy4xLTkuNC0xMS40LTIwLjEtMTEuNEM5LjEsMTc2LDAsMTg0LjcsMCwxOTcuNGMwLDEzLDkuMSwyMS41LDIzLDIxLjVjMTYsMCwyMi45LTguNiwyMi45LTIwLjUKCQkJCWMwLTEuMiwwLTIuNS0wLjMtMy44SDIzLjJ2N2gxMi41YzAuMiw1LjYtNS4yLDkuNC0xMS45LDkuNGMtOC42LDAtMTMtNS4zLTEzLTEzLjZjMC04LjMsNC41LTEzLjgsMTIuMy0xMy44CgkJCQljNC42LDAsOC40LDEuOSw5LjksNi4yTDQ0LjIsMTg3LjR6Ii8+CgkJCTxwYXRoIGQ9Ik03NC42LDIxMC45Yy03LjYsMC0xMi40LTUuMy0xMi40LTEzLjVjMC04LjMsNC44LTEzLjUsMTIuNC0xMy41YzcuNiwwLDEyLjQsNS4yLDEyLjQsMTMuNQoJCQkJQzg3LDIwNS43LDgyLjIsMjEwLjksNzQuNiwyMTAuOXogTTc0LjYsMjE4LjhjMTQsMCwyMy4xLTguMywyMy4xLTIxLjRjMC0xMy4xLTkuMS0yMS40LTIzLjEtMjEuNHMtMjMuMSw4LjMtMjMuMSwyMS40CgkJCQlDNTEuNiwyMTAuNSw2MC43LDIxOC44LDc0LjYsMjE4Ljh6Ii8+CgkJCTxwYXRoIGQ9Ik0xMjUsMjEwLjdoLTExLjJ2LTEwLjNoMTEuM2M0LjIsMCw2LjMsMiw2LjMsNS4xQzEzMS40LDIwOC40LDEyOS40LDIxMC43LDEyNSwyMTAuN3ogTTExMy44LDE4NC4yaDEwLjYKCQkJCWM0LjMsMCw1LjksMi4xLDUuOSw0LjZjMCwyLjYtMS40LDQuOS01LjksNC45aC0xMC42VjE4NC4yeiBNMTMzLjQsMTk2LjVjNS0xLjIsNy42LTQuNyw3LjYtOWMwLTUuOS00LjMtMTAuMy0xMy44LTEwLjNoLTIzLjYKCQkJCXY0MC41aDI0LjFjOS45LDAsMTQuNC01LjEsMTQuNC0xMS41QzE0Mi4xLDIwMC45LDEzOS4xLDE5Ny40LDEzMy40LDE5Ni41eiIvPgoJCQk8cG9seWdvbiBwb2ludHM9IjE0OS4yLDE3Ny4yIDE0OS4yLDIxNy43IDE4MC42LDIxNy43IDE4MC42LDIwOS41IDE1OS40LDIwOS41IDE1OS40LDE3Ny4yIAkJCSIvPgoJCTwvZz4KCTwvZz4KPC9nPgo8L3N2Zz4K"
alt="GOBL"
/>
</a>
</div>
</footer>
</article>
</body>
</html>
4 changes: 2 additions & 2 deletions pkg/pdf/pdf.go
Original file line number Diff line number Diff line change
@@ -112,8 +112,8 @@ func WithAttachment(a *Attachment) Option {
}
}

// WithXMPMetadata adds the XMP metadata to the conversion request.
func WithXMPMetadata() Option {
// WithZugferd adds the Zugferd XMP metadata to the conversion request.
func WithZugferd() Option {
return func(o *options) {
o.xmpMetadata = &XMPMetadata{
Data: loadXMP(),
1 change: 1 addition & 0 deletions pkg/pdf/prince.go
Original file line number Diff line number Diff line change
@@ -68,6 +68,7 @@ func (pc *princeConvertor) HTML(_ context.Context, data []byte, opts ...Option)
if j.PDF == nil {
j.PDF = new(princepdf.PDF)
}
j.PDF.PDFXMP = xmpFilename
j.PDF.Attach = append(j.PDF.Attach, &princepdf.Attachment{
URL: xmpFilename,
Filename: xmpFilename,