浏览代码

implement licence cmf export basis (until twig rendering)

Olivier Massot 3 年之前
父节点
当前提交
d21d1950b6

+ 2 - 1
config/packages/twig.yaml

@@ -1,2 +1,3 @@
 twig:
-    default_path: '%kernel.project_dir%/templates'
+    paths:
+        '%kernel.project_dir%/templates': templates

+ 8 - 1
config/routes.yaml

@@ -4,4 +4,11 @@ login_check:
 
 swagger_ui:
   path: /docs
-  controller: api_platform.swagger.action.ui
+  controller: api_platform.swagger.action.ui
+
+ot_internal_secure_file_download:
+  path: /_internal/secure/files/{id}
+  controller: App\Controller\FileController::downloadFile
+  methods: GET
+  requirements:
+    id: '\d+'

+ 0 - 0
doc/exports.md


二进制
public/static/ciseaux.png


二进制
public/static/cmf-reseau.png


二进制
public/static/cmf_licence.png


二进制
public/static/footer_report_activity.jpg


二进制
public/static/header_report_activity.jpg


二进制
public/static/logo_welcome.png


二进制
public/static/picto_face.png


+ 40 - 0
src/ApiResources/Export/ExportRequest.php

@@ -0,0 +1,40 @@
+<?php
+declare(strict_types=1);
+
+namespace App\ApiResources\Export;
+
+use ApiPlatform\Core\Annotation\ApiProperty;
+use ApiPlatform\Core\Annotation\ApiResource;
+use Symfony\Component\Validator\Constraints as Assert;
+
+/**
+ * Demande d'export d'un fichier
+ * -- C'est la classe de base des ressources de type ExportRequest --
+ */
+abstract class ExportRequest
+{
+    const SUPPORTED_FORMATS = ['pdf', 'xlsx', 'txt', 'csv'];
+
+    /**
+     * Format de sortie attendu (pdf, txt...)
+     * @var string
+     */
+    #[Assert\Choice(self::SUPPORTED_FORMATS)]
+    protected string $format;
+
+    /**
+     * @return string
+     */
+    public function getFormat(): string
+    {
+        return $this->format;
+    }
+
+    /**
+     * @param string $format
+     */
+    public function setFormat(string $format): void
+    {
+        $this->format = $format;
+    }
+}

+ 60 - 0
src/ApiResources/Export/LicenceCmf/LicenceCmfOrganizationER.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace App\ApiResources\Export\LicenceCmf;
+
+use ApiPlatform\Core\Annotation\ApiProperty;
+use ApiPlatform\Core\Annotation\ApiResource;
+use App\ApiResources\Export\ExportRequest;
+use Symfony\Component\Validator\Constraints as Assert;
+
+/**
+ * Requête d'export d'une licence CMF pour l'organization ciblée
+ *
+ * Appeler avec une requête POST à /export/licencecmf/organization,
+ * avec un body comme :
+ *
+ *   {"organizationId" : 1}
+ */
+#[ApiResource(
+    collectionOperations: [
+        'post' => [
+            'method' => 'POST',
+            'path' => '/licencecmf/organization'
+        ],
+    ],
+    routePrefix: '/export'
+)]
+class LicenceCmfOrganizationER extends ExportRequest
+{
+    const SUPPORTED_FORMATS = ['pdf'];
+
+    /**
+     * Id de l'organization pour laquelle on exporte la licence
+     * @var int | null
+     */
+    #[ApiProperty(identifier: true)]
+    protected ?int $organizationId = null;
+
+    /**
+     * Format de sortie attendu (pdf seulement ici)
+     * @var string
+     */
+    #[Assert\Choice(self::SUPPORTED_FORMATS)]
+    protected string $format = 'pdf';
+
+    /**
+     * @return int|null
+     */
+    public function getOrganizationId(): ?int
+    {
+        return $this->organizationId;
+    }
+
+    /**
+     * @param int|null $organizationId
+     */
+    public function setOrganizationId(?int $organizationId): void
+    {
+        $this->organizationId = $organizationId;
+    }
+}

+ 37 - 0
src/DataPersister/Export/LicenceCmf/LicenceCmfOrganizationERDataPersister.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace App\DataPersister\Export\LicenceCmf;
+
+use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
+use App\ApiResources\Export\LicenceCmf\LicenceCmfOrganizationER;
+use App\Service\Export\LicenceCmfExporter;
+use HttpRequestException;
+
+class LicenceCmfOrganizationERDataPersister implements ContextAwareDataPersisterInterface
+{
+    public function __construct(
+        private LicenceCmfExporter $licenceCmfExporter,
+    ) {}
+
+    public function supports($data, array $context = []): bool
+    {
+        return $data instanceof LicenceCmfOrganizationER;
+    }
+
+    /**
+     * @param $licenceCmfExportRequest LicenceCmfOrganizationER Une requête d'export d'une LicenceCmfOrganization
+     * @param array $context
+     */
+    public function persist($licenceCmfExportRequest, array $context = [])
+    {
+        $this->licenceCmfExporter->export($licenceCmfExportRequest);
+    }
+
+    /**
+     * @throws HttpRequestException
+     */
+    public function remove($data, array $context = [])
+    {
+        throw new HttpRequestException('not implemented error', 500);
+    }
+}

+ 8 - 0
src/DataProvider/Export/LicenceCmfDataProvider.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace App\DataProvider\Export;
+
+class LicenceCmfDataProvider
+{
+
+}

+ 292 - 0
src/Entity/Export/LicenceCmf.php

@@ -0,0 +1,292 @@
+<?php
+
+namespace App\Entity\Export;
+
+class LicenceCmf
+{
+    /**
+     * An id for the licence
+     * @var int
+     */
+    private int $id;
+
+    /**
+     * Year of the licence
+     * @var int
+     */
+    private int $year;
+
+    /**
+     * Name of the organization
+     * @var string
+     */
+    private string $organizationName;
+
+    /**
+     * Identifier of the organization
+     * @var string
+     */
+    private string $organizationIdentifier;
+
+    /**
+     * Name of the federation
+     * @var string|null
+     */
+    private string $federationName;
+
+    /**
+     * Color of the licence card for the given year
+     * @var string
+     */
+    private string $color;
+
+    /**
+     * URI of the organization's logo
+     * @var string | null
+     */
+    private ?string $logoUri;
+
+    /**
+     * URI of the CMF QrCode
+     * @var string | null
+     */
+    private ?string $qrCodeUri;
+
+    /**
+     * Gender of the licence owner
+     * @var int
+     */
+    private int $personId;
+
+    /**
+     * Gender of the licence owner
+     * @var string
+     */
+    private string $personGender;
+
+    /**
+     * First name of the licence owner
+     * @var string
+     */
+    private string $personFirstName;
+
+    /**
+     * Name of the licence owner
+     * @var string
+     */
+    private string $personLastName;
+
+    /**
+     * Avatar of the person
+     * @var string|null
+     */
+    private ?string $personAvatarUri;
+
+    /**
+     * @return int
+     */
+    public function getId(): int
+    {
+        return $this->id;
+    }
+
+    /**
+     * @param int $id
+     */
+    public function setId(int $id): void
+    {
+        $this->id = $id;
+    }
+
+    /**
+     * @return int
+     */
+    public function getYear(): int
+    {
+        return $this->year;
+    }
+
+    /**
+     * @param int $year
+     */
+    public function setYear(int $year): void
+    {
+        $this->year = $year;
+    }
+
+    /**
+     * @return string
+     */
+    public function getOrganizationName(): string
+    {
+        return $this->organizationName;
+    }
+
+    /**
+     * @param string $organizationName
+     */
+    public function setOrganizationName(string $organizationName): void
+    {
+        $this->organizationName = $organizationName;
+    }
+
+    /**
+     * @return string
+     */
+    public function getOrganizationIdentifier(): string
+    {
+        return $this->organizationIdentifier;
+    }
+
+    /**
+     * @param string|null $organizationIdentifier
+     */
+    public function setOrganizationIdentifier(?string $organizationIdentifier): void
+    {
+        $this->organizationIdentifier = $organizationIdentifier;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getFederationName(): ?string
+    {
+        return $this->federationName;
+    }
+
+    /**
+     * @param string $federationName
+     */
+    public function setFederationName(string $federationName): void
+    {
+        $this->federationName = $federationName;
+    }
+
+    /**
+     * @return string
+     */
+    public function getColor(): string
+    {
+        return $this->color;
+    }
+
+    /**
+     * @param string $color
+     */
+    public function setColor(string $color): void
+    {
+        $this->color = $color;
+    }
+
+    /**
+     * @return string
+     */
+    public function getLogoUri(): string
+    {
+        return $this->logoUri;
+    }
+
+    /**
+     * @param string $logoUri
+     */
+    public function setLogoUri(string $logoUri): void
+    {
+        $this->logoUri = $logoUri;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getQrCodeUri(): ?string
+    {
+        return $this->qrCodeUri;
+    }
+
+    /**
+     * @param string|null $qrCodeUri
+     */
+    public function setQrCodeUri(?string $qrCodeUri): void
+    {
+        $this->qrCodeUri = $qrCodeUri;
+    }
+
+    /**
+     * @return int
+     */
+    public function getPersonId(): int
+    {
+        return $this->personId;
+    }
+
+    /**
+     * @param int $personId
+     */
+    public function setPersonId(int $personId): void
+    {
+        $this->personId = $personId;
+    }
+
+    /**
+     * @return string
+     */
+    public function getPersonGender(): string
+    {
+        return $this->personGender;
+    }
+
+    /**
+     * @param string $personGender
+     */
+    public function setPersonGender(string $personGender): void
+    {
+        $this->personGender = $personGender;
+    }
+
+    /**
+     * @return string
+     */
+    public function getPersonFirstName(): string
+    {
+        return $this->personFirstName;
+    }
+
+    /**
+     * @param string $personFirstName
+     */
+    public function setPersonFirstName(string $personFirstName): void
+    {
+        $this->personFirstName = $personFirstName;
+    }
+
+    /**
+     * @return string
+     */
+    public function getPersonLastName(): string
+    {
+        return $this->personLastName;
+    }
+
+    /**
+     * @param string $personLastName
+     */
+    public function setPersonLastName(string $personLastName): void
+    {
+        $this->personLastName = $personLastName;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getPersonAvatarUri(): ?string
+    {
+        return $this->personAvatarUri;
+    }
+
+    /**
+     * @param string|null $personAvatarUri
+     */
+    public function setPersonAvatarUri(?string $personAvatarUri): void
+    {
+        $this->personAvatarUri = $personAvatarUri;
+    }
+}

+ 20 - 0
src/Entity/Organization/Organization.php

@@ -7,6 +7,7 @@ use ApiPlatform\Core\Annotation\ApiResource;
 use ApiPlatform\Core\Annotation\ApiSubresource;
 use App\Entity\Core\BankAccount;
 use App\Entity\Core\ContactPoint;
+use App\Entity\Core\File;
 use App\Entity\Network\NetworkOrganization;
 use App\Repository\Organization\OrganizationRepository;
 use Doctrine\Common\Collections\ArrayCollection;
@@ -63,6 +64,9 @@ class Organization
     #[ORM\JoinColumn(nullable: false)]
     private Parameters $parameters;
 
+    #[ORM\OneToOne(cascade: ['persist', 'remove'])]
+    private File $logo;
+
     #[ORM\Column(length: 255, nullable: true)]
     private ?string $description = null;
 
@@ -328,6 +332,22 @@ class Organization
         return $this;
     }
 
+    /**
+     * @return File
+     */
+    public function getLogo(): File
+    {
+        return $this->logo;
+    }
+
+    /**
+     * @param File $logo
+     */
+    public function setLogo(File $logo): void
+    {
+        $this->logo = $logo;
+    }
+
     public function getDescription(): ?string
     {
         return $this->description;

+ 20 - 0
src/Entity/Organization/Parameters.php

@@ -4,6 +4,7 @@ declare(strict_types=1);
 namespace App\Entity\Organization;
 
 use ApiPlatform\Core\Annotation\ApiResource;
+use App\Entity\Core\File;
 use App\Repository\Organization\ParametersRepository;
 use Doctrine\ORM\Mapping as ORM;
 use Symfony\Component\Validator\Constraints as Assert;
@@ -112,6 +113,9 @@ class Parameters
     #[Assert\Choice(callback: ['\App\Enum\Organization\PeriodicityEnum', 'toArray'], message: 'invalid-periodicity')]
     private ?string $educationPeriodicity = null;
 
+    #[ORM\OneToOne(cascade: ['persist', 'remove'])]
+    private File $qrCode;
+
     public function getId(): ?int
     {
         return $this->id;
@@ -476,4 +480,20 @@ class Parameters
 
         return $this;
     }
+
+    /**
+     * @return File
+     */
+    public function getQrCode(): File
+    {
+        return $this->qrCode;
+    }
+
+    /**
+     * @param File $qrCode
+     */
+    public function setQrCode(File $qrCode): void
+    {
+        $this->qrCode = $qrCode;
+    }
 }

+ 35 - 3
src/Repository/Access/AccessRepository.php

@@ -5,6 +5,8 @@ namespace App\Repository\Access;
 
 use App\DQL\DateConditions;
 use App\Entity\Access\Access;
+use App\Entity\Organization\Organization;
+use DateTime;
 use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
 use Doctrine\Persistence\ManagerRegistry;
 use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface;
@@ -72,11 +74,11 @@ class AccessRepository extends ServiceEntityRepository implements UserLoaderInte
     }
 
     /**
-     * @param Access $acces
+     * @param Access $access
      * @return mixed
      * @throws \Exception
      */
-    public function findAllValidAccesses(Access $acces): array
+    public function findAllValidAccesses(Access $access): array
     {
         $datetime = new \DateTime();
         $today = $datetime->format('Y-m-d');
@@ -86,7 +88,7 @@ class AccessRepository extends ServiceEntityRepository implements UserLoaderInte
             ->innerJoin('organization.networkOrganizations', 'networkOrganizations')
             ->where('access.person = :person')
             ->andWhere('networkOrganizations.startDate <= :today')
-            ->setParameter('person', $acces->getPerson())
+            ->setParameter('person', $access->getPerson())
             ->setParameter('today', $today)
             ->getQuery()
             ->getResult()
@@ -114,4 +116,34 @@ class AccessRepository extends ServiceEntityRepository implements UserLoaderInte
 
         return $result;
     }
+
+    /**
+     * Retourne tous les accesses de l'organization ayant la fonction donnée à la date donnée
+     *
+     * @param Organization $organization
+     * @param $function
+     * @param DateTime|null $date
+     * @return array
+     */
+    public function findByOrganizationAndMission(Organization $organization, $function, \DateTime $date = null): array
+    {
+        if ($date === null)
+            $date = new DateTime();
+
+        $this->_em->getFilters()->disable('date_time_filter');
+
+        $qb = $this->createQueryBuilder('access');
+        $qb
+            ->innerJoin('access.organizationFunction', 'organization_function')
+            ->innerJoin('organization_function.functionType', 'function_type')
+            ->where('function_type.mission = :mission')
+            ->andWhere('access.organization = :id')
+            ->setParameter('id', $organization->getId())
+            ->setParameter('mission', $function)
+        ;
+        DateConditions::addDateInPeriodCondition($qb, 'organization_function', $date->format('Y-m-d'));
+        $this->_em->getFilters()->enable('date_time_filter');
+
+        return $qb->getQuery()->getResult();
+    }
 }

+ 10 - 0
src/Service/Export/BaseExporter.php

@@ -0,0 +1,10 @@
+<?php
+
+namespace App\Service\Export;
+
+use App\ApiResources\Export\ExportRequest;
+
+abstract class BaseExporter
+{
+    protected function export($exportRequest) {}
+}

+ 101 - 0
src/Service/Export/LicenceCmfExporter.php

@@ -0,0 +1,101 @@
+<?php
+
+namespace App\Service\Export;
+
+
+use App\Entity\Export\LicenceCmf;
+use App\Enum\Access\FunctionEnum;
+use App\Repository\Access\AccessRepository;
+use App\Repository\Organization\OrganizationRepository;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+use Twig\Environment;
+
+/**
+ * Exporte la licence CMF de la structure ou du ou des access, au format demandé
+ */
+class LicenceCmfExporter extends BaseExporter
+{
+    const CMF_ID = 12097;
+
+    /**
+     * La couleur de la carte de licence change chaque année, de manière cyclique
+     */
+    const LICENCE_CMF_COLOR_START_YEAR = "2020";
+    const LICENCE_CMF_COLOR = [0 => '931572', 1 => 'C2981A', 2 =>  '003882', 3 =>  '27AAE1', 4 =>  '2BB673'];
+
+    public function __construct(
+        private OrganizationRepository $organizationRepository,
+        private AccessRepository $accessRepository,
+        private Environment $twig,
+        private UrlGeneratorInterface $router,
+    )
+    {}
+
+    /**
+     *
+     */
+    public function export($exportRequest)
+    {
+        $organization = $this->organizationRepository->find($exportRequest->getOrganizationId());
+        $currentYear = date('Y');
+
+        $model = new LicenceCmf();
+        $model->setId($organization->getId());
+        $model->setYear($currentYear);
+        $model->setOrganizationName($organization->getName());
+        $model->setOrganizationIdentifier($organization->getIdentifier());
+
+//        $parentNetwork = $organization->getParent()
+        $model->setFederationName('');
+
+        $model->setColor(
+            $this->getLicenceColor($currentYear)
+        );
+
+        $logoId = $organization->getLogo()?->getId();
+        if ($logoId) {
+            $model->setLogoUri(
+                $this->router->generate('ot_internal_secure_file_download', array('id' => $logoId))
+            );
+        }
+
+        $presidents = $this->accessRepository->findByOrganizationAndMission($organization, FunctionEnum::PRESIDENT()->getValue());
+        if (count($presidents) > 0) {
+            $president = $presidents[0]->getPerson();
+            $model->setPersonId($president->getId());
+            $model->setPersonGender($president->getGender());
+            $model->setPersonFirstName($president->getGivenName());
+            $model->setPersonLastName($president->getName());
+        }
+
+        $cmf = $this->organizationRepository->find(self::CMF_ID);
+        $qrCodeId = $cmf->getParameters()?->getQrCode()?->getId();
+        if ($qrCodeId) {
+            $model->setQrCodeUri(
+                $this->router->generate('ot_internal_secure_file_download', array('id' => $qrCodeId))
+            );
+        }
+
+        $html = $this->twig->render(
+            '@templates/export/licence_cmf/licence_cmf_organization.html.twig',
+            [
+                'models' => [$model],
+                'isOrganizationLicence' => true
+            ]
+        );
+
+        var_dump($html); die;
+    }
+
+    /**
+     * Retourne la couleur de licence pour l'année donnée
+     * @param int $year
+     * @return string
+     */
+    protected function getLicenceColor(int $year): string {
+        if (!self::LICENCE_CMF_COLOR_START_YEAR > $year) {
+            return self::LICENCE_CMF_COLOR[0];
+        }
+        return self::LICENCE_CMF_COLOR[($year - self::LICENCE_CMF_COLOR_START_YEAR) % count(self::LICENCE_CMF_COLOR)];
+    }
+}

+ 348 - 0
templates/export/licence_cmf/licence_cmf_organization.html.twig

@@ -0,0 +1,348 @@
+<!DOCTYPE html>
+<html lang="fr">
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+    <style media="all">
+
+        {% block style %}
+        html {
+            width: 21cm;
+            height: 100%;
+        }
+
+        @page {
+            margin: 180px 50px;
+        }
+
+        body {
+            height: 100%;
+        }
+
+        .Style1 {
+            font-family: Verdana, Arial, Helvetica, sans-serif;
+            font-size: 12px;
+            font-weight: bold;
+            color: #FFFFFF;
+        }
+
+        .Style2 {
+            font-family: Verdana, Arial, Helvetica, sans-serif;
+            font-size: 12px;
+            font-weight: bold;
+        }
+
+        .Style3 {
+            font-family: Verdana, Arial, Helvetica, sans-serif;
+            font-size: 8px;
+            color: #FFFFFF;
+            vertical-align: text-top;
+        }
+
+        .Style4 {
+            font-family: Verdana, Arial, Helvetica, sans-serif;
+            font-size: 9px;
+        }
+
+        .Style5 {
+            font-family: Verdana, Arial, Helvetica, sans-serif;
+            font-size: 10px;
+        }
+
+        .Style7 {
+            font-family: Verdana, Arial, Helvetica, sans-serif;
+            font-size: 16px;
+        }
+
+        .Style8 {
+            font-family: Verdana, Arial, Helvetica, sans-serif;
+            font-size: 12px;
+        }
+
+        .Style9 {
+            font-family: Verdana, Arial, Helvetica, sans-serif;
+            font-size: 12px;
+            font-weight: bold;
+        }
+
+        .relative {
+            position: relative;
+        }
+
+        #year_head {
+            position: absolute;
+            bottom: 10px;
+            left: 100px;
+            color: #9d1348;
+            font-size: 25px;
+            font-weight: bold;
+        }
+
+        .avatar {
+            max-width: 85px;
+            max-height: 82px;
+        }
+
+        #year_card {
+            position: absolute;
+            bottom: 75px;
+            left: 55px;
+            color: #9d1348;
+            font-size: 14px;
+            font-weight: bold;
+        }
+
+        .Style2 p, .Style4 p {
+            margin: 0;
+            padding: 0;
+        }
+
+        .page_break {
+            page-break-before: always;
+            top: 0 !important;
+            margin-top: 0 !important;
+            position: initial;
+        }
+
+        #card {
+            position: relative;
+            z-index: 1;
+            top: 0;
+            left: 0;
+            margin: 50px auto;
+        }
+
+        .card_dimension {
+            width: 110mm;
+            height: 72mm;
+        }
+
+        #dashed {
+            width: 106mm;
+            height: 66mm;
+            border: 1px dashed #0f0f0f;
+            position: absolute;
+            border-radius: 4px;
+            top: 2mm;
+            left: 2mm;
+            z-index: 2;
+        }
+
+        #scissor{
+            position: absolute;
+            top: 0;
+            left: 15px;
+        }
+
+        .up {
+            padding-top: 100px;
+        }
+
+        .bottom {
+            padding-top: 20px;
+        }
+
+        #qrCode {
+            padding-right: 15px;
+        }
+
+        #avatar{
+            padding-left: 5px;
+        }
+
+        {% endblock style %}
+    </style>
+
+    <link
+            href=
+            "https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
+            rel="stylesheet"
+            integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN"
+            crossorigin="anonymous"
+    />
+
+    <title>Licence CMF</title>
+</head>
+
+<body>
+
+{% block content %}
+    {% for model in models %}
+        <page data-iri="{{ model.id }}">
+            <table width="793" border="0" cellspacing="0" cellpadding="0">
+                <tbody>
+                <tr>
+                    <td width="693">
+                        <table width="680" border="0" align="center" cellpadding="0" cellspacing="0">
+                            <tbody>
+                            <tr>
+                                <td width="340" class="relative">
+                                    <img src="{{ asset('public/static/cmf_licence.png') }}"
+                                            width="170" height="86"/>
+                                    <span id="year_head">{{ model.year }}</span>
+                                </td>
+                                <td width="340">
+                                    <div align="right">
+                                        <img src="{{ asset('public/static/cmf-reseau.png') }}"
+                                                width="200" height="86"/>
+                                    </div>
+                                </td>
+                            </tr>
+                            </tbody>
+                        </table>
+                    </td>
+                </tr>
+                <tr>
+                    <td><p class="Style7"></p>
+                        <p class="Style7"></p>
+                        <p class="Style7">
+                            {{ (model.personGender ~ '_long')| trans }} {{ model.personLastName }} {{ model.personFirstName }}
+                            ,</p>
+
+                        <p class="Style7"></p>
+                        <p class="Style8">Vous trouverez ci-joint votre <strong>Licence CMF pour
+                                l’année {{ model.year }}</strong>.
+                        </p>
+                        <p class="Style8">Vous pouvez : </p>
+                        <ul class="Style8">
+                            <li> Imprimer ce document tel quel et le garder au format A4.</li>
+                            <li> Imprimer ce document sur du papier cartonné et le découper selon les pointillés.</li>
+                            <li> Enregister le PDF pour garder ce document au format numérique.</li>
+                        </ul>
+                        <p class="Style8">Cette licence et le numéro associé vous sera utile pour bénéficier des
+                            avantages partenaires mis en place par la CMF.
+                            Pour connaître les partenariats existants, rendez-vous sur [
+                            <a href="https://www.cmf-musique.org/services/tarifs-preferentiels/">
+                                www.cmf-musique.org/services/tarifs-preferentiels/
+                            </a>]
+                        </p>
+                        <p class="Style8">
+                            Nous vous rappelons que la Licence CMF, personnelle et unique, <br/>
+                            est <strong>valable jusqu’au 31 décembre de l’année {{ model.year }}</strong>.
+                        </p>
+                        <p class="Style8"></p>
+                        <hr/>
+                    </td>
+                </tr>
+                </tbody>
+            </table>
+
+            <div id="card" class="card_dimension">
+                <table class="card_dimension" align="center" cellpadding="0" cellspacing="0">
+                    <tbody>
+                    <tr>
+                        <td height="26" colspan="3" align="center" valign="bottom" bgcolor="{{ model.color }}">
+                            <div align="center"><span class="Style1">Licence CMF</span></div>
+                        </td>
+                    </tr>
+                    <tr class="up">
+                        {% if isOrganizationLicence %}
+                            <td width="80" id="avatar">
+                                <div align="center">
+                                    {% if(model.logoUri is null) %}
+                                        <img src="{{ asset('public/static/picto_face.png') }}"
+                                             width="85"
+                                             height="82"/>
+                                    {% else %}
+                                        <img src="{{ asset(model.logoUri) }}"
+                                             width="85"
+                                             height="82"/>
+                                    {% endif %}
+                                </div>
+                            </td>
+                            <td colspan="2">
+                                <span class="Style2">
+                                    <p>{{ model.personLastName }} {{ model.personFirstName }}</p>
+                                    <p>{{ model.organizationName }}</p>
+                                    <p>{{ model.federationName }}</p>
+                                    <p>N° : {{ model.organizationIdentifier }}</p>
+                                    <p>Licence valable jusqu’au 31/12/{{ model.year }}</p>
+                                </span>
+                            </td>
+                        {% else %}
+                            <td width="80" id="avatar">
+                                <div align="center">
+                                    {% if(model.personAvatarUri is null) %}
+                                        <img
+                                                src="{{ asset('public/static/picto_face.png') }}"
+                                                width="85"
+                                                height="82"/>
+                                    {% else %}
+                                        <img class="avatar"
+                                             src="{{ asset(model.personAvatarUri) }}"/>
+                                    {% endif %}
+                                </div>
+                            </td>
+                            <td colspan="2">
+                                <span class="Style2">
+                                   <p>{{ model.personLastName }} {{ model.personFirstName }}</p>
+                                   <p>{{ model.organizationName }}</p>
+                                   <p>?</p>
+                                  <p>N° : {{ model.organizationIdentifier }}-{{ model.personId }}</p>
+                                <p>Licence valable jusqu’au 31/12/{{ model.year }}</p>
+                                </span>
+                            </td>
+                        {% endif %}
+                    </tr>
+
+                    <tr class="bottom">
+                        <td width="70" valign="middle"
+                            style="vertical-align: top;">
+                            <div align="center">
+                                <img src="{{ asset('public/static/cmf_licence.png') }}"
+                                     height="45"/>
+                                <span id="year_card">{{ model.year }}</span>
+                            </div>
+                        </td>
+                        <td width="140" align="right" valign="middle">
+                            <div align="right"><span class="Style4">
+                                <p>Consultez vos</p>
+                                <p> avantages sur</p>
+                                <p>www.cmf-musique.org</p>
+                                <p> ou flashez ce code</p></span>
+                            </div>
+                        </td>
+                        <td width="70" align="right" valign="middle" id="qrCode">
+                            {% if(model.qrCodeUri is not null) %}
+                                <img style="margin-right: 10px;"
+                                     src="{{ asset(model.qrCodeUri) }}"
+                                     alt=""
+                                     width="65" height="65"/>
+                            {% endif %}
+                        </td>
+                    </tr>
+
+                    <tr>
+                        <td colspan="3" align="center" bgcolor="{{ model.color }}"><span class="Style3">CMF ● cmf@cmf-musique.org ● 01 55 58 22 82 ● www.cmf-musique.org</span>
+                        </td>
+                    </tr>
+
+                    </tbody>
+                </table>
+
+                <div id="dashed"></div>
+                <i class="fa fa-scissors" id="scissor" aria-hidden="true"></i>
+            </div>
+
+
+            <table width="793" border="0" cellspacing="0" cellpadding="0">
+                <tbody>
+                <tr>
+                    <td></td>
+                    <td>
+                        <hr/>
+                        <p align="center" class="Style9">Vous rencontrez des difficultés pour utiliser cette carte ?</p>
+                        <p align="center" class="Style8">Contactez nous : <br/>
+                            CMF<br/>
+                            cmf@cmf-musique.org<br/>
+                            01 55 58 22 82 </p>
+                    </td>
+                    <td></td>
+                </tr>
+                </tbody>
+            </table>
+            <div class="page_break"></div>
+        </page>
+    {% endfor %}
+{% endblock content %}
+</body>
+</html>