[ADD] report_carbone, jsonifier, export_json : carbone is an alternative to Py3o

This commit is contained in:
2026-04-21 14:59:24 +02:00
parent ae3c34257f
commit c2061984d1
216 changed files with 29344 additions and 0 deletions

View File

@@ -0,0 +1,297 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
a {
color: "#5e17eb";
}
body {
font-family: "Inter", sans-serif;
line-height: 1.7;
color: #001e2b;
background: #ffffff;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.header {
border-radius: 0;
margin-bottom: 40px;
position: relative;
}
.header-title-container {
display: flex;
align-items: center;
justify-content: center;
gap: 40px;
margin-bottom: 20px;
}
.header-icon {
width: 300px;
height: 300px;
display: flex;
align-items: center;
justify-content: center;
}
.header-icon img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.header h1 {
color: #5e17eb;
font-size: 3em;
font-weight: 700;
letter-spacing: -0.02em;
}
.header .subtitle {
font-size: 1.8em;
opacity: 0.85;
margin-bottom: 15px;
font-weight: 250;
color: #e6fffa;
}
.badge {
display: inline-block;
background: transparent;
border: 1px solid rgba(245, 243, 237, 0.3);
padding: 10px 24px;
border-radius: 2px;
margin: 10px 8px;
font-size: 0.85em;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.section {
margin-bottom: 80px;
padding: 0;
background: transparent;
border-radius: 0;
box-shadow: none;
}
.section h2 {
color: #001e2b;
font-size: 2.2em;
margin-bottom: 30px;
border-bottom: 1px solid #001e2b;
padding-bottom: 15px;
font-weight: 400;
letter-spacing: -0.01em;
}
.section h3 {
color: #001e2b;
font-size: 1.4em;
margin-top: 40px;
margin-bottom: 20px;
font-weight: 500;
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 30px;
margin-top: 40px;
}
.feature-card {
background: #ffffff;
padding: 35px;
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.08);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.feature-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}
.feature-card h4 {
color: #001e2b;
font-size: 1.15em;
margin-bottom: 12px;
display: flex;
align-items: center;
font-weight: 500;
}
.feature-card .icon {
font-size: 1.4em;
margin-right: 12px;
opacity: 0.8;
}
.feature-card p {
color: #5a5a5a;
line-height: 1.6;
font-size: 0.95em;
}
.screenshot-placeholder {
background: #ffffff;
border-radius: 0;
padding: 0;
text-align: center;
margin: 30px 0;
color: #8a8a8a;
font-size: 1em;
font-weight: 400;
letter-spacing: 0.02em;
overflow: hidden;
display: flex;
flex-direction: column;
}
.screenshot-placeholder img {
width: 100%;
height: auto;
object-fit: contain;
display: block;
}
.benefits-list {
list-style: none;
padding: 0;
}
.benefits-list li {
padding: 20px 0;
border-bottom: 1px solid #e8e6df;
display: flex;
align-items: start;
}
.benefits-list li:before {
content: "—";
color: #5e17eb;
font-weight: 400;
font-size: 1.3em;
margin-right: 20px;
margin-top: 2px;
}
.tech-specs {
background: #ffffff;
padding: 30px;
border-radius: 0;
margin-top: 30px;
border: 1px solid #e8e6df;
}
.tech-specs table {
width: 100%;
border-collapse: collapse;
}
.tech-specs td {
padding: 15px 0;
border-bottom: 1px solid #e8e6df;
font-size: 0.95em;
}
.tech-specs td:first-child {
font-weight: 500;
color: #001e2b;
width: 240px;
}
.tech-specs td:last-child {
color: #5a5a5a;
}
.cta-section {
background: #001e2b;
color: #f5f3ed;
padding: 70px 60px;
border-radius: 0;
text-align: center;
margin-top: 80px;
}
.cta-button {
display: inline-block;
background: transparent;
color: #f5f3ed;
border: 2px solid #f5f3ed;
padding: 16px 45px;
border-radius: 2px;
text-decoration: none;
font-weight: 400;
font-size: 1em;
margin-top: 25px;
transition: all 0.3s ease;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.cta-button:hover {
background: #f5f3ed;
color: #001e2b;
transform: none;
box-shadow: none;
}
.doc-ressource-list {
list-style: none;
padding: 0;
margin-left: 40px;
line-height: 2;
}
.doc-ressource-list li:before {
content: "—";
color: #5e17eb;
font-weight: 400;
font-size: 1.3em;
margin-right: 20px;
margin-top: 2px;
}
.highlight-box {
background: #5e17eb;
border-left: 3px solid #001e2b;
padding: 25px 30px;
margin: 30px 0;
border-radius: 0;
color: #001e2b;
}
.highlight-box strong {
color: #001e2b;
}
.green-horizontal {
width: 70px;
height: 1px;
background-color: #5e17eb;
position: relative;
flex-shrink: 0;
right: 90px;
}
.text-item .text-bottom {
font-weight: 700;
position: relative;
z-index: 1;
}
.text-item .text-item .text-bottom {
font-size: 44px;
}
.title-item h3 {
margin: 0;
}
.title-item {
display: flex;
align-items: center;
}
.footer {
text-align: center;
padding: 40px 20px;
color: #666;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 892 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg
width="100%"
height="100%"
viewBox="0 0 229 48"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xml:space="preserve"
xmlns:serif="http://www.serif.com/"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"
>
<g id="original">
<path
d="M35.015,0l-32.18,0c-1.566,0 -2.835,1.269 -2.835,2.835l-0,42.132c-0,1.566 1.269,2.835 2.835,2.835c7.732,0 29.127,0 36.859,0c0.752,0 1.473,-0.299 2.004,-0.83c0.532,-0.532 0.831,-1.253 0.831,-2.005c-0,-9.176 -0,-37.478 -0,-37.478l-7.514,-7.489Z"
style="fill:#a544c5;"
/>
<path
d="M60.53,37.702c-3.43,0 -6.114,-0.671 -8.052,-2.013c-1.939,-1.342 -2.908,-3.309 -2.908,-5.901c-0,-2.336 0.759,-4.126 2.276,-5.37c1.517,-1.244 3.675,-1.999 6.476,-2.266l7.362,-0.742l0,-1.172c0,-1.559 -0.507,-2.693 -1.523,-3.402c-1.015,-0.709 -2.409,-1.063 -4.181,-1.063c-1.41,-0 -2.772,0.192 -4.087,0.576c-1.315,0.385 -2.485,0.827 -3.51,1.328c-0.332,-0.268 -0.625,-0.614 -0.879,-1.038c-0.255,-0.425 -0.382,-0.863 -0.382,-1.316c-0,-1.136 0.621,-1.981 1.863,-2.535c0.974,-0.465 2.108,-0.818 3.402,-1.058c1.294,-0.24 2.623,-0.36 3.987,-0.36c3.291,-0 5.91,0.716 7.855,2.148c1.946,1.432 2.919,3.682 2.919,6.749l-0,12.368c-0,0.817 -0.18,1.453 -0.54,1.909c-0.36,0.456 -0.855,0.87 -1.485,1.244c-0.927,0.519 -2.127,0.967 -3.6,1.346c-1.472,0.379 -3.137,0.568 -4.993,0.568Zm0,-4.359c1.237,-0 2.306,-0.125 3.209,-0.376c0.902,-0.251 1.556,-0.508 1.96,-0.771l0,-6.72l-5.8,0.585c-1.6,0.122 -2.807,0.474 -3.621,1.056c-0.814,0.582 -1.221,1.43 -1.221,2.544c0,1.139 0.452,2.037 1.356,2.695c0.904,0.658 2.276,0.987 4.117,0.987Z"
style="fill:#a544c5;fill-rule:nonzero;"
/>
<path
d="M87.41,17.492l0,7.721l-5.575,0l-0,-7.878c-0,-0.872 0.184,-1.586 0.554,-2.142c0.37,-0.556 0.932,-1.079 1.688,-1.568c1.008,-0.64 2.317,-1.174 3.927,-1.6c1.609,-0.427 3.361,-0.641 5.255,-0.641c3.114,0 4.67,0.89 4.67,2.669c0,0.433 -0.063,0.834 -0.188,1.203c-0.126,0.37 -0.287,0.686 -0.483,0.947c-0.354,-0.07 -0.803,-0.136 -1.349,-0.197c-0.545,-0.061 -1.105,-0.091 -1.681,-0.091c-1.417,0 -2.71,0.151 -3.877,0.451c-1.168,0.301 -2.148,0.676 -2.941,1.126Zm-5.575,5.446l5.575,0.458l0,13.443c-0.236,0.081 -0.583,0.168 -1.04,0.261c-0.457,0.093 -0.957,0.14 -1.5,0.14c-1.019,0 -1.779,-0.192 -2.282,-0.577c-0.502,-0.384 -0.753,-1.046 -0.753,-1.984l-0,-11.741Z"
style="fill:#a544c5;fill-rule:nonzero;"
/>
<path
d="M118.188,11.37c2.23,-0 4.242,0.48 6.036,1.441c1.794,0.961 3.209,2.411 4.245,4.348c1.037,1.938 1.555,4.378 1.555,7.321c-0,2.958 -0.541,5.413 -1.623,7.365c-1.082,1.953 -2.586,3.411 -4.512,4.374c-1.927,0.963 -4.173,1.445 -6.74,1.445c-1.899,0 -3.579,-0.232 -5.039,-0.695c-1.46,-0.464 -2.666,-0.983 -3.619,-1.557c-0.742,-0.504 -1.271,-1.008 -1.589,-1.511c-0.317,-0.503 -0.475,-1.146 -0.475,-1.927l-0,-17.338l5.56,0l0,17.115c0.491,0.339 1.181,0.658 2.072,0.957c0.891,0.299 1.912,0.449 3.061,0.449c2.175,-0 3.924,-0.708 5.247,-2.124c1.323,-1.415 1.984,-3.6 1.984,-6.553c0,-2.999 -0.66,-5.185 -1.981,-6.558c-1.321,-1.373 -3.026,-2.06 -5.115,-2.06c-1.314,0 -2.479,0.252 -3.495,0.755c-1.017,0.502 -1.863,1.073 -2.54,1.71l-0.572,-4.513c0.787,-0.551 1.809,-1.097 3.067,-1.636c1.258,-0.539 2.749,-0.808 4.473,-0.808Zm-6.186,4.485l-5.575,0l-0,-13.328c0.235,-0.081 0.588,-0.168 1.058,-0.261c0.47,-0.093 0.977,-0.14 1.52,-0.14c1.029,-0 1.786,0.19 2.27,0.569c0.485,0.38 0.727,1.044 0.727,1.992l0,11.168Z"
style="fill:#a544c5;fill-rule:nonzero;"
/>
<path
d="M163.493,24.509c-0,2.674 -0.516,4.997 -1.549,6.969c-1.032,1.973 -2.488,3.497 -4.367,4.573c-1.88,1.075 -4.092,1.613 -6.635,1.613c-2.537,0 -4.752,-0.535 -6.646,-1.605c-1.894,-1.07 -3.358,-2.591 -4.392,-4.564c-1.034,-1.974 -1.551,-4.302 -1.551,-6.986c0,-2.694 0.526,-5.02 1.578,-6.978c1.052,-1.958 2.524,-3.474 4.418,-4.549c1.894,-1.075 4.094,-1.612 6.598,-1.612c2.506,-0 4.698,0.539 6.577,1.619c1.879,1.079 3.344,2.599 4.394,4.56c1.05,1.96 1.575,4.281 1.575,6.96Zm-12.554,-8.647c-2.108,0 -3.78,0.765 -5.018,2.295c-1.237,1.53 -1.856,3.647 -1.856,6.352c-0,2.751 0.604,4.882 1.813,6.394c1.21,1.512 2.898,2.269 5.064,2.269c2.146,-0 3.822,-0.765 5.029,-2.295c1.207,-1.53 1.81,-3.652 1.81,-6.368c0,-2.695 -0.606,-4.81 -1.817,-6.345c-1.211,-1.535 -2.886,-2.302 -5.025,-2.302Z"
style="fill:#a544c5;fill-rule:nonzero;"
/>
<path
d="M195.564,20.647l0,5.257l-5.575,-0l-0,-5.03c-0,-1.731 -0.493,-3 -1.477,-3.804c-0.984,-0.805 -2.299,-1.208 -3.944,-1.208c-1.216,0 -2.305,0.152 -3.267,0.456c-0.961,0.304 -1.801,0.661 -2.519,1.07l0,8.516l-5.575,-0l-0,-8.704c-0,-0.802 0.165,-1.458 0.494,-1.97c0.33,-0.512 0.861,-1.002 1.593,-1.471c0.998,-0.619 2.302,-1.172 3.91,-1.659c1.608,-0.487 3.406,-0.73 5.393,-0.73c3.433,-0 6.119,0.772 8.059,2.318c1.939,1.545 2.908,3.865 2.908,6.959Zm-22.357,2.52l5.575,-0l0,13.672c-0.236,0.081 -0.583,0.168 -1.04,0.261c-0.457,0.093 -0.957,0.14 -1.5,0.14c-1.019,0 -1.779,-0.192 -2.282,-0.577c-0.502,-0.384 -0.753,-1.046 -0.753,-1.984l-0,-11.512Zm16.782,-0l5.575,-0l0,13.672c-0.236,0.081 -0.589,0.168 -1.059,0.261c-0.47,0.093 -0.963,0.14 -1.481,0.14c-1.044,0 -1.811,-0.192 -2.301,-0.577c-0.49,-0.384 -0.734,-1.046 -0.734,-1.984l-0,-11.512Z"
style="fill:#a544c5;fill-rule:nonzero;"
/>
<path
d="M208.92,27.583l-0.242,-4.066l14.952,-2.128c-0.121,-1.597 -0.703,-2.946 -1.746,-4.047c-1.043,-1.102 -2.516,-1.652 -4.418,-1.652c-1.973,-0 -3.606,0.695 -4.899,2.084c-1.292,1.389 -1.948,3.377 -1.968,5.964l0.089,2.086c0.277,2.49 1.17,4.352 2.679,5.586c1.508,1.234 3.485,1.851 5.931,1.851c1.484,0 2.846,-0.233 4.087,-0.698c1.241,-0.465 2.229,-0.965 2.966,-1.501c0.438,0.277 0.794,0.626 1.068,1.045c0.274,0.42 0.412,0.882 0.412,1.385c-0,0.81 -0.395,1.531 -1.185,2.164c-0.789,0.632 -1.848,1.125 -3.176,1.478c-1.329,0.353 -2.83,0.53 -4.504,0.53c-2.73,0 -5.123,-0.502 -7.179,-1.506c-2.056,-1.004 -3.652,-2.504 -4.789,-4.5c-1.137,-1.995 -1.706,-4.451 -1.706,-7.367c0,-2.103 0.317,-3.958 0.949,-5.565c0.633,-1.607 1.503,-2.954 2.609,-4.039c1.107,-1.085 2.402,-1.91 3.885,-2.473c1.483,-0.563 3.068,-0.844 4.754,-0.844c2.254,-0 4.235,0.466 5.942,1.4c1.707,0.933 3.047,2.227 4.018,3.88c0.971,1.653 1.456,3.549 1.456,5.688c0,0.897 -0.221,1.555 -0.663,1.974c-0.443,0.42 -1.061,0.68 -1.856,0.78l-17.466,2.491Z"
style="fill:#a544c5;fill-rule:nonzero;"
/>
<path
d="M23.852,15.915c-2.224,0 -4.089,0.743 -5.596,2.23c-1.506,1.487 -2.259,3.631 -2.259,6.432c0,2.776 0.727,4.898 2.18,6.364c1.453,1.467 3.34,2.201 5.66,2.201c1.366,-0 2.521,-0.184 3.466,-0.55c0.944,-0.367 1.776,-0.78 2.493,-1.241c0.469,0.29 0.839,0.63 1.111,1.017c0.271,0.387 0.407,0.86 0.407,1.418c0,1.109 -0.723,2.03 -2.169,2.763c-1.447,0.734 -3.32,1.1 -5.619,1.1c-2.577,0 -4.862,-0.48 -6.857,-1.442c-1.995,-0.961 -3.553,-2.413 -4.673,-4.355c-1.12,-1.943 -1.68,-4.367 -1.68,-7.275c-0,-2.931 0.59,-5.375 1.77,-7.33c1.18,-1.955 2.766,-3.423 4.756,-4.405c1.99,-0.982 4.167,-1.472 6.533,-1.472c2.28,-0 4.13,0.395 5.551,1.187c1.422,0.791 2.132,1.733 2.132,2.826c0,0.493 -0.133,0.936 -0.4,1.328c-0.268,0.392 -0.6,0.711 -0.999,0.957c-0.728,-0.461 -1.547,-0.868 -2.459,-1.222c-0.912,-0.354 -2.028,-0.531 -3.348,-0.531Z"
style="fill:#fff;fill-rule:nonzero;"
/>
<path
d="M35.015,6.638c-0,0.226 0.089,0.442 0.249,0.602c0.159,0.159 0.375,0.249 0.601,0.249c1.967,-0 6.664,-0 6.664,-0l-7.514,-7.489l-0,6.638Z"
style="fill:#e8a4ff;fill-rule:nonzero;"
/>
</g>
<script xmlns=""/>
</svg>

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -0,0 +1,3 @@
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<circle cx="12" cy="12" r="11" stroke="#5e17eb" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 193 B

View File

@@ -0,0 +1,3 @@
<svg width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<circle cx="9.5" cy="9.5" r="9.5" fill="#5e17eb"/>
</svg>

After

Width:  |  Height:  |  Size: 177 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,484 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Carbone Document Generator</title>
<link rel="stylesheet" href="/report_carbone/static/description/assets/description.css">
</head>
<body>
<div class="container" style="font-family: 'Inter', sans-serif; line-height: 1.7; color: #001E2B;background-color: #ffffff; max-width: 1200px; margin: 0 auto; padding: 20px;">
<!-- Header -->
<div class="header">
<img src="assets/logo/Carbone BentoGrid.png" style="max-width: 100%;border-radius: 20px;"/>
</div>
<h1 style="font-size: 2.5em;font-weight: 700;letter-spacing: -0.02em;margin-bottom:20px">Carbone Document Generator</h1>
<!-- Description -->
<div class="section" style="margin-bottom: 40px; padding: 0; background: transparent; border-radius: 0; box-shadow: none;">
<div class="text-item" style="margin-bottom: 40px; padding: 0; background: transparent; border-radius: 0; box-shadow: none; position: relative;">
<h2 class="text-bottom" style="color: #001E2B; font-size: 2.2em; margin-bottom: 30px; border-bottom: 1px solid #001E2B; padding-bottom: 15px; font-weight: 700; letter-spacing: -0.01em; position: relative; z-index: 1;">Description</h2>
<!-- <img src="./assets/logo/icon-description.png" style="position: absolute;height: 100%;top: 0;right: -5px;"/>-->
</div>
<p style="font-size: 1.1em; line-height: 1.8;">
<strong>Carbone Document Generator</strong> is the ultimate solution for automating the creation of professional documents from your Odoo data and a Template file.
Whether you need to generate invoices, contracts, reports, or any other type of document, Carbone makes it easy and efficient, without any coding.
To get started: simply select your Odoo data, design a template in your preferred text editor (LibreOffice, Word, Excel, PowerPoint, or Google Docs), add Carbone tags (placeholders), and youre ready to automate your documents!
</p>
<div style="text-align: center;background-color: #5e17eb; padding: 25px 30px; margin: 30px 0; border-radius: 0; color: white;border-radius: 10px;">
This integration is the official <b><a style="color:white" href="https://carbone.io" target="_blank">Carbone.io</a></b> connector for Odoo, developed and maintained by <b><a style="color:white" href="https://mangono.fr/">Mangono</a></b>, an Official Carbone Partner ⭐️
</div>
</div>
<!-- Use cases -->
<div class="section" style="margin-bottom: 40px; padding: 0; background: transparent; border-radius: 0; box-shadow: none;">
<div class="text-item" style="padding: 0; background: transparent; border-radius: 0; box-shadow: none; position: relative;">
<h2 class="text-bottom" style="color: #001E2B; font-size: 2.2em; margin-bottom: 30px; border-bottom: 1px solid #001E2B; padding-bottom: 15px; font-weight: 700; letter-spacing: -0.01em; position: relative; z-index: 1;">Use Cases</h2>
<!-- <img src="./assets/logo/icon-use-case.png" style="position: absolute;height: 100%;top: 0;right: -5px;"/>-->
</div>
<ul style=" list-style-type: none">
<li><b>Sales:</b> Generate quotes, contracts, and purchase orders.</li>
<li><b>Logistics:</b> Create delivery notes, packing slips, and shipping labels.</li>
<li><b>Human Resources:</b> Automate the creation of employment contracts, payslips, and onboarding documents.</li>
<li><b>Finance:</b> Produce invoices, financial statements, and expense reports.</li>
<li><b>Marketing:</b> Render personalized proposals, campaign reports, and promotional materials.</li>
<li><b>Custom Models:</b> Build documents from any custom Odoo model to fit your specific business needs.</li>
</ul>
</div>
<!-- Fonctionnalités -->
<div class="section" style="margin-bottom: 20px; padding: 0; background: transparent; border-radius: 0; box-shadow: none;">
<div class="text-item" style="padding: 0; background: transparent; border-radius: 0; box-shadow: none; position: relative;">
<h2 class="text-bottom" style="color: #001E2B; font-size: 2.2em; margin-bottom: 30px; border-bottom: 1px solid #001E2B; padding-bottom: 15px; font-weight: 700; letter-spacing: -0.01em; position: relative; z-index: 1;">Key Features</h2>
<!-- <img src="./assets/logo/icon-key-features.png" style="position: absolute;height: 100%;top: 0;right: -5px;"/>-->
</div>
<div class="features-grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 30px; margin-top: 40px;">
<div class="feature-card" style="border:1px solid #ebebeb;background-color: #ffffff; padding: 35px; border-radius: 16px; transition: transform 0.2s ease, box-shadow 0.2s ease;">
<h4 style="color: #001E2B; font-size: 1.15em; margin-bottom: 12px; display: flex; align-items: center; font-weight: 500;"><span class="icon" style="font-size: 1.4em; margin-right: 12px; opacity: 0.8;"><img src="assets/svg/green-dot-empty.svg" alt="green-dot-empty"/></span> Generate and Convert Documents Instantly</h4>
<p style="color: #5a5a5a; line-height: 1.6; font-size: 0.95em;">Generate and convert documents from a customized Template and your Odoo data, including Leads, Contacts, Orders, and custom models.</p>
</div>
<div class="feature-card" style="border:1px solid #ebebeb;background-color: #ffffff; padding: 35px; border-radius: 16px; transition: transform 0.2s ease, box-shadow 0.2s ease;">
<h4 style="color: #001E2B; font-size: 1.15em; margin-bottom: 12px; display: flex; align-items: center; font-weight: 500;"><span class="icon" style="font-size: 1.4em; margin-right: 12px; opacity: 0.8;"><img src="assets/svg/green-dot-empty.svg" alt="green-dot-empty"/></span>Flexible Template Format Support</h4>
<p style="color: #5a5a5a; line-height: 1.6; font-size: 0.95em;">Carbone supports an extensive range of input formats: PDF, DOCX, XLSX, PPTX, ODG, IDML, ODT, ODS, ODP, XML, HTML, and Markdown.</p>
</div>
<div class="feature-card" style="border:1px solid #ebebeb;background-color: #ffffff; padding: 35px; border-radius: 16px; transition: transform 0.2s ease, box-shadow 0.2s ease;">
<h4 style="color: #001E2B; font-size: 1.15em; margin-bottom: 12px; display: flex; align-items: center; font-weight: 500;"><span class="icon" style="font-size: 1.4em; margin-right: 12px; opacity: 0.8;"><img src="assets/svg/green-dot-empty.svg" alt="green-dot-empty"/></span> Export to Any Format You Need</h4>
<p style="color: #5a5a5a; line-height: 1.6; font-size: 0.95em;">Export your documents to 100+ formats, including PDF, ODT, DOCX, JPG, PNG, EPUB, ODS, XLSX, CSV, ODP, PPTX, XML, HTML, TXT, IDML, and many more.</p>
</div>
<div class="feature-card" style="border:1px solid #ebebeb;background-color: #ffffff; padding: 35px; border-radius: 16px; transition: transform 0.2s ease, box-shadow 0.2s ease;">
<h4 style="color: #001E2B; font-size: 1.15em; margin-bottom: 12px; display: flex; align-items: center; font-weight: 500;"><span class="icon" style="font-size: 1.4em; margin-right: 12px; opacity: 0.8;"><img src="assets/svg/green-dot-empty.svg" alt="green-dot-empty"/></span> Design Templates in Your Preferred Editor</h4>
<p style="color: #5a5a5a; line-height: 1.6; font-size: 0.95em;">Create your templates using the tools you already know and love, such as LibreOffice, Word, Excel, PowerPoint, Google Docs, Pages, and OnlyOffice. No need to learn new software or coding.</p>
</div>
<div class="feature-card" style="border:1px solid #ebebeb;background-color: #ffffff; padding: 35px; border-radius: 16px; transition: transform 0.2s ease, box-shadow 0.2s ease;">
<h4 style="color: #001E2B; font-size: 1.15em; margin-bottom: 12px; display: flex; align-items: center; font-weight: 500;"><span class="icon" style="font-size: 1.4em; margin-right: 12px; opacity: 0.8;"><img src="assets/svg/green-dot-empty.svg" alt="green-dot-empty"/></span> Enterprise-Grade Features</h4>
<p style="color: #5a5a5a; line-height: 1.6; font-size: 0.95em;">Enrich your documents with dynamic charts, tables, barcodes, images, colors, SVGs, hyperlinks, HTML from WYSIWYG editors, conditional rendering, and more.</p>
</div>
<div class="feature-card" style="border:1px solid #ebebeb;background-color: #ffffff; padding: 35px; border-radius: 16px; transition: transform 0.2s ease, box-shadow 0.2s ease;">
<h4 style="color: #001E2B; font-size: 1.15em; margin-bottom: 12px; display: flex; align-items: center; font-weight: 500;"><span class="icon" style="font-size: 1.4em; margin-right: 12px; opacity: 0.8;">
<img src="assets/svg/green-dot-empty.svg" alt="green-dot-empty"/>
</span> Multi-Language Support</h4>
<p style="color: #5a5a5a; line-height: 1.6; font-size: 0.95em;">Generate international reports with full support for multiple languages, numbers, dates, times, and currency formats.</p>
</div>
<div class="feature-card" style="border:1px solid #ebebeb;background-color: #ffffff; padding: 35px; border-radius: 16px; transition: transform 0.2s ease, box-shadow 0.2s ease;">
<h4 style="color: #001E2B; font-size: 1.15em; margin-bottom: 12px; display: flex; align-items: center; font-weight: 500;"><span class="icon" style="font-size: 1.4em; margin-right: 12px; opacity: 0.8;"><img src="assets/svg/green-dot-empty.svg" alt="green-dot-empty"/></span> E-Signature Support</h4>
<p style="color: #5a5a5a; line-height: 1.6; font-size: 0.95em;">Enhance your documents with electronic signatures for secure and legally binding agreements.</p>
</div>
<div class="feature-card" style="border:1px solid #ebebeb;background-color: #ffffff; padding: 35px; border-radius: 16px; transition: transform 0.2s ease, box-shadow 0.2s ease;">
<h4 style="color: #001E2B; font-size: 1.15em; margin-bottom: 12px; display: flex; align-items: center; font-weight: 500;"><span class="icon" style="font-size: 1.4em; margin-right: 12px; opacity: 0.8;"><img src="assets/svg/green-dot-empty.svg" alt="green-dot-empty"/></span> Merge PDFs </h4>
<p style="color: #5a5a5a; line-height: 1.6; font-size: 0.95em;">Combine multiple documents into a single PDF document for streamlined document management.</p>
</div>
<div class="feature-card" style="border:1px solid #ebebeb;background-color: #ffffff; padding: 35px; border-radius: 16px; transition: transform 0.2s ease, box-shadow 0.2s ease;">
<h4 style="color: #001E2B; font-size: 1.15em; margin-bottom: 12px; display: flex; align-items: center; font-weight: 500;"><span class="icon" style="font-size: 1.4em; margin-right: 12px; opacity: 0.8;"><img src="assets/svg/green-dot-empty.svg" alt="green-dot-empty"/></span> HTML to PDF Conversion </h4>
<p style="color: #5a5a5a; line-height: 1.6; font-size: 0.95em;">If you're already using Gothenberg or Puppeteer, migrate to Carbone in seconds, and enjoy the powerful Carbone conversion, the fastest in the industry.</p>
</div>
</div>
</div>
<!-- Screenshots Section -->
<div class="section" style="margin-bottom: 40px; padding: 0; background: transparent; border-radius: 0; box-shadow: none;">
<div class="text-item" style="margin-bottom: 40px; padding: 0; background: transparent; border-radius: 0; box-shadow: none; position: relative;">
<h2 class="text-bottom" style="color: #001E2B; font-size: 2.2em; margin-bottom: 30px; border-bottom: 1px solid #001E2B; padding-bottom: 15px; font-weight: 700; letter-spacing: -0.01em; position: relative; z-index: 1;">Screenshots</h2>
<!-- <img src="./assets/logo/icon-screenshots.png" style="position: absolute;height: 100%;top: 0;right: -5px;"/>-->
</div>
<div class="title-item" style="display: flex; align-items: center;flex-direction: column;padding-bottom: 10px;">
<div style="width: 70px;height: 1px; background-color: #5e17eb; position: relative; flex-shrink: 0;right: 90px;"></div>
<h3 style="color: #001E2B; font-size: 1.4em; font-weight: 500; margin:0;">Odoo Module Configuration</h3>
</div>
<p> After installing the module, youll need to add your Carbone API key (found in your <a style="color : #5e17eb" href="https://account.carbone.io/">Carbone account</a>). No subscription yet? No problem—use the free test API key to try it out first!</p>
<div class="screenshot-placeholder" style="background-color: #ffffff; border-radius: 0; padding: 0; text-align: center; margin: 30px 0; color: #8a8a8a; font-size: 1em; font-weight: 400; letter-spacing: 0.02em; overflow: hidden; display: flex; flex-direction: column;">
<img style="width: 100%; height: auto; object-fit: contain;display: block;" src="assets/screenshots/add_api_keys.png"/>
</div>
<div class="title-item" style="display: flex; align-items: left;flex-direction: column;">
<div class="green-horizontal" style="width: 70px;height: 1px; background-color: #5e17eb; position: relative; flex-shrink: 0;right: 90px;"></div>
<h3 style="color: #001E2B; font-size: 1.4em; font-weight: 500; margin:0;">Creating Carbone Templates</h3>
</div>
<ul style=" list-style-type: none">
<li><b>Step 1:</b> Open <a style="color : #5e17eb" href="https://studio.carbone.io/">Carbone Studio</a> (the web interface for managing templates).</li>
<ul>
<div class="screenshot-placeholder" style="background-color: #ffffff; border-radius: 0; padding: 0; text-align: center; margin: 30px 0; color: #8a8a8a; font-size: 1em; font-weight: 400; letter-spacing: 0.02em; overflow: hidden; display: flex; flex-direction: column;">
<img style="width: 100%; height: auto; object-fit: contain;display: block;" src="assets/screenshots/open_carbone_studio.png"/>
</div>
<ul style=" list-style-type: none">
<li><b>Step 2:</b> On the home page, click "Start from scratch" to begin designing your template.</li>
<ul>
<div class="screenshot-placeholder" style="background-color: #ffffff; border-radius: 0; padding: 0; text-align: center; margin: 30px 0; color: #8a8a8a; font-size: 1em; font-weight: 400; letter-spacing: 0.02em; overflow: hidden; display: flex; flex-direction: column;">
<img style="width: 100%; height: auto; object-fit: contain;display: block;" src="assets/screenshots/landing_page_carbone.png"/>
</div>
<ul style=" list-style-type: none">
<li><b>Step 3:</b> Create your template using a text editor, and upload on Carbone Studio. The editor has three key sections to help you design your template:</li>
<ul style=" list-style-type: none">
<li>📄 Template Panel (on the top right panel): Where you upload and version your Template (e.g., invoices, contracts).</li>
<li>📊 Data Model Editor (left panel): Here, you define the JSON data that fills your template (e.g., {d.firstName}).</li>
<li>🔍 Document Preview (right panel): See a real-time PDF preview of your final document as you edit.</li>
</ul>
<ul><br>
<div class="screenshot-placeholder" style="background-color: #ffffff; border-radius: 0; padding: 0; text-align: center; margin: 30px 0; color: #8a8a8a; font-size: 1em; font-weight: 400; letter-spacing: 0.02em; overflow: hidden; display: flex; flex-direction: column;">
<img style="width: 100%; height: auto; object-fit: contain;display: block;" src="assets/screenshots/create_new_template_carbone.png"/>
</div>
<b>Step 4:</b> Once your design is ready, save your changes, and get a Template ID.
<ul style=" list-style-type: none">
<li>1. Click "New document" to upload your template.</li>
<li>2. Disable "Auto-reload" (to finalize changes).</li>
<li>3. Click "Save": this generates a unique Template Version ID (youll need this for Odoo).</li>
</ul>
<div class="screenshot-placeholder" style="background-color: #ffffff; border-radius: 0; padding: 0; text-align: center; margin: 30px 0; color: #8a8a8a; font-size: 1em; font-weight: 400; letter-spacing: 0.02em; overflow: hidden; display: flex; flex-direction: column;">
<img style="width: 100%; height: auto; object-fit: contain;display: block;" src="assets/screenshots/copie_template_carbone_id.png"/>
</div>
<div class="title-item" style="display: flex; align-items: center;padding-bottom: 10px;">
<div class="green-horizontal" style="width: 70px;height: 1px; background-color: #5e17eb; position: relative; flex-shrink: 0;right: 90px;"></div>
<h3 style="color: #001E2B; font-size: 1.4em; font-weight: 500; margin:0;">Setting Up Document Generation in Odoo</h3>
</div>
<ul style=" list-style-type: none">
<li>In Odoo, go to Document Generation (main menu).</li>
<li>Click "New" to create a document workflow.</li>
</ul>
<div class="screenshot-placeholder" style="background-color: #ffffff; border-radius: 0; padding: 0; text-align: center; margin: 30px 0; color: #8a8a8a; font-size: 1em; font-weight: 400; letter-spacing: 0.02em; overflow: hidden; display: flex; flex-direction: column;">
<img style="width: 100%; height: auto; object-fit: contain;display: block;" src="assets/screenshots/open_carbone_menu.png"/>
</div>
<div class="screenshot-placeholder" style="background-color: #ffffff; border-radius: 0; padding: 0; text-align: center; margin: 30px 0; color: #8a8a8a; font-size: 1em; font-weight: 400; letter-spacing: 0.02em; overflow: hidden; display: flex; flex-direction: column;">
<img style="width: 100%; height: auto; object-fit: contain;display: block;" src="assets/screenshots/new_button.png"/>
</div>
<div class="title-item" style="display: flex; align-items: center;padding-bottom: 10px;">
<div class="green-horizontal" style="width: 70px;height: 1px; background-color: #5e17eb; position: relative; flex-shrink: 0;right: 90px;"></div>
<h3 style="color: #001E2B; font-size: 1.4em; font-weight: 500; margin:0;">Copy Your Template Version ID from Carbone into Odoo</h3>
</div>
<ul style=" list-style-type: none">
<li>1. Paste your Template Version ID from Carbone Studio (1).</li>
<li>2. Select the Odoo data model (e.g., Invoices, Contacts).</li>
<li>3. Customize translation and language settings (if needed).</li>
<li>4. Click "Add to Print Menu" (2), now youre ready to generate documents!</li>
</ul>
<div class="screenshot-placeholder" style="background-color: #ffffff; border-radius: 0; padding: 0; text-align: center; margin: 30px 0; color: #8a8a8a; font-size: 1em; font-weight: 400; letter-spacing: 0.02em; overflow: hidden; display: flex; flex-direction: column;">
<img style="width: 100%; height: auto; object-fit: contain;display: block;" src="assets/screenshots/copie_carbone_template_id_add_menu.png"/>
</div>
<div class="title-item" style="display: flex; align-items: center;padding-bottom: 10px;">
<div class="green-horizontal" style="width: 70px;height: 1px; background-color: #5e17eb; position: relative; flex-shrink: 0;right: 90px;"></div>
<h3 style="color: #001E2B; font-size: 1.4em; font-weight: 500; margin:0;">Real-time Configuration of your Document</h3>
</div>
With Carbones Odoo integration, you can:
<ul style="margin-left: 18px;">
<li>Download your template and tweak it anytime using in your favorite text editor.</li>
<li>See changes instantly with a live preview of the final document.</li>
</ul>
<div class="screenshot-placeholder" style="background-color: #ffffff; border-radius: 0; padding: 0; text-align: center; margin: 30px 0; color: #8a8a8a; font-size: 1em; font-weight: 400; letter-spacing: 0.02em; overflow: hidden; display: flex; flex-direction: column;">
<img style="width: 100%; height: auto; object-fit: contain;display: block;" src="assets/gifs/live-reload-change-record.gif"/>
</div>
<div class="title-item" style="display: flex; align-items: center;">
<div class="green-horizontal" style="width: 70px;height: 1px; background-color: #5e17eb; position: relative; flex-shrink: 0;right: 90px;"></div>
<h3 style="color: #001E2B; font-size: 1.4em; font-weight: 500; margin:0;">Document Generation</h3>
</div>
<div class="screenshot-placeholder" style="background-color: #ffffff; border-radius: 0; padding: 0; text-align: center; margin: 30px 0; color: #8a8a8a; font-size: 1em; font-weight: 400; letter-spacing: 0.02em; overflow: hidden; display: flex; flex-direction: column;">
<img style="width: 100%; height: auto; object-fit: contain;display: block;" src="assets/gifs/generate-report-from-report.gif"/>
</div>
<div class="screenshot-placeholder" style="background-color: #ffffff; border-radius: 0; padding: 0; text-align: center; margin: 30px 0; color: #8a8a8a; font-size: 1em; font-weight: 400; letter-spacing: 0.02em; overflow: hidden; display: flex; flex-direction: column;">
<img style="width: 100%; height: auto; object-fit: contain;display: block;" src="assets/gifs/generate-report-from-po.gif"/>
</div>
<div class="title-item" style="display: flex; align-items: center;">
<div class="green-horizontal" style="width: 70px;height: 1px; background-color: #5e17eb; position: relative; flex-shrink: 0;right: 90px;"></div>
<h3 style="color: #001E2B; font-size: 1.4em; font-weight: 500; margin:0;">Track and Version Your Documents</h3>
</div>
<div class="screenshot-placeholder" style="background-color: #ffffff; border-radius: 0; padding: 0; text-align: center; margin: 30px 0; color: #8a8a8a; font-size: 1em; font-weight: 400; letter-spacing: 0.02em; overflow: hidden; display: flex; flex-direction: column;">
<img style="width: 100%; height: auto; object-fit: contain;display: block;" src="assets/screenshots/copie_unique_template_id.png"/>
</div>
<div class="screenshot-placeholder" style="background-color: #ffffff; border-radius: 0; padding: 0; text-align: center; margin: 30px 0; color: #8a8a8a; font-size: 1em; font-weight: 400; letter-spacing: 0.02em; overflow: hidden; display: flex; flex-direction: column;">
<img style="width: 100%; height: auto; object-fit: contain;display: block;" src="assets/screenshots/unique_template_id_on_odoo.png"/>
</div>
<div class="screenshot-placeholder" style="background-color: #ffffff; border-radius: 0; padding: 0; text-align: center; margin: 30px 0; color: #8a8a8a; font-size: 1em; font-weight: 400; letter-spacing: 0.02em; overflow: hidden; display: flex; flex-direction: column;">
<img style="width: 100%; height: auto; object-fit: contain;display: block;" src="assets/gifs/different-report-with-one-template-id.gif"/>
</div>
<div class="title-item" style="display: flex; align-items: center;">
<div class="green-horizontal" style="width: 70px;height: 1px; background-color: #5e17eb; position: relative; flex-shrink: 0;right: 90px;"></div>
<h3 style="color: #001E2B; font-size: 1.4em; font-weight: 500; margin:0;">Choose Your Own Data Models for Reports</h3>
</div>
<div class="screenshot-placeholder" style="background-color: #ffffff; border-radius: 0; padding: 0; text-align: center; margin: 30px 0; color: #8a8a8a; font-size: 1em; font-weight: 400; letter-spacing: 0.02em; overflow: hidden; display: flex; flex-direction: column;">
<img style="width: 100%; height: auto; object-fit: contain;display: block;" src="assets/screenshots/select_one_record.png"/>
</div>
<div class="screenshot-placeholder" style="background-color: #ffffff; border-radius: 0; padding: 0; text-align: center; margin: 30px 0; color: #8a8a8a; font-size: 1em; font-weight: 400; letter-spacing: 0.02em; overflow: hidden; display: flex; flex-direction: column;">
<img style="width: 100%; height: auto; object-fit: contain;display: block;" src="assets/screenshots/pop-up-export.png"/>
</div>
<div class="screenshot-placeholder" style="background-color: #ffffff; border-radius: 0; padding: 0; text-align: center; margin: 30px 0; color: #8a8a8a; font-size: 1em; font-weight: 400; letter-spacing: 0.02em; overflow: hidden; display: flex; flex-direction: column;">
<img src="assets/screenshots/pop-up-export-2.png"/>
</div>
<div class="screenshot-placeholder" style="background-color: #ffffff; border-radius: 0; padding: 0; text-align: center; margin: 30px 0; color: #8a8a8a; font-size: 1em; font-weight: 400; letter-spacing: 0.02em; overflow: hidden; display: flex; flex-direction: column;">
<img style="width: 100%; height: auto; object-fit: contain;display: block;" src="assets/screenshots/find-your-export.png"/>
</div>
<div class="title-item" style="display: flex; align-items: center;">
<div class="green-horizontal" style="width: 70px;height: 1px; background-color: #5e17eb; position: relative; flex-shrink: 0;right: 90px;"></div>
<h3 style="color: #001E2B; font-size: 1.4em; font-weight: 500; margin:0; ">Add and Manage Translations with Ease</h3>
</div>
<div class="screenshot-placeholder" style="background-color: #ffffff; border-radius: 0; padding: 0; text-align: center; margin: 30px 0; color: #8a8a8a; font-size: 1em; font-weight: 400; letter-spacing: 0.02em; overflow: hidden; display: flex; flex-direction: column;">
<img style="width: 100%; height: auto; object-fit: contain;display: block;" src="assets/screenshots/add_translate.png"/>
</div>
<div class="screenshot-placeholder" style="background-color: #ffffff; border-radius: 0; padding: 0; text-align: center; margin: 30px 0; color: #8a8a8a; font-size: 1em; font-weight: 400; letter-spacing: 0.02em; overflow: hidden; display: flex; flex-direction: column;">
<img style="width: 100%; height: auto; object-fit: contain;display: block;" src="assets/screenshots/add_source_and_translate.png"/>
</div>
<div class="title-item" style="display: flex; align-items: center;">
<div class="green-horizontal" style="width: 70px;height: 1px; background-color: #5e17eb; position: relative; flex-shrink: 0;right: 90px;"></div>
<h3 style="color: #001E2B; font-size: 1.4em; font-weight: 500; margin:0;">Translation for Multi-Language Reports</h3>
</div>
<div class="screenshot-placeholder" style="background-color: #ffffff; border-radius: 0; padding: 0; text-align: center; margin: 30px 0; color: #8a8a8a; font-size: 1em; font-weight: 400; letter-spacing: 0.02em; overflow: hidden; display: flex; flex-direction: column;">
<img style="width: 100%; height: auto; object-fit: contain;display: block;" src="assets/gifs/report-translated.gif"/>
</div>
<div class="title-item" style="display: flex; align-items: center;">
<div class="green-horizontal" style="width: 70px;height: 1px; background-color: #5e17eb; position: relative; flex-shrink: 0;right: 90px;"></div>
<h3 style="color: #001E2B; font-size: 1.4em; font-weight: 500; margin: 0;">Manage Access Rights Through Group-Based Permissions</h3>
</div>
<div class="screenshot-placeholder" style="background-color: #ffffff; border-radius: 0; padding: 0; text-align: center; margin: 30px 0; color: #8a8a8a; font-size: 1em; font-weight: 400; letter-spacing: 0.02em; overflow: hidden; display: flex; flex-direction: column;">
<img style="width: 100%; height: auto; object-fit: contain;display: block;" src="assets/screenshots/carbone_groups.png"/>
</div>
</div>
<!-- Avantages -->
<div class="section" style="margin-bottom: 40px; padding: 0; background: transparent; border-radius: 0; box-shadow: none;">
<div class="text-item" style="margin-bottom: 40px; padding: 0; background: transparent; border-radius: 0; box-shadow: none; position: relative;">
<h2 class="text-bottom" style="color: #001E2B; font-size: 2.2em; margin-bottom: 30px; border-bottom: 1px solid #001E2B; padding-bottom: 15px; font-weight: 700; letter-spacing: -0.01em; position: relative; z-index: 1;">Why Youll Love Carbone</h2>
<!-- <img src="./assets/logo/icon-why-carbone2.png" style="position: absolute;height: 100%;top: 0;right: -5px;"/>-->
</div>
<ul class="benefits-list" style="list-style: '— '; padding: 0;" >
<li style="padding: 20px 0; border-bottom: 1px solid #e8e6df; display: flex; align-items: start;">
<div>
<strong>Premium Support</strong><br>
Get priority assistance through our official support ticketing system, backed by the Carbone engineering team.<br>
Were committed to providing the best premium support experience—youll be heard, championed, and empowered to succeed.
</div>
</li>
<li style="padding: 20px 0; border-bottom: 1px solid #e8e6df; display: flex; align-items: start;">
<div>
<strong>Future-proof</strong><br>
Every template you create is guaranteed to work with all future versions of the Carbone Engine. <br>
Youll never have to update or recreate your templates due to software changes.<br>
New features are always optional and backward-compatible, so you can adopt them on your terms.<br>
With Carbone, you can focus on your business, not maintenance.
</div>
</li>
<li style="padding: 20px 0; border-bottom: 1px solid #e8e6df; display: flex; align-items: start;">
<div>
<strong>Built for Speed and Scalability</strong><br>
Carbone delivers high-performance document generation, optimized at every level for speed, and efficiency.<br>
Whether youre generating a few documents or thousands, Carbone scales to meet your demands without compromising quality or performance.
Its powerful rendering engine is designed to handle high-throughput processing, ensuring rapid processing and reliability, even with complex templates or high-volume batches.<br>
</div>
</li>
<li style="padding: 20px 0; border-bottom: 1px solid #e8e6df; display: flex; align-items: start;">
<div>
<strong>Empowering you with Your Own Tools</strong><br>
Not only can you create templates using your preferred text editor (LibreOffice, Word, Google Doc, and more),<br>
but you can also sign your generated documents using leading e-signature providers like:<br>
Docusign, Yousign, Documenso, DocuSeal, SignWell and more.
</div>
</li>
<li style="padding: 20px 0; border-bottom: 1px solid #e8e6df; display: flex; align-items: start;">
<div>
<strong>Security & Privacy</strong><br>
At Carbone, we carefully design products with the highest level of security to protect your privacy and give you control over your information.<br>
Generated documents are processed in real time and returned directly to your Odoo application, and never stored on Carbone Servers.<br>
Odoo data samples are not saved or logged on our servers. We prioritize your privacy and do not store any sensitive information.<br>
Template files are stored securely on our servers, but they do not contain sensitive information unless explicitly added by you.
</div>
</li>
<li style="padding: 20px 0; border-bottom: 1px solid #e8e6df; display: flex; align-items: start;">
<div>
<strong>Flexible Deployment: Cloud, Self-Hosted, or Private Cloud</strong><br>
By default, the Carbone integration for Odoo uses the Carbone Cloud API, hosted and managed by us for seamless performance.<br>
For organizations with <b>strict privacy or security requirements</b>, you can easily deploy Carbone On-Premise on your own infrastructure (GCP, Azure, AWS, or others).
The transition from Cloud to On-Premise is instantaneous: simply update the API endpoint, and youre up and running in minutes.
</div>
</li>
<li style="padding: 20px 0; border-bottom: 1px solid #e8e6df; display: flex; align-items: start;">
<div>
<strong>Open Source</strong><br>
The Carbone plugin for Odoo is open-source under the LGPL-3 license.<br>
The Carbone Document Engine is open-core and available on <a href="https://github.com/carboneio/carbone" target="_blank">GitHub</a>, licensed under the <a href="https://github.com/carboneio/carbone/blob/master/LICENSE.md" target="_blank">Carbone Community License</a>.
</div>
</li>
<li style="padding: 20px 0; border-bottom: 1px solid #e8e6df; display: flex; align-items: start;">
<div>
<strong>Trusted by Thousands of Happy Users</strong><br>
Carbone is highly rated on major review platforms:<br> <b>|</b>
G2: 4.8/5 (Top-rated document automation solution)<br> <b>|</b>
Capterra: 4.8/5 (Loved for ease of use and reliability)<br> <b>|</b>
Trustpilot: 4.7/5 (Praise for the best customer support in the industry)
</div>
</li>
</ul>
</div>
<!-- Installation -->
<div class="section" style="margin-bottom: 40px; padding: 0; background: transparent; border-radius: 0; box-shadow: none;">
<div class="text-item" style="margin-bottom: 40px; padding: 0; background: transparent; border-radius: 0; box-shadow: none; position: relative;">
<h2 class="text-bottom" style="color: #001E2B; font-size: 2.2em; margin-bottom: 30px; border-bottom: 1px solid #001E2B; padding-bottom: 15px; font-weight: 700; letter-spacing: -0.01em; position: relative; z-index: 1;">Installation & Configuration</h2>
<!-- <img src="./assets/logo/icon-installation.png" style="position: absolute;height: 100%;top: 0;right: -5px;"/>-->
</div>
<h3>1. Installation</h3>
<p>Install the module from the Odoo Store or directly from your Odoo interface:</p>
<ul style="margin-left: 40px; margin-top: 10px;">
<li>Applications → Search for "Carbone Document Generator"</li>
<li>Click on "Install"</li>
</ul>
<h3>2. Configuration</h3>
<p>Set up your Carbone.io API key:</p>
<ul style="margin-left: 40px; margin-top: 10px;">
<li>Settings → Carbone.io Integration → API Keys</li>
<li>Enter your API key (Get yours at your <a style="color : #5e17eb" href="https://account.carbone.io">Carbone Account</a>)</li>
</ul>
<h3>3. Usage</h3>
<p>To automate documents in Odoo:</p>
<ul style="margin-left: 40px; margin-top: 10px;">
<li>Upload your templates to your Carbone account</li>
<li>Create a new "report" in the dedicated menu</li>
<li>Link them to Odoo models (invoices, quotes, etc.)</li>
<li>Generate your documents in one click!</li>
</ul>
</div>
<!-- Spécifications Techniques -->
<div class="section" style="margin-bottom: 40px; padding: 0; background: transparent; border-radius: 0; box-shadow: none;">
<div class="text-item" style="margin-bottom: 40px; padding: 0; background: transparent; border-radius: 0; box-shadow: none; position: relative;">
<h2 class="text-bottom" style="color: #001E2B; font-size: 2.2em; margin-bottom: 30px; border-bottom: 1px solid #001E2B; padding-bottom: 15px; font-weight: 700; letter-spacing: -0.01em; position: relative; z-index: 1;">Technical Specifications</h2>
<!-- <img src="./assets/logo/icon-technical-specs.png" style="position: absolute;height: 100%;top: 0;right: -5px;"/>-->
</div>
<div class="tech-specs" style="background-color: #ffffff; padding: 30px; border-radius: 20px; margin-top: 30px; border: 1px solid #e8e6df;">
<table style="width: 100%; border-collapse: collapse;">
<tr>
<td style="padding: 15px 0; border-bottom: 1px solid #e8e6df;font-size: 0.95em; font-weight: 500; color: #001E2B; width: 240px;">Version Odoo</td>
<td style="padding: 15px 0; border-bottom: 1px solid #e8e6df;font-size: 0.95em; color: #5a5a5a;">18.0</td>
</tr>
<tr>
<td style="padding: 15px 0; border-bottom: 1px solid #e8e6df;font-size: 0.95em; font-weight: 500; color: #001E2B; width: 240px;">Dépendances</td>
<td style="padding: 15px 0; border-bottom: 1px solid #e8e6df;font-size: 0.95em; color: #5a5a5a;">base, web, base_setup, export_json</td>
</tr>
<tr>
<td style="padding: 15px 0; border-bottom: 1px solid #e8e6df;font-size: 0.95em; font-weight: 500; color: #001E2B; width: 240px;">Catégorie</td>
<td style="padding: 15px 0; border-bottom: 1px solid #e8e6df;font-size: 0.95em; color: #5a5a5a;">Productivity / Reporting / Automation / Nocode / Lowcode</td>
</tr>
<tr>
<td style="padding: 15px 0; border-bottom: 1px solid #e8e6df;font-size: 0.95em; font-weight: 500; color: #001E2B; width: 240px;">Licence</td>
<td style="padding: 15px 0; border-bottom: 1px solid #e8e6df;font-size: 0.95em; color: #5a5a5a;">LGPL-3</td>
</tr>
<tr>
<td style="padding: 15px 0; border-bottom: 1px solid #e8e6df;font-size: 0.95em; font-weight: 500; color: #001E2B; width: 240px;">API Carbone</td>
<td style="padding: 15px 0; border-bottom: 1px solid #e8e6df;font-size: 0.95em; color: #5a5a5a;">Compatible v3, v4 and v5</td>
</tr>
<tr>
<td style="padding: 15px 0; border-bottom: 1px solid #e8e6df;font-size: 0.95em; font-weight: 500; color: #001E2B; width: 240px;">Supported input formats</td>
<td style="padding: 15px 0; border-bottom: 1px solid #e8e6df;font-size: 0.95em; color: #5a5a5a;">PDF, DOCX, XLSX, PPTX, ODG, IDML, ODT, ODS, ODP, XML, HTML, and Markdown.</td>
</tr>
<tr>
<td style="padding: 15px 0; border-bottom: 1px solid #e8e6df;font-size: 0.95em; font-weight: 500; color: #001E2B; width: 240px;">Supported output formats</td>
<td style="padding: 15px 0; border-bottom: 1px solid #e8e6df;font-size: 0.95em; color: #5a5a5a;">PDF, DOCX, ODT, BIB, DOC, DOC6, DOC95, DOCBOOK, DOCX7, FODT, HTML, LATEX, MEDIAWIKI, OOXML, OTT, PDB, PSW, RTF, SDW, SDW4, SDW3, STW, SXW, TEXT, TXT, UOT, VOR, VOR4, VOR3, XHTML, JPG, JPEG, PNG, EPUB, XLSX, ODS, CSV, DBF, DIF, FODS, OTS, PXL, SDC, SDC4, SDC3, SLK, STC, SXC, UOS, XLS, XLS5, XLS95, XLT, XLT5, XLT95, PPTX, ODP, BMP, EMF, EPS, FODP, GIF, MET, ODG, PBM, PCT, PGM, PPM, PWP, RAS, SDA, SDD, SDD3, SDD4, SXD, STI, SVG, SVM, SWF, SXI, TIFF, UOP, VOR5, OTP, POTM, POT, PPS, PPT, ETEXT, HTML10, TEXT10, ODD, OTG, STD, SXD3, SXD5, SXW, XPM, and IDML</td>
</tr>
</table>
</div>
</div>
<!-- Documentation -->
<div class="section" style="margin-bottom: 40px; padding: 0; background: transparent; border-radius: 0; box-shadow: none;">
<div class="text-item" style="margin-bottom: 40px; padding: 0; background: transparent; border-radius: 0; box-shadow: none; position: relative;">
<h2 class="text-bottom" style="color: #001E2B; font-size: 2.2em; margin-bottom: 30px; border-bottom: 1px solid #001E2B; padding-bottom: 15px; font-weight: 700; letter-spacing: -0.01em; position: relative; z-index: 1;">Documentation & Ressource</h2>
<img src="./assets/logo/icon-documentation.png" style="position: absolute;height: 100%;top: 0;right: -5px;"/>
</div>
<ul class="doc-ressource-list" style=" list-style:'— '; padding: 0;margin-left: 40px; margin-right: 20px; line-height: 2; color: #5e17eb;
font-weight: 400; margin-top: 2px;">
<li><a style="color : #5e17eb" href="https://carbone.io/examples/">Templates Examples</a></li>
<li><a style="color : #5e17eb" href="https://carbone.io/documentation/design/overview/getting-started.html">Getting started</a></li>
<li><a style="color : #5e17eb" href="https://carbone.io/documentation/design/overview/philosophy.html">Philosophy</a></li>
</ul>
</div>
<!-- CTA -->
<div class="cta-section" style="background-color: #2C003B; color: #f5f3ed; padding: 70px 60px; border-radius: 30px; text-align: center; margin-top: 80px;">
<h2 style="color: white; border: none; font-size: 2.2em; margin-bottom: 15px;">
Ready to Upgrade Your Odoo Reports?
</h2>
<p style="font-size: 1.2em; margin-bottom: 10px;">
Download the module for free and start generating professional documents in minutes!
</p>
<a href="#" class="cta-button" style="display: inline-block; background: transparent; color: #f5f3ed; border: 2px solid #f5f3ed; padding: 16px 45px; border-radius: 2px; text-decoration: none; font-weight: 400; font-size: 1em; margin-top: 25px; transition: all 0.3s ease; letter-spacing: 0.05em; text-transform: uppercase;">Download</a>
</div>
<!-- Footer -->
<div class="footer" style="text-align: center; padding: 40px 20px; color: #666;">
<p style="margin-top: 10px;">Questions? Contact us at <a style="color:#5e17eb" href="mailto:contact+odoo@carbone.io">Carbone</a></p>.
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,28 @@
/** @odoo-module */
import {ListController} from "@web/views/list/list_controller";
import {registry} from "@web/core/registry";
import {listView} from "@web/views/list/list_view";
import {_t} from "@web/core/l10n/translation";
export class CarboneIrActionsReportController extends ListController {
setup() {
super.setup();
}
OnClickCreateReport() {
this.actionService.doAction({
type: "ir.actions.act_window",
res_model: "carbone.create.report.wizard",
name: _t("Create new Carbone report"),
view_mode: "form",
view_type: "form",
views: [[false, "form"]],
target: "new",
res_id: false,
});
}
}
CarboneIrActionsReportController.template = "carbone_report_button_on_tree_view.ListView.Buttons";
export const CustomCarboneIrActionsReportController = {
...listView,
Controller: CarboneIrActionsReportController,
};
registry.category("views").add("carbone_report_button_in_tree", CustomCarboneIrActionsReportController);

View File

@@ -0,0 +1,47 @@
/** @odoo-module **/
import {download} from "@web/core/network/download";
import {registry} from "@web/core/registry";
import {user} from "@web/core/user";
registry.category("ir.actions.report handlers").add("carbone_handler", async function (action, options, env) {
if (action.report_type === "carbone") {
const type = action.report_type;
let url = `/report/${type}/${action.report_name}`;
const actionContext = action.context || {};
if (action.data && JSON.stringify(action.data) !== "{}") {
// Build a query string with `action.data` (it's the place where reports
// using a wizard to customize the output traditionally put their options)
const action_options = encodeURIComponent(JSON.stringify(action.data));
const context = encodeURIComponent(JSON.stringify(actionContext));
url += `?options=${action_options}&context=${context}`;
} else {
if (actionContext.active_ids) {
url += `/${actionContext.active_ids.join(",")}`;
}
const context = encodeURIComponent(JSON.stringify(user.context));
url += `?context=${context}`;
}
env.services.ui.block();
try {
const context = actionContext || user.context;
await download({
url: "/report/download",
data: {
data: JSON.stringify([url, action.report_type]),
context: JSON.stringify(context),
},
});
} finally {
env.services.ui.unblock();
}
const onClose = options.onClose;
if (action.close_on_report_download) {
return env.services.action.doAction({type: "ir.actions.act_window_close"}, {onClose});
} else if (onClose) {
onClose();
}
return Promise.resolve(true);
}
return Promise.resolve(false);
});

View File

@@ -0,0 +1,36 @@
/** @odoo-module **/
import {registry} from "@web/core/registry";
function copyJsonAction(env, action, notifyUser = true) {
const notification = env.services.notification;
const jsonData = action.context.json_data;
if (!jsonData && notifyUser) {
notification.add("No JSON given in context", {type: "warning"});
return {type: "ir.actions.act_window_close"};
}
const formattedJson = formatJsonData(jsonData);
copyJsonToTarget(formattedJson, notification, notifyUser);
return {type: "ir.actions.act_window_close"};
}
registry.category("actions").add("copy_options_to_carbone", copyJsonAction);
export function formatJsonData(jsonData) {
if (typeof jsonData === "string") {
try {
const parsed = JSON.parse(jsonData);
return JSON.stringify(parsed, null, 4);
} catch (e) {
return jsonData;
}
} else if (typeof jsonData === "object") {
return JSON.stringify(jsonData, null, 4);
}
return String(jsonData || "");
}

View File

@@ -0,0 +1,485 @@
/** @odoo-module **/
import {registry} from "@web/core/registry";
import {loadJS} from "@web/core/assets";
import {rpc} from "@web/core/network/rpc";
import {user} from "@web/core/user";
import {formatJsonData} from "@report_carbone/js/report/copy_export_json";
import {FormController} from "@web/views/form/form_controller";
import {patch} from "@web/core/utils/patch";
// Bon, voici mon analyse complète et la correction. Le problème est clair :
//
// 1. openTemplateId → Tf.openTemplate({id}, true) sans retourner la promise
// 2. openTemplate est async : fetch versions → getSample → restoreSampleDataFromTemplate → setState → render → updatePreview
// 3. restoreSampleDataFromTemplate fait kf.setState({data: oldData, ...}) — écrase nos données
// 4. kf.setState déclenche le re-render de tous les subscribers → updatePreview → AbortError si un preview était déjà en cours
// 5. Aucun événement externe n'est émis quand openTemplate termine (options:updated n'est émis que par les interactions UI)
//
// Le listener options:updated ne se déclenche jamais dans ce cas. Il faut une autre stratégie :
// ========== CONSTANTS ==========
// /!\ You must leave a minimum of 450ms, because when creating a new report, you must allow
// Carbone time to deploy the new template, otherwise it cannot be displayed directly.
// Between two recording changes, a minimum of 250 ms is required.
const DEBOUNCE_DELAY = 450;
const DEFAULT_OPTIONS = ["{}", "{}", "en", "UTC", "USD", "", ""];
const CARBONE_STUDIO_SELECTOR = "carbone-studio";
// ========== DOM UTILITY==========
const LOADER_CLASS = "carbone-studio-loader";
class DOMUtils {
static setDisplayNone(element) {
if (!element) return;
Object.assign(element.style, {
display: "none",
width: "0%",
height: "0%",
});
}
static showLoader(studio) {
DOMUtils.removeLoader(studio);
const parent = studio.parentElement;
if (!parent) return null;
if (!parent.style.position || parent.style.position === "static") {
parent.style.position = "relative";
}
const overlay = document.createElement("div");
overlay.className = LOADER_CLASS;
Object.assign(overlay.style, {
position: "absolute",
inset: "0",
display: "flex",
alignItems: "center",
justifyContent: "center",
background: "rgba(255, 255, 255, 0.75)",
zIndex: "1000",
});
overlay.innerHTML = `
<div style="text-align:center">
<div class="o_loading_indicator">
<img src="/web/static/img/spin.svg" alt="" style="width:48px;height:48px" />
</div>
<div style="margin-top:8px;font-size:14px;color:#666">
Loading Odoo data...
</div>
</div>`;
parent.appendChild(overlay);
return overlay;
}
static removeLoader(studio) {
const parent = studio?.parentElement;
if (!parent) return;
parent.querySelectorAll(`.${LOADER_CLASS}`).forEach((el) => el.remove());
}
static waitForElement(selector, callback) {
const element = document.querySelector(selector);
if (element) {
callback(element);
return null;
}
const observer = new MutationObserver(() => {
const found = document.querySelector(selector);
if (found) {
observer.disconnect();
callback(found);
}
});
observer.observe(document.body, {childList: true, subtree: true});
return observer;
}
static improveOdooRecordView() {
const node = document.querySelector("div.o_group.row.align-items-start");
if (!node) return;
node.childNodes.forEach((childNode) => {
if (childNode.nodeName === "DIV") {
childNode.classList.add("col-lg-12");
}
});
}
}
// ========== CARBONE HANDLER==========
class CarboneStudioManager {
constructor() {
this.observer = null;
this.studioURL = null;
this.carboneToken = null;
this.startIrReportId = null;
this.isInitialized = false;
this.debounceTimer = null;
this.optionsList = [...DEFAULT_OPTIONS];
this.currentStudio = null;
this.isTemplateLoading = false;
}
async initialize() {
if (!this.isInitialized) {
this.studioURL = await CarboneAPI.importCarboneJs();
this.carboneToken = await CarboneAPI.getCarboneApiKey();
this.isInitialized = true;
}
return {
studioURL: this.studioURL,
carboneToken: this.carboneToken,
};
}
/**
* Safely close the current template, suppressing AbortError rejections
* that the Carbone web component fires internally (from aborted fetch
* calls in updatePreview) but does not handle itself.
*/
safeCloseTemplate() {
if (!this.currentStudio) return;
const suppressAbort = (event) => {
if (event.reason?.name === "AbortError") {
event.preventDefault();
}
};
window.addEventListener("unhandledrejection", suppressAbort);
try {
this.currentStudio.closeTemplate();
} catch (e) {
if (e.name !== "AbortError") {
console.error("Error closing template:", e);
}
}
// Keep the listener long enough for the async rejection to fire
setTimeout(() => window.removeEventListener("unhandledrejection", suppressAbort), 500);
}
cleanup() {
if (this.observer) {
this.observer.disconnect();
this.observer = null;
}
if (this.isTemplateLoading && this.currentStudio) {
this.safeCloseTemplate();
this.isTemplateLoading = false;
}
}
resetOptions() {
this.optionsList = [...DEFAULT_OPTIONS];
}
async refreshStudio(irReportId = null, services) {
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
return new Promise((resolve) => {
this.debounceTimer = setTimeout(async () => {
try {
this.cleanup();
this.resetOptions();
const config = await this.initialize();
const isCarboneManager = await CarboneAPI.isCarboneManagerUser();
const isCarboneReport = this.getIsCarboneReport(services);
if (!isCarboneReport || !isCarboneManager) {
this.handleHideStudio(config);
resolve();
return;
}
if (irReportId) {
this.startIrReportId = irReportId;
const options = await CarboneAPI.getDefaultOptionParameter(irReportId);
if (options) {
this.optionsList = options;
}
}
this.observer = DOMUtils.waitForElement(CARBONE_STUDIO_SELECTOR, (studio) => {
this.setupStudio(studio, config);
});
resolve();
} catch (error) {
console.error("Error refreshing Carbone Studio:", error);
this.isTemplateLoading = false;
resolve();
}
}, DEBOUNCE_DELAY);
});
}
setupStudio(studio, config) {
if (this.currentStudio && this.isTemplateLoading) {
this.safeCloseTemplate();
}
this.currentStudio = studio;
Object.assign(studio.style, {
display: "",
width: "100%",
height: "100%",
});
DOMUtils.improveOdooRecordView();
this.isTemplateLoading = true;
this.launchCarbone(studio, config.studioURL, config.carboneToken);
this.addStudioListeners(studio);
}
handleHideStudio(config) {
DOMUtils.waitForElement(CARBONE_STUDIO_SELECTOR, (studio) => {
if (!config.studioURL || !config.carboneToken) {
DOMUtils.setDisplayNone(studio);
}
});
}
getIsCarboneReport(services) {
try {
const actionService = services.action;
if (!actionService?.currentController?.action) {
return false;
}
const currentController = actionService.currentController;
this.startIrReportId = currentController.currentState?.resId;
return currentController.action.context?.default_report_type === "carbone";
} catch (error) {
console.error("Error getting current ir.actions.report:", error);
return false;
}
}
async launchCarbone(studio, studioURL, carboneToken) {
if (!studio || !studioURL || !carboneToken) {
DOMUtils.setDisplayNone(studio);
return false;
}
studio.setConfig({origin: studioURL, token: carboneToken, mode: "embedded-versioning"});
const options = this.buildStudioOptions();
const template = this.buildTemplateConfig();
await this.safeOpenTemplate(studio, template, options);
this.isTemplateLoading = false;
return true;
}
buildStudioOptions() {
if (!this.optionsList || this.optionsList.length < 5) {
this.optionsList = [...DEFAULT_OPTIONS];
}
return {
data: JSON.parse(this.optionsList[0]),
translations: JSON.parse(this.optionsList[1]),
lang: this.optionsList[2],
timezone: this.optionsList[3],
currencySource: this.optionsList[4],
currencyTarget: this.optionsList[4],
};
}
buildTemplateConfig() {
const template = {templateId: this.optionsList[5]};
const extensionName = this.optionsList[6];
if (extensionName !== false) {
template.extension = extensionName;
}
return template;
}
safeOpenTemplate(studio, template, options) {
// openTemplateId() internally calls Tf.openTemplate() which is async,
// but openTemplateId does NOT return the promise (fire-and-forget).
// openTemplate fetches the saved "sample" from the Carbone backend
// and calls restoreSampleDataFromTemplate(), which overwrites
// kf.state.data with OLD saved data via kf.setState().
//
// No external event is emitted when openTemplate finishes
// (options:updated is only emitted by UI interactions).
//
// Strategy: suppress the AbortError caused by the competing
// updatePreview calls, show a loader, then re-apply our Odoo data
// after a delay long enough for openTemplate to finish loading.
const suppressAbort = (event) => {
if (event.reason?.name === "AbortError") {
event.preventDefault();
}
};
window.addEventListener("unhandledrejection", suppressAbort);
DOMUtils.showLoader(studio);
try {
studio.openTemplateId(template["templateId"]);
} catch (e) {
console.error("Error opening template:", e);
DOMUtils.removeLoader(studio);
window.removeEventListener("unhandledrejection", suppressAbort);
return Promise.resolve();
}
// Wait for openTemplate's async work (getVersions + getSample +
// restoreSampleDataFromTemplate + setState/render) to complete,
// then overwrite with the correct Odoo record data.
return new Promise((resolve) => {
setTimeout(() => {
studio.setRenderOptions(options);
DOMUtils.removeLoader(studio);
setTimeout(() => window.removeEventListener("unhandledrejection", suppressAbort), 2000);
resolve();
}, 2050);
});
}
addStudioListeners(studio) {
const events = {
connected: "studio connected.",
disconnected: "studio disconnected.",
"options:updated": null,
};
Object.entries(events).forEach(([event, message]) => {
studio.addEventListener(event, (e) => {
if (message) {
console.log(message, e);
} else {
console.log(e.detail);
}
});
});
}
}
// ========== API CARBONE ==========
class CarboneAPI {
static async isCarboneManagerUser() {
if (!user) return false;
return await user.hasGroup("report_carbone.group_report_carbone_manager");
}
static async importCarboneJs() {
try {
const res = await rpc("/carbone_config/carbone_studio_params");
await loadJS(res.js_url);
return res.studio_url;
} catch (e) {
console.error(`Failed to launch Carbone Studio: ${e.message}`);
return null;
}
}
static async getCarboneApiKey() {
try {
const res = await rpc("/carbone_config/carbone_api_key", {});
return res.token;
} catch (e) {
console.error("Failed to retrieve API key:", e);
return null;
}
}
static async getDefaultOptionParameter(irReportId) {
try {
const res = await rpc("/web/dataset/call_kw", {
model: "ir.actions.report",
method: "action_setup_carbone_studio_options",
args: [[irReportId]],
kwargs: {},
});
return [
formatJsonData(res.context.json_data),
formatJsonData(res.context.json_translate_data),
res.context.lang,
res.context.timezone,
res.context.currency,
res.context.template,
res.context.extension,
];
} catch (e) {
console.error("Failed to retrieve options from backend:", e);
return null;
}
}
}
// ========== PATCH FORM CONTROLLER ==========
patch(FormController.prototype, {
// Allows you to systematically refresh the studio, either by scrolling through records using the arrows or
// by switching back and forth between the list view.
onWillLoadRoot(nextConfiguration) {
super.onWillLoadRoot(...arguments);
// Skip refresh during save — onRecordSaved will handle it after save completes
if (!this._carboneIsSaving) {
this.handleCarboneStudio(nextConfiguration.resId);
}
},
async onPagerUpdate(params) {
await super.onPagerUpdate(...arguments);
this.handleCarboneStudio(this.model.root.resId);
},
async save(params) {
this._carboneIsSaving = true;
const result = await super.save(...arguments);
this._carboneIsSaving = false;
return result;
},
async onRecordSaved(record, changes) {
await super.onRecordSaved(...arguments);
this.handleCarboneStudio(this.model.root.resId);
},
handleCarboneStudio(resId) {
const dynamicService = this.env.services.dynamic_element_service;
const isCarbone = this.props.context.default_report_type === "carbone";
if (!isCarbone) {
this.waitForStudioThenRemove();
return;
}
dynamicService?.refreshCarboneStudio(resId);
},
waitForStudioThenRemove() {
DOMUtils.waitForElement(CARBONE_STUDIO_SELECTOR, (studio) => {
DOMUtils.setDisplayNone(studio);
});
},
});
// ========== SERVICE DYNAMIC ELEMENT ==========
export const dynamicElementService = {
dependencies: ["action"],
start(env, services) {
const manager = new CarboneStudioManager();
async function refreshCarboneStudio(irReportId = null) {
return await manager.refreshStudio(irReportId, services);
}
async function actionRefreshCarboneStudio() {
await refreshCarboneStudio(manager.startIrReportId);
}
registry.category("actions").add("action_refresh_carbone_studio", actionRefreshCarboneStudio);
return {refreshCarboneStudio};
},
};
registry.category("services").add("dynamic_element_service", dynamicElementService);

View File

@@ -0,0 +1,47 @@
carbone-studio {
height: 100%;
width: 100%;
}
.o_settings_carbone_container {
display: flex;
flex-direction: column;
width: 100% !important;
}
.o_field_row {
display: flex;
white-space: normal;
margin-bottom: 0.25rem;
overflow: visible;
}
.o_form_label_fixed {
min-width: 200px;
text-align: left;
font-weight: 400 !important;
flex: 1;
margin: 0;
}
.o_field_aligned {
flex: 1;
padding-top: 0.125rem;
}
.o_tooltip_icon {
font-size: 0.8rem;
margin-left: 0.5rem;
flex-shrink: 0;
opacity: 0.7;
display: flex;
align-items: center;
transition: opacity 0.2s ease;
}
.o_tooltip_icon:hover {
opacity: 1;
}
.carbone-link-website {
text-align: left;
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="carbone_report_button_on_tree_view.ListView.Buttons" t-inherit="web.ListView">
<xpath expr="//Layout/t[@t-set-slot='control-panel-create-button']" position="inside">
<button type="button" class="btn btn-primary" style="margin-right: 10px;" t-on-click="OnClickCreateReport">
New document
</button>
</xpath>
</t>
</templates>