html to pdf for ticket
This commit is contained in:
parent
41cd94e072
commit
8c768cfd5a
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,149 @@
|
||||||
|
@font-face {
|
||||||
|
font-family: Libre Barcode;
|
||||||
|
src: url(librebarcode128-regular.ttf);
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: Barlow Condensed;
|
||||||
|
src: url(barlowcondensed-regular.otf);
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: Barlow Condensed;
|
||||||
|
font-weight: 300;
|
||||||
|
src: url(barlowcondensed-light.otf);
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: Barlow Condensed;
|
||||||
|
font-weight: 700;
|
||||||
|
src: url(barlowcondensed-bold.otf);
|
||||||
|
}
|
||||||
|
|
||||||
|
@page {
|
||||||
|
margin: 0;
|
||||||
|
size: landscape;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: #eef1f5;
|
||||||
|
display: flex;
|
||||||
|
font-family: Barlow Condensed, sans-serif;
|
||||||
|
height: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background: #fff;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: #2A3239;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
height: 8cm;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 0;
|
||||||
|
width: 25cm;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
dl {
|
||||||
|
columns: 4;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
dt {
|
||||||
|
font-size: 9pt;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
dd {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
#informations {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
#informations h1 {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 25pt;
|
||||||
|
font-weight: 300;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
#informations #name {
|
||||||
|
margin-left: 1cm;
|
||||||
|
}
|
||||||
|
#informations #destination {
|
||||||
|
position: absolute;
|
||||||
|
right: 1cm;
|
||||||
|
}
|
||||||
|
#informations dl {
|
||||||
|
background: #2A3239;
|
||||||
|
color: #fff;
|
||||||
|
margin: 0;
|
||||||
|
padding: 1cm 0;
|
||||||
|
}
|
||||||
|
#informations dd {
|
||||||
|
border-left: 1pt solid #fff;
|
||||||
|
font-size: 35pt;
|
||||||
|
}
|
||||||
|
#informations dd:first-of-type {
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
#informations ul {
|
||||||
|
margin-left: 1cm;
|
||||||
|
}
|
||||||
|
#informations li {
|
||||||
|
font-weight: 300;
|
||||||
|
padding: 0.15cm;
|
||||||
|
}
|
||||||
|
#informations li:first-of-type {
|
||||||
|
background: #2A3239;
|
||||||
|
border-radius: 4pt;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
#informations li:last-of-type {
|
||||||
|
font-family: Libre Barcode, cursive;
|
||||||
|
font-size: 25pt;
|
||||||
|
margin-left: auto;
|
||||||
|
padding-right: 1cm;
|
||||||
|
padding-top: 0.5cm;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ticket {
|
||||||
|
border-left: 1pt dashed #2A3239;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 8cm;
|
||||||
|
justify-content: space-around;
|
||||||
|
padding: 0 1cm;
|
||||||
|
}
|
||||||
|
#ticket h2 {
|
||||||
|
font-weight: 300;
|
||||||
|
margin: 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
#ticket p {
|
||||||
|
font-family: Libre Barcode, cursive;
|
||||||
|
font-size: 25pt;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#ticket dl {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#ticket li {
|
||||||
|
margin: 0 0.25cm;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,611 @@
|
||||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
|
||||||
|
<!-- [ if !mso]> <!-->
|
||||||
|
<meta content="IE=edge" http-equiv="X-UA-Compatible"/>
|
||||||
|
<!-- <![endif] -->
|
||||||
|
<meta content="telephone=no" name="format-detection"/>
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
|
<title>Vos billets</title>
|
||||||
|
<style type="text/css">
|
||||||
|
.ExternalClass {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div, .ExternalClass b, .ExternalClass br, .ExternalClass img {
|
||||||
|
line-height: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* iOS BLUE LINKS */
|
||||||
|
.appleBody a {
|
||||||
|
color: #212121;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appleFooter a {
|
||||||
|
color: #212121 !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* END iOS BLUE LINKS */
|
||||||
|
img {
|
||||||
|
color: #ffffff;
|
||||||
|
text-align: center;
|
||||||
|
font-family: Open Sans, Helvetica, Arial, sans-serif;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
-webkit-text-size-adjust: 100% !important;
|
||||||
|
-ms-text-size-adjust: 100% !important;
|
||||||
|
font-family: 'Open Sans', Helvetica, Arial, sans-serif !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body, #body_style {
|
||||||
|
background: #fffffe;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tbody {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.yshortcuts, a span.yshortcuts {
|
||||||
|
color: #000001;
|
||||||
|
background-color: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.yshortcuts:hover,
|
||||||
|
span.yshortcuts:active,
|
||||||
|
span.yshortcuts:focus {
|
||||||
|
color: #000001;
|
||||||
|
background-color: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
-ms-interpolation-mode:: bicubic;
|
||||||
|
}
|
||||||
|
|
||||||
|
a[x-apple-data-detectors] {
|
||||||
|
color: inherit !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
font-size: inherit !important;
|
||||||
|
font-family: inherit !important;
|
||||||
|
font-weight: inherit !important;
|
||||||
|
line-height: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**** My desktop styles ****/
|
||||||
|
@media only screen and (min-width: 600px) {
|
||||||
|
.noDesk {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.td-padding {
|
||||||
|
padding-left: 15px !important;
|
||||||
|
padding-right: 15px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.padding-container {
|
||||||
|
padding: 0px 15px 0px 15px !important;
|
||||||
|
mso-padding-alt: 0px 15px 0px 15px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-column-left-padding {
|
||||||
|
padding: 0px 0px 0px 0px !important;
|
||||||
|
mso-alt-padding: 0px 0px 0px 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-column-right-padding {
|
||||||
|
padding: 0px 0px 0px 0px !important;
|
||||||
|
mso-alt-padding: 0px 0px 0px 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile {
|
||||||
|
display: none !important
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**** My mobile styles ****/
|
||||||
|
@media only screen and (max-width: 599px) and (-webkit-min-device-pixel-ratio: 1) {
|
||||||
|
*[class].wrapper {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
*[class].container {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
*[class].mobile {
|
||||||
|
width: 100% !important;
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
*[class].image {
|
||||||
|
width: 100% !important;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
*[class].center {
|
||||||
|
margin: 0 auto !important;
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
*[class="mobileOff"] {
|
||||||
|
width: 0px !important;
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
*[class*="mobileOn"] {
|
||||||
|
display: block !important;
|
||||||
|
max-height: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
p[class="mobile-padding"] {
|
||||||
|
padding-left: 0px !important;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.padding-container {
|
||||||
|
padding: 0px 15px 0px 15px !important;
|
||||||
|
mso-padding-alt: 0px 15px 0px 15px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hund {
|
||||||
|
width: 100% !important;
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.td-padding {
|
||||||
|
padding-left: 15px !important;
|
||||||
|
padding-right: 15px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-column-left-padding {
|
||||||
|
padding: 18px 0px 18px 0px !important;
|
||||||
|
mso-alt-padding: 18px 0px 18px 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-column-right-padding {
|
||||||
|
padding: 18px 0px 0px 0px !important;
|
||||||
|
mso-alt-padding: 18px 0px 0px 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stack {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100% !important;
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
*[class="hide"] {
|
||||||
|
display: none !important
|
||||||
|
}
|
||||||
|
|
||||||
|
*[class="Gmail"] {
|
||||||
|
display: none !important
|
||||||
|
}
|
||||||
|
|
||||||
|
.Gmail {
|
||||||
|
display: none !important
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-padding-fix {
|
||||||
|
padding: 0px 0px 18px 0px !important;
|
||||||
|
mso-alt-padding: 0px 0px 18px 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.social, .social:active {
|
||||||
|
opacity: 1 !important;
|
||||||
|
transform: scale(1);
|
||||||
|
transition: all .2s !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social:hover {
|
||||||
|
opacity: 0.8 !important;
|
||||||
|
transform: scale(1.1);
|
||||||
|
transition: all .2s !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.raised {
|
||||||
|
transition: box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
transition: all .2s;
|
||||||
|
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.raised:hover {
|
||||||
|
box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2);
|
||||||
|
transition: all .2s;
|
||||||
|
-webkit-box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2);
|
||||||
|
transition: all .2s;
|
||||||
|
-moz-box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2);
|
||||||
|
transition: all .2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-1 {
|
||||||
|
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .16), 0 2px 10px 0 rgba(0, 0, 0, .12);
|
||||||
|
-webkit-box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .16), 0 2px 10px 0 rgba(0, 0, 0, .12);
|
||||||
|
-moz-box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .16), 0 2px 10px 0 rgba(0, 0, 0, .12);
|
||||||
|
transition: box-shadow .45s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-1:hover {
|
||||||
|
box-shadow: 0 8px 17px 0 rgba(0, 0, 0, .2), 0 6px 20px 0 rgba(0, 0, 0, .19);
|
||||||
|
-webkit-box-shadow: 0 8px 17px 0 rgba(0, 0, 0, .2), 0 6px 20px 0 rgba(0, 0, 0, .19);
|
||||||
|
-moz-box-shadow: 0 8px 17px 0 rgba(0, 0, 0, .2), 0 6px 20px 0 rgba(0, 0, 0, .19);
|
||||||
|
transition: box-shadow .45s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ripplelink {
|
||||||
|
display: block
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
-webkit-transition: all 0.2s ease;
|
||||||
|
-moz-transition: all 0.2s ease;
|
||||||
|
-o-transition: all 0.2s ease;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ripplelink:hover {
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ink {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 100%;
|
||||||
|
-webkit-transform: scale(0);
|
||||||
|
-moz-transform: scale(0);
|
||||||
|
-o-transform: scale(0);
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate {
|
||||||
|
-webkit-animation: ripple 0.65s linear;
|
||||||
|
-moz-animation: ripple 0.65s linear;
|
||||||
|
-ms-animation: ripple 0.65s linear;
|
||||||
|
-o-animation: ripple 0.65s linear;
|
||||||
|
animation: ripple 0.65s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes ripple {
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-transform: scale(2.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-moz-keyframes ripple {
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
-moz-transform: scale(2.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-o-keyframes ripple {
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
-o-transform: scale(2.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ripple {
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(2.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<!--[if gte mso 9]>
|
||||||
|
<xml>
|
||||||
|
<o:OfficeDocumentSettings>
|
||||||
|
<o:AllowPNG/>
|
||||||
|
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||||
|
</o:OfficeDocumentSettings>
|
||||||
|
</xml>
|
||||||
|
<![endif]-->
|
||||||
|
</head>
|
||||||
|
<body style="margin:0; padding:0; background-color: #eeeeee;" bgcolor="#eeeeee">
|
||||||
|
<!--[if mso]>
|
||||||
|
<style type="text/css">
|
||||||
|
body, table, td {
|
||||||
|
font-family: Arial, Helvetica, sans-serif !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<![endif]-->
|
||||||
|
<!-- START EMAIL -->
|
||||||
|
<table width="100%" cellpadding="0" cellspacing="0" border="0" bgcolor="#eeeeee">
|
||||||
|
<div class="Gmail"
|
||||||
|
style="height: 1px !important; margin-top: -1px !important; max-width: 600px !important; min-width: 600px !important; width: 600px !important;"></div>
|
||||||
|
<div style="display: none; max-height: 0px; overflow: hidden;">
|
||||||
|
Vos billets pour {{ config.organisation }}
|
||||||
|
</div>
|
||||||
|
<!-- Insert ‌ hack after hidden preview text -->
|
||||||
|
<div style="display: none; max-height: 0px; overflow: hidden;">
|
||||||
|
‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- START LOGO -->
|
||||||
|
<tr>
|
||||||
|
<td width="100%" valign="top" align="center" class="padding-container"
|
||||||
|
style="padding: 18px 0px 18px 0px!important; mso-padding-alt: 18px 0px 18px 0px;">
|
||||||
|
<table width="600" cellpadding="0" cellspacing="0" border="0" align="center" class="wrapper">
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<table cellpadding="0" cellspacing="0" border="0">
|
||||||
|
<tr>
|
||||||
|
<td width="100%" valign="top" align="center">
|
||||||
|
<table width="600" cellpadding="0" cellspacing="0" border="0" align="center"
|
||||||
|
class="wrapper" bgcolor="#eeeeee">
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<table width="600" cellpadding="0" cellspacing="0" border="0"
|
||||||
|
class="container" align="center">
|
||||||
|
<!-- START HEADER IMAGE -->
|
||||||
|
<tr>
|
||||||
|
<td align="center" class="hund" width="600">
|
||||||
|
<a href="www.tibillet.re">
|
||||||
|
<img src="https://wiki.tibillet.re/images/c/ca/Logo_Tibillet_Noir_Ombre.png"
|
||||||
|
width="200" alt="Logo" border="0"
|
||||||
|
style="max-width: 200px; display:block; ">
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- END HEADER IMAGE -->
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- END LOGO -->
|
||||||
|
|
||||||
|
<!-- START CARD 1 -->
|
||||||
|
<tr>
|
||||||
|
<td width="100%" valign="top" align="center" class="padding-container"
|
||||||
|
style="padding-top: 0px!important; padding-bottom: 18px!important; mso-padding-alt: 0px 0px 18px 0px;">
|
||||||
|
<table width="600" cellpadding="0" cellspacing="0" border="0" align="center" class="wrapper">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<table cellpadding="0" cellspacing="0" border="0">
|
||||||
|
<tr>
|
||||||
|
<td style="border-radius: 3px; border-bottom: 2px solid #d4d4d4;" class="card-1"
|
||||||
|
width="100%" valign="top" align="center">
|
||||||
|
<table style="border-radius: 3px;" width="600" cellpadding="0" cellspacing="0"
|
||||||
|
border="0" align="center" class="wrapper" bgcolor="#ffffff">
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<table width="600" cellpadding="0" cellspacing="0" border="0"
|
||||||
|
class="container">
|
||||||
|
<!-- START HEADER IMAGE -->
|
||||||
|
<tr>
|
||||||
|
<td align="center" class="hund ripplelink" width="600">
|
||||||
|
<img align="center" width="600"
|
||||||
|
style="border-radius: 3px 3px 0px 0px; width: 100%; max-width: 600px!important"
|
||||||
|
class="hund"
|
||||||
|
src="{{ config.img.med }}">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- END HEADER IMAGE -->
|
||||||
|
<!-- START BODY COPY -->
|
||||||
|
<tr>
|
||||||
|
<td class="td-padding" align="left"
|
||||||
|
style="font-family: 'Roboto Mono', monospace; color: #212121!important; font-size: 24px; line-height: 30px; padding-top: 18px; padding-left: 18px!important; padding-right: 18px!important; padding-bottom: 0px!important; mso-line-height-rule: exactly; mso-padding-alt: 18px 18px 0px 13px;">
|
||||||
|
{{ config.organisation }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="td-padding" align="left"
|
||||||
|
style="font-family: 'Roboto Mono', monospace; color: #212121!important; font-size: 16px; line-height: 24px; padding-top: 18px; padding-left: 18px!important; padding-right: 18px!important; padding-bottom: 0px!important; mso-line-height-rule: exactly; mso-padding-alt: 18px 18px 0px 18px;">
|
||||||
|
Bonjour !
|
||||||
|
<br><br>
|
||||||
|
Nous vous confirmons votre achat
|
||||||
|
d'un total de {{ reservation.total_paid | floatformat:2 }}€.
|
||||||
|
|
||||||
|
<strong>Grand merci pour votre reservation !</strong>
|
||||||
|
<br><br>
|
||||||
|
{% if reservation.tickets %}
|
||||||
|
Vous trouverez vos tickets en pièce jointe.
|
||||||
|
{% else %}
|
||||||
|
Aucun ticket pour un concert ? Etrange etrange
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- END BODY COPY -->
|
||||||
|
<!-- BUTTON -->
|
||||||
|
<tr>
|
||||||
|
<td align="left"
|
||||||
|
style="padding: 18px 18px 18px 18px; mso-alt-padding: 18px 18px 18px 18px!important;">
|
||||||
|
<table width="100%" border="0" cellspacing="0"
|
||||||
|
cellpadding="0">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<table border="0" cellspacing="0"
|
||||||
|
cellpadding="0">
|
||||||
|
<tr>
|
||||||
|
<td align="left"
|
||||||
|
style="border-radius: 3px;"
|
||||||
|
bgcolor="#17bef7">
|
||||||
|
<a class="button raised"
|
||||||
|
href="{{ config.domaine_cashless }}/rapport/invoice/{{ adhesion.commande }}"
|
||||||
|
target="_blank"
|
||||||
|
style="font-size: 14px; line-height: 14px; font-weight: 500; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; border-radius: 3px; padding: 10px 25px; border: 1px solid #17bef7; display: inline-block;">
|
||||||
|
TELECHARGER REÇU</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- END BUTTON -->
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- END CARD 1 -->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- START CARD 2 -->
|
||||||
|
<tr>
|
||||||
|
<td width="100%" valign="top" align="center" class="padding-container"
|
||||||
|
style="padding-top: 0px!important; padding-bottom: 18px!important; mso-padding-alt: 0px 0px 18px 0px;">
|
||||||
|
<table width="600" cellpadding="0" cellspacing="0" border="0" align="center" class="wrapper">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<table cellpadding="0" cellspacing="0" border="0">
|
||||||
|
<tr>
|
||||||
|
<td style="border-radius: 3px; border-bottom: 2px solid #d4d4d4;" class="card-1"
|
||||||
|
width="100%" valign="top" align="center">
|
||||||
|
<table style="border-radius: 3px;" width="600" cellpadding="0" cellspacing="0"
|
||||||
|
border="0" align="center" class="wrapper" bgcolor="#ffffff">
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<table width="600" cellpadding="0" cellspacing="0" border="0"
|
||||||
|
class="container">
|
||||||
|
<!-- START HEADER IMAGE -->
|
||||||
|
<tr>
|
||||||
|
<td align="center" class="hund ripplelink" width="600">
|
||||||
|
<img align="center" width="600"
|
||||||
|
style="border-radius: 3px 3px 0px 0px; width: 100%; max-width: 600px!important"
|
||||||
|
class="hund"
|
||||||
|
src="https://wiki.tibillet.re/images/0/0d/Logo_Tibillet_Noir_Ombre_700px.png">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- END HEADER IMAGE -->
|
||||||
|
<!-- START BODY COPY -->
|
||||||
|
<tr>
|
||||||
|
<td class="td-padding" align="left"
|
||||||
|
style="font-family: 'Roboto Mono', monospace; color: #212121!important; font-size: 24px; line-height: 30px; padding-top: 18px; padding-left: 18px!important; padding-right: 18px!important; padding-bottom: 0px!important; mso-line-height-rule: exactly; mso-padding-alt: 18px 18px 0px 13px;">
|
||||||
|
Vous entrez maintenant dans le réseau TiBillet !
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="td-padding" align="left"
|
||||||
|
style="font-family: 'Roboto Mono', monospace; color: #212121!important; font-size: 16px; line-height: 24px; padding-top: 18px; padding-left: 18px!important; padding-right: 18px!important; padding-bottom: 0px!important; mso-line-height-rule: exactly; mso-padding-alt: 18px 18px 0px 18px;">
|
||||||
|
|
||||||
|
<a href="https://www.tibillet.re">TiBillet</a> est une
|
||||||
|
solution coopérative et open-source de gestion associative,
|
||||||
|
de cashless et de billetterie orientée économie
|
||||||
|
sociale et solidaire.
|
||||||
|
<br><br>
|
||||||
|
Vous pouvez acheter des billets dans tout le reseau avec
|
||||||
|
votre
|
||||||
|
carte cashless, recharger cette dernière en ligne ou sur
|
||||||
|
place,
|
||||||
|
et en profiter dans tout les lieux partenaires !
|
||||||
|
<br><br>
|
||||||
|
Vous êtes artiste ? organisateur ? Vous pouvez créer votre
|
||||||
|
espace
|
||||||
|
TiBillet en quelques clics et démarcher dans tout le reseau
|
||||||
|
coopératif !
|
||||||
|
<br><br>
|
||||||
|
Si vous voulez en savoir plus, n'hésitez pas à visiter notre
|
||||||
|
<a href="https://wiki.tibillet.re">wiki</a> !
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- END BODY COPY -->
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- END CARD 2 -->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- SPACER -->
|
||||||
|
<!--[if gte mso 9]>
|
||||||
|
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="top" width="600" height="18">
|
||||||
|
<![endif]-->
|
||||||
|
<tr>
|
||||||
|
<td height="18px"></td>
|
||||||
|
</tr>
|
||||||
|
<!--[if gte mso 9]>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<![endif]-->
|
||||||
|
<!-- END SPACER -->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- SPACER -->
|
||||||
|
<!--[if gte mso 9]>
|
||||||
|
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="top" width="600" height="36">
|
||||||
|
<![endif]-->
|
||||||
|
<tr>
|
||||||
|
<td height="36px"></td>
|
||||||
|
</tr>
|
||||||
|
<!--[if gte mso 9]>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<![endif]-->
|
||||||
|
<!-- END SPACER -->
|
||||||
|
|
||||||
|
</table>
|
||||||
|
<!-- END EMAIL -->
|
||||||
|
<div style="display:none; white-space:nowrap; font:15px courier; line-height:0;">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<link rel="stylesheet" href="{% static 'ticket/ticket.css' %}"/>
|
||||||
|
|
||||||
|
<title>Boarding ticket</title>
|
||||||
|
<meta name="description" content="Boarding ticket">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<section id="informations">
|
||||||
|
<h1 id="name">Théodore Marcelin</h1>
|
||||||
|
<h1 id="destination">CDG ✈ LFLL</h1>
|
||||||
|
<dl>
|
||||||
|
<dt>Flight</dt>
|
||||||
|
<dd>DL31</dd>
|
||||||
|
<dt>Gate</dt>
|
||||||
|
<dd>29</dd>
|
||||||
|
<dt>Seat</dt>
|
||||||
|
<dd>26E</dd>
|
||||||
|
<dt>Zone</dt>
|
||||||
|
<dd>4</dd>
|
||||||
|
</dl>
|
||||||
|
<ul>
|
||||||
|
<li>5:10pm</li>
|
||||||
|
<li>Dec 15, 2018</li>
|
||||||
|
<li>Coach</li>
|
||||||
|
<li>1257797706706</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="ticket">
|
||||||
|
<p>1257797706706</p>
|
||||||
|
<h2>Théodore Marcelin</h2>
|
||||||
|
<dl>
|
||||||
|
<dt>Flight</dt>
|
||||||
|
<dd>DL31</dd>
|
||||||
|
<dt>Gate</dt>
|
||||||
|
<dd>29</dd>
|
||||||
|
<dt>Seat</dt>
|
||||||
|
<dd>26E</dd>
|
||||||
|
<dt>Zone</dt>
|
||||||
|
<dd>4</dd>
|
||||||
|
</dl>
|
||||||
|
<ul>
|
||||||
|
<li>CDG ✈ LFLL</li>
|
||||||
|
<li>5:10pm</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,98 @@
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
from django.core.mail import send_mail, EmailMessage, EmailMultiAlternatives
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
from django.utils import timezone
|
||||||
|
from weasyprint import HTML
|
||||||
|
from BaseBillet.models import Configuration, Reservation, Ticket
|
||||||
|
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
from ApiBillet.thread_mailer import ThreadMaileur
|
||||||
|
config = Configuration.get_solo()
|
||||||
|
context = {'config': config, }
|
||||||
|
mail = ThreadMaileur('jturbeaux@pm.me', "Vos Billets", template='mails/ticket.html', context=context)
|
||||||
|
mail.send_with_tread()
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
class ThreadMaileur():
|
||||||
|
|
||||||
|
def __init__(self, email, title, text=None, html=None, template=None, context=None):
|
||||||
|
self.title = title
|
||||||
|
self.email = email
|
||||||
|
self.text = text
|
||||||
|
self.html = html
|
||||||
|
self.config = Configuration.get_solo()
|
||||||
|
self.context = None
|
||||||
|
if template and context :
|
||||||
|
self.html = render_to_string(template, context=context)
|
||||||
|
self.context = context
|
||||||
|
self.attached_file = self._attached_file()
|
||||||
|
|
||||||
|
def _attached_file(self):
|
||||||
|
attached_file = []
|
||||||
|
if self.context :
|
||||||
|
if self.context.get('reservation'):
|
||||||
|
reservation: Reservation = self.context.get('reservation')
|
||||||
|
tickets = reservation.tickets.filter(status=Ticket.NOT_SCANNED)
|
||||||
|
if len(tickets) > 0:
|
||||||
|
for ticket in tickets :
|
||||||
|
attached_file.append(render_to_string('ticket/ticket.html', context={'context': 'context'}))
|
||||||
|
return attached_file
|
||||||
|
|
||||||
|
def config_valid(self):
|
||||||
|
EMAIL_HOST = os.environ.get('EMAIL_HOST')
|
||||||
|
EMAIL_PORT = os.environ.get('EMAIL_PORT')
|
||||||
|
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER')
|
||||||
|
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD')
|
||||||
|
|
||||||
|
if EMAIL_HOST and EMAIL_PORT and EMAIL_HOST_USER and EMAIL_HOST_PASSWORD and self.config.email :
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def send(self):
|
||||||
|
if self.html and self.config_valid() :
|
||||||
|
logger.info(f' send_mail')
|
||||||
|
mail = EmailMultiAlternatives(
|
||||||
|
self.title,
|
||||||
|
self.text,
|
||||||
|
self.config.email,
|
||||||
|
[self.email,],
|
||||||
|
)
|
||||||
|
mail.attach_alternative(self.html, "text/html")
|
||||||
|
|
||||||
|
# msg = EmailMessage(subject, html_content, from_email, [to])
|
||||||
|
# msg.content_subtype = "html" # Main content is now text/html
|
||||||
|
# msg.send()
|
||||||
|
|
||||||
|
# import ipdb; ipdb.set_trace()
|
||||||
|
i=1
|
||||||
|
for file in self.attached_file:
|
||||||
|
html_before_pdf = HTML(string=file)
|
||||||
|
mail.attach(f'ticket_{i}.pdf', html_before_pdf.write_pdf(), 'application/pdf')
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
|
||||||
|
mail_return = mail.send(fail_silently=False)
|
||||||
|
if mail_return == 1 :
|
||||||
|
logger.info(f' mail envoyé : {mail_return} - {self.email}')
|
||||||
|
else :
|
||||||
|
logger.error(f' mail non envoyé : {mail_return} - {self.email}')
|
||||||
|
return mail
|
||||||
|
else :
|
||||||
|
logger.error(f'Pas de contenu HTML ou de configuration email valide')
|
||||||
|
raise ValueError('Pas de contenu HTML ou de configuration email valide')
|
||||||
|
|
||||||
|
|
||||||
|
def send_with_tread(self):
|
||||||
|
|
||||||
|
self.send()
|
||||||
|
# logger.info(f'{timezone.now()} on lance le thread email {self.email}')
|
||||||
|
# thread_email = threading.Thread(target=self.send)
|
||||||
|
# thread_email.start()
|
||||||
|
# logger.info(f'{timezone.now()} Thread email lancé')
|
||||||
|
|
@ -4,6 +4,8 @@ from django.urls import include, path, re_path
|
||||||
from ApiBillet import views as api_view
|
from ApiBillet import views as api_view
|
||||||
from rest_framework import routers
|
from rest_framework import routers
|
||||||
|
|
||||||
|
from ApiBillet.views import TicketPdf
|
||||||
|
|
||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
router.register(r'events', api_view.EventsViewSet, basename='event')
|
router.register(r'events', api_view.EventsViewSet, basename='event')
|
||||||
router.register(r'products', api_view.ProductViewSet, basename='product')
|
router.register(r'products', api_view.ProductViewSet, basename='product')
|
||||||
|
|
@ -13,4 +15,6 @@ router.register(r'reservations', api_view.ReservationViewset, basename='reservat
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', include(router.urls)),
|
path('', include(router.urls)),
|
||||||
|
path('ticket/<uuid:pk_uuid>', TicketPdf.as_view()),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
@ -1,17 +1,25 @@
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from django.http import Http404
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
|
from django.utils import timezone
|
||||||
|
from django_weasyprint import WeasyTemplateView
|
||||||
from rest_framework.generics import get_object_or_404
|
from rest_framework.generics import get_object_or_404
|
||||||
|
from rest_framework.permissions import AllowAny
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from ApiBillet.serializers import EventSerializer, PriceSerializer, ProductSerializer, ReservationSerializer, \
|
from ApiBillet.serializers import EventSerializer, PriceSerializer, ProductSerializer, ReservationSerializer, \
|
||||||
ReservationValidator
|
ReservationValidator
|
||||||
from AuthBillet.models import TenantAdminPermission
|
from AuthBillet.models import TenantAdminPermission
|
||||||
from Customers.models import Client, Domain
|
from Customers.models import Client, Domain
|
||||||
from BaseBillet.models import Event, Price, Product, Reservation
|
from BaseBillet.models import Event, Price, Product, Reservation, Configuration, Ticket
|
||||||
from rest_framework import viewsets, permissions, status
|
from rest_framework import viewsets, permissions, status
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def new_tenants(schema_name):
|
def new_tenants(schema_name):
|
||||||
|
|
@ -130,7 +138,28 @@ class ReservationViewset(viewsets.ViewSet):
|
||||||
return Response(validator.data, status=status.HTTP_201_CREATED)
|
return Response(validator.data, status=status.HTTP_201_CREATED)
|
||||||
return Response(validator.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(validator.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
def get_permissions(self):
|
def get_permissions(self):
|
||||||
permission_classes = [TenantAdminPermission]
|
permission_classes = [TenantAdminPermission]
|
||||||
return [permission() for permission in permission_classes]
|
return [permission() for permission in permission_classes]
|
||||||
|
|
||||||
|
|
||||||
|
class TicketPdf(WeasyTemplateView):
|
||||||
|
permission_classes = [AllowAny]
|
||||||
|
template_name = 'ticket/ticket.html'
|
||||||
|
|
||||||
|
def get_context_data(self, pk_uuid, **kwargs):
|
||||||
|
logger.info(f"{timezone.now()} création de pdf demandé. uuid : {pk_uuid}")
|
||||||
|
self.config = Configuration.get_solo()
|
||||||
|
ticket: Ticket = get_object_or_404(Ticket, uuid=pk_uuid)
|
||||||
|
kwargs['ticket'] = ticket
|
||||||
|
kwargs['config'] = self.config
|
||||||
|
|
||||||
|
self.nom_prenom = f"{ticket.first_name.upper()}_{ticket.last_name.capitalize()}"
|
||||||
|
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def get_pdf_filename(self, **kwargs):
|
||||||
|
nom_prenom = self.nom_prenom
|
||||||
|
return f"Ticket_{nom_prenom}.pdf"
|
||||||
|
|
||||||
|
#
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import uuid
|
||||||
import requests
|
import requests
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models.aggregates import Sum
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
@ -314,8 +315,6 @@ class Event(models.Model):
|
||||||
verbose_name_plural = _('Evenements')
|
verbose_name_plural = _('Evenements')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Reservation(models.Model):
|
class Reservation(models.Model):
|
||||||
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True, db_index=True)
|
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True, db_index=True)
|
||||||
datetime = models.DateTimeField(auto_now=True)
|
datetime = models.DateTimeField(auto_now=True)
|
||||||
|
|
@ -337,6 +336,7 @@ class Reservation(models.Model):
|
||||||
status = models.CharField(max_length=3, choices=TYPE_CHOICES, default=UNPAID,
|
status = models.CharField(max_length=3, choices=TYPE_CHOICES, default=UNPAID,
|
||||||
verbose_name=_("Status de la réservation"))
|
verbose_name=_("Status de la réservation"))
|
||||||
|
|
||||||
|
mail_send = models.BooleanField(default=False)
|
||||||
# paiement = models.OneToOneField(Paiement_stripe, on_delete=models.PROTECT, blank=True, null=True,
|
# paiement = models.OneToOneField(Paiement_stripe, on_delete=models.PROTECT, blank=True, null=True,
|
||||||
# related_name='reservation')
|
# related_name='reservation')
|
||||||
|
|
||||||
|
|
@ -348,10 +348,30 @@ class Reservation(models.Model):
|
||||||
def user_mail(self):
|
def user_mail(self):
|
||||||
return self.user_commande.email
|
return self.user_commande.email
|
||||||
|
|
||||||
|
def paiements_paid(self):
|
||||||
|
return self.paiements.filter(
|
||||||
|
Q(status=Paiement_stripe.PAID) | Q(status=Paiement_stripe.VALID)
|
||||||
|
)
|
||||||
|
|
||||||
|
def articles_paid(self):
|
||||||
|
articles_paid = []
|
||||||
|
for paiement in self.paiements_paid():
|
||||||
|
for ligne in paiement.lignearticle_set.filter(
|
||||||
|
Q(status=LigneArticle.PAID) | Q(status=LigneArticle.VALID)
|
||||||
|
):
|
||||||
|
articles_paid.append(ligne)
|
||||||
|
return articles_paid
|
||||||
|
|
||||||
|
def total_paid(self):
|
||||||
|
total_paid = 0
|
||||||
|
for article in self.articles_paid():
|
||||||
|
article: LigneArticle
|
||||||
|
total_paid += article.price.prix * article.qty
|
||||||
|
return total_paid
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{str(self.uuid).partition('-')[0]} - {self.user_commande.email}"
|
return f"{str(self.uuid).partition('-')[0]} - {self.user_commande.email}"
|
||||||
|
|
||||||
|
|
||||||
# def total_billet(self):
|
# def total_billet(self):
|
||||||
# total = 0
|
# total = 0
|
||||||
# for ligne in self.paiements.all():
|
# for ligne in self.paiements.all():
|
||||||
|
|
@ -372,8 +392,6 @@ class Reservation(models.Model):
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Ticket(models.Model):
|
class Ticket(models.Model):
|
||||||
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True, db_index=True)
|
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True, db_index=True)
|
||||||
|
|
||||||
|
|
@ -410,7 +428,6 @@ class Ticket(models.Model):
|
||||||
ordering = ('-datetime',)
|
ordering = ('-datetime',)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Paiement_stripe(models.Model):
|
class Paiement_stripe(models.Model):
|
||||||
"""
|
"""
|
||||||
La commande
|
La commande
|
||||||
|
|
@ -437,7 +454,8 @@ class Paiement_stripe(models.Model):
|
||||||
(CANCELED, 'Annulée'),
|
(CANCELED, 'Annulée'),
|
||||||
)
|
)
|
||||||
|
|
||||||
reservation = models.ForeignKey(Reservation, on_delete=models.PROTECT, blank=True, null=True)
|
reservation = models.ForeignKey(Reservation, on_delete=models.PROTECT, blank=True, null=True,
|
||||||
|
related_name="paiements")
|
||||||
|
|
||||||
status = models.CharField(max_length=1, choices=STATUT_CHOICES, default=NON, verbose_name="Statut de la commande")
|
status = models.CharField(max_length=1, choices=STATUT_CHOICES, default=NON, verbose_name="Statut de la commande")
|
||||||
|
|
||||||
|
|
@ -446,7 +464,8 @@ class Paiement_stripe(models.Model):
|
||||||
(QRCODE, _('Depuis scan QR-Code')),
|
(QRCODE, _('Depuis scan QR-Code')),
|
||||||
(API_BILLETTERIE, _('Depuis billetterie')),
|
(API_BILLETTERIE, _('Depuis billetterie')),
|
||||||
)
|
)
|
||||||
source = models.CharField(max_length=1, choices=SOURCE_CHOICES, default=API_BILLETTERIE, verbose_name="Source de la commande")
|
source = models.CharField(max_length=1, choices=SOURCE_CHOICES, default=API_BILLETTERIE,
|
||||||
|
verbose_name="Source de la commande")
|
||||||
|
|
||||||
total = models.FloatField(default=0)
|
total = models.FloatField(default=0)
|
||||||
|
|
||||||
|
|
@ -461,8 +480,6 @@ class Paiement_stripe(models.Model):
|
||||||
[f"{ligne.product.name} {ligne.qty * ligne.product.prix}€" for ligne in self.lignearticle_set.all()])
|
[f"{ligne.product.name} {ligne.qty * ligne.product.prix}€" for ligne in self.lignearticle_set.all()])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class LigneArticle(models.Model):
|
class LigneArticle(models.Model):
|
||||||
uuid = models.UUIDField(primary_key=True, db_index=True, default=uuid.uuid4)
|
uuid = models.UUIDField(primary_key=True, db_index=True, default=uuid.uuid4)
|
||||||
datetime = models.DateTimeField(auto_now=True)
|
datetime = models.DateTimeField(auto_now=True)
|
||||||
|
|
@ -494,4 +511,3 @@ class LigneArticle(models.Model):
|
||||||
return self.paiement_stripe.status
|
return self.paiement_stripe.status
|
||||||
else:
|
else:
|
||||||
return _('no stripe send')
|
return _('no stripe send')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
import requests
|
import requests
|
||||||
|
from django.db import connection
|
||||||
|
from django.db.models import Q
|
||||||
from django.db.models.signals import post_save, pre_save
|
from django.db.models.signals import post_save, pre_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from ApiBillet.thread_mailer import ThreadMaileur
|
||||||
from BaseBillet.models import Reservation, LigneArticle, Ticket, Product, Configuration, Paiement_stripe
|
from BaseBillet.models import Reservation, LigneArticle, Ticket, Product, Configuration, Paiement_stripe
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
|
@ -20,21 +24,6 @@ def trigger_reservation(sender, instance: Reservation, created, **kwargs):
|
||||||
ticket.save()
|
ticket.save()
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_save, sender=LigneArticle)
|
|
||||||
def trigger_LigneArticle(sender, instance: LigneArticle, update_fields=None, **kwargs):
|
|
||||||
# if not created
|
|
||||||
if not instance._state.adding:
|
|
||||||
old_instance = sender.objects.get(pk=instance.pk)
|
|
||||||
new_instance = pre_save_signal_status(old_instance, instance)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_save, sender=Paiement_stripe)
|
|
||||||
def trigger_paiement_stripe(sender, instance: Paiement_stripe, update_fields=None, **kwargs):
|
|
||||||
# if not create
|
|
||||||
if not instance._state.adding:
|
|
||||||
old_instance = sender.objects.get(pk=instance.pk)
|
|
||||||
new_instance = pre_save_signal_status(old_instance, instance)
|
|
||||||
|
|
||||||
|
|
||||||
########################################################################
|
########################################################################
|
||||||
######################## SIGNAL PRE & POST SAVE ########################
|
######################## SIGNAL PRE & POST SAVE ########################
|
||||||
|
|
@ -136,6 +125,8 @@ def expire_paiement_stripe(old_instance, new_instance):
|
||||||
|
|
||||||
def valide_stripe_paiement(old_instance, new_instance):
|
def valide_stripe_paiement(old_instance, new_instance):
|
||||||
logger.info(f" TRIGGER PAIEMENT STRIPE valide_stripe_paiement {old_instance.status} to {new_instance.status}")
|
logger.info(f" TRIGGER PAIEMENT STRIPE valide_stripe_paiement {old_instance.status} to {new_instance.status}")
|
||||||
|
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -143,8 +134,28 @@ def valide_stripe_paiement(old_instance, new_instance):
|
||||||
|
|
||||||
|
|
||||||
def send_billet_to_mail(old_instance, new_instance):
|
def send_billet_to_mail(old_instance, new_instance):
|
||||||
logger.info(f" TRIGGER RESERVATION send_billet_to_mail {old_instance.status} to {new_instance.status}")
|
if not new_instance.mail_send :
|
||||||
pass
|
logger.info(f" TRIGGER RESERVATION send_billet_to_mail {old_instance.status} to {new_instance.status}")
|
||||||
|
new_instance : Reservation
|
||||||
|
config = Configuration.get_solo()
|
||||||
|
|
||||||
|
if new_instance.user_commande.email:
|
||||||
|
try:
|
||||||
|
mail = ThreadMaileur(
|
||||||
|
new_instance.user_commande.email,
|
||||||
|
f"Votre reservation pour {config.organisation}",
|
||||||
|
template='mails/buy_confirmation.html',
|
||||||
|
context={
|
||||||
|
'config': config,
|
||||||
|
'reservation': new_instance,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
# import ipdb; ipdb.set_trace()
|
||||||
|
mail.send_with_tread()
|
||||||
|
except Exception as e :
|
||||||
|
logger.error(f"{timezone.now()} Erreur envoie de mail pour reservation {new_instance} : {e}")
|
||||||
|
else :
|
||||||
|
logger.info(f" TRIGGER RESERVATION mail déja envoyé {new_instance} : {new_instance.mail_send} - status : {old_instance.status} to {new_instance.status}")
|
||||||
|
|
||||||
|
|
||||||
######################## MOTEUR TRIGGER ########################
|
######################## MOTEUR TRIGGER ########################
|
||||||
|
|
@ -165,7 +176,8 @@ TRANSITIONS = {
|
||||||
Reservation.PAID: send_billet_to_mail
|
Reservation.PAID: send_billet_to_mail
|
||||||
},
|
},
|
||||||
Reservation.PAID: {
|
Reservation.PAID: {
|
||||||
LigneArticle.PAID: send_billet_to_mail,
|
Reservation.VALID: send_billet_to_mail,
|
||||||
|
Reservation.PAID: send_billet_to_mail,
|
||||||
'_else_': error_regression,
|
'_else_': error_regression,
|
||||||
},
|
},
|
||||||
Reservation.VALID: {
|
Reservation.VALID: {
|
||||||
|
|
@ -202,21 +214,26 @@ TRANSITIONS = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@receiver(pre_save)
|
||||||
|
def pre_save_signal_status(sender, instance, **kwargs):
|
||||||
|
# if not create
|
||||||
|
if not instance._state.adding:
|
||||||
|
sender_str = sender.__name__.upper()
|
||||||
|
dict_transition = TRANSITIONS.get(sender_str)
|
||||||
|
if dict_transition:
|
||||||
|
old_instance = sender.objects.get(pk=instance.pk)
|
||||||
|
new_instance = instance
|
||||||
|
|
||||||
def pre_save_signal_status(old_instance, new_instance):
|
logger.info(f"dict_transition {sender_str} {new_instance} : {old_instance.status} to {new_instance.status}")
|
||||||
sender_str = old_instance.__class__.__name__.upper()
|
transitions = dict_transition.get(old_instance.status, None)
|
||||||
dict_transition = TRANSITIONS.get(sender_str)
|
if transitions:
|
||||||
if dict_transition:
|
# Par ordre de préférence :
|
||||||
logger.info(f"dict_transition {sender_str} {new_instance} : {old_instance.status} to {new_instance.status}")
|
trigger_function = transitions.get('_all_', (
|
||||||
transitions = dict_transition.get(old_instance.status, None)
|
transitions.get(new_instance.status, (
|
||||||
if transitions:
|
transitions.get('_else_', None)
|
||||||
# Par ordre de préférence :
|
))))
|
||||||
trigger_function = transitions.get('_all_', (
|
|
||||||
transitions.get(new_instance.status, (
|
|
||||||
transitions.get('_else_', None)
|
|
||||||
))))
|
|
||||||
|
|
||||||
if trigger_function:
|
if trigger_function:
|
||||||
if not callable(trigger_function):
|
if not callable(trigger_function):
|
||||||
raise Exception(f'Fonction {trigger_function} is not callable. Disdonc !?')
|
raise Exception(f'Fonction {trigger_function} is not callable. Disdonc !?')
|
||||||
trigger_function(old_instance, new_instance)
|
trigger_function(old_instance, new_instance)
|
||||||
|
|
|
||||||
|
|
@ -229,11 +229,12 @@ class retour_stripe(View):
|
||||||
return HttpResponseRedirect(f"/qr/{ligne_article.carte.uuid}#erreurpaiement")
|
return HttpResponseRedirect(f"/qr/{ligne_article.carte.uuid}#erreurpaiement")
|
||||||
|
|
||||||
elif paiement_stripe.source == Paiement_stripe.API_BILLETTERIE :
|
elif paiement_stripe.source == Paiement_stripe.API_BILLETTERIE :
|
||||||
return HttpResponse(
|
if paiement_stripe.status == Paiement_stripe.VALID :
|
||||||
'Coucou')
|
return HttpResponse(
|
||||||
|
'Coucou')
|
||||||
|
|
||||||
else :
|
|
||||||
raise Http404('paiement_stripe.source ?')
|
raise Http404(f'{paiement_stripe.status}')
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
|
||||||
|
|
@ -61,11 +61,17 @@ RUN pip install django-stdimage
|
||||||
RUN pip install stripe
|
RUN pip install stripe
|
||||||
|
|
||||||
|
|
||||||
|
RUN apt-get install -y fonts-font-awesome
|
||||||
|
RUN apt-get install -y libffi-dev
|
||||||
|
RUN apt-get install -y libgdk-pixbuf2.0-0
|
||||||
|
RUN apt-get install -y libpango1.0-0
|
||||||
|
RUN apt-get install -y python-dev
|
||||||
|
# RUN apt-get install -y python-lxml
|
||||||
|
RUN apt-get install -y shared-mime-info
|
||||||
|
RUN apt-get install -y libcairo2
|
||||||
|
RUN pip install django-weasyprint
|
||||||
|
|
||||||
|
RUN apt-get -y clean
|
||||||
|
|
||||||
|
|
||||||
RUN python --version
|
RUN python --version
|
||||||
RUN django-admin --version
|
RUN django-admin --version
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue