``🗑️ chore: Supprime les contrôleurs et templates obsolètes du newsletter

Ce commit supprime les fichiers obsolètes liés à la gestion des
newsletters. Ces fichiers ne sont plus utilisés et leur suppression
simplifie le code base. Les fichiers supprimés incluent des
contrôleurs, des templates Twig et des classes JavaScript.
This commit is contained in:
Serreau Jovann
2025-09-24 14:23:23 +02:00
parent 6d0b8e67ef
commit 636e273e14
31 changed files with 1568 additions and 2865 deletions

1
.env
View File

@@ -72,3 +72,4 @@ AMAZON_SES_SECRET=BD63dADmgFJJPnjlT9utRDlvcOh8pRH3eOZXsyhNL/F3
# MAILER_DSN=ses://ACCESS_KEY:SECRET_KEY@default?region=eu-west-1
# MAILER_DSN=ses+smtp://ACCESS_KEY:SECRET_KEY@default?region=eu-west-1
###< symfony/amazon-mailer ###
CLOUDFLARE_TOKEN=4mqx9d7ynvoeCaXonJA07U19rH8gGhctqp7j2Lch

1
.gitignore vendored
View File

@@ -32,3 +32,4 @@ phpstan.neon
coverage/
.phpunit.cache
/public/build
script/demande/hosts.ini

View File

@@ -4,8 +4,8 @@ import {AutoSubmit} from './class/AutoSubmit'
import {ServerCard} from './class/ServerCard'
import {AutoCustomer} from './class/AutoCustomer'
import {RepeatLine} from './class/RepeatLine'
import {RegisterPayment} from './class/RegisterPayment'
import {OrderCtrl} from './class/OrderCtrl'
import {MainframeEmailEditor} from './class/MainframeEmailEditor'
import preactCustomElement from './functions/preact'
@@ -15,7 +15,7 @@ function script() {
customElements.define('auto-customer',AutoCustomer,{extends:'button'})
customElements.define('repeat-line',RepeatLine,{extends:'div'})
customElements.define('order-ctrl',OrderCtrl,{extends:'div'})
customElements.define("email-builder",MainframeEmailEditor)
customElements.define("register-payment",RegisterPayment,{extends:'a'})
}
function full() {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
export class RegisterPayment extends HTMLAnchorElement {
connectedCallback() {
let element = this;
element.addEventListener('click', (event) => {
event.preventDefault();
let modal = document.createElement('div');
modal.classList = "modal-payment"
modal.innerHTML =`
<div class="modal-payment-content">
<h2>Enregistér un paiement</h2>
</div>
`;
})
}
}

BIN
bun.lockb

Binary file not shown.

View File

@@ -6,8 +6,11 @@
"require": {
"php": ">=8.2",
"ext-ctype": "*",
"ext-dom": "*",
"ext-iconv": "*",
"ext-libxml": "*",
"chillerlan/php-qrcode": "*",
"cocur/slugify": "*",
"doctrine/dbal": "^3.10",
"doctrine/doctrine-bundle": "^2.15",
"doctrine/doctrine-migrations-bundle": "^3.4.2",
@@ -69,9 +72,7 @@
"twig/extra-bundle": "^3.21",
"twig/intl-extra": "^3.21",
"twig/twig": "^3.21",
"vich/uploader-bundle": "^2.7",
"ext-dom": "*",
"ext-libxml": "*"
"vich/uploader-bundle": "^2.7"
},
"config": {
"allow-plugins": {

1704
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250924074745 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE revendeur (id SERIAL NOT NULL, raison_social VARCHAR(255) NOT NULL, code VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, surname VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, phone VARCHAR(255) NOT NULL, parent INT DEFAULT NULL, PRIMARY KEY(id))');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('DROP TABLE revendeur');
}
}

View File

@@ -10,28 +10,28 @@
"preview": "vite preview"
},
"devDependencies": {
"@hotwired/stimulus": "^3.0.0",
"@symfony/stimulus-bridge": "^3.2.0 || ^4.0.0",
"@tailwindcss/postcss": "^4.1.10",
"@hotwired/stimulus": "^3.2.2",
"@symfony/stimulus-bridge": "^4.0.1",
"@tailwindcss/postcss": "^4.1.13",
"postcss": "^8.5.6",
"postcss-scss": "^4.0.9",
"rollup-plugin-javascript-obfuscator": "^1.0.4",
"sass": "^1.89.2",
"vite": "^7.0.0"
"sass": "^1.93.0",
"vite": "^7.1.7"
},
"dependencies": {
"@grafikart/drop-files-element": "^1.0.9",
"@grapesjs/studio-sdk": "^1.0.55",
"@grapesjs/studio-sdk-plugins": "^1.0.27",
"@grapesjs/studio-sdk": "^1.0.56",
"@grapesjs/studio-sdk-plugins": "^1.0.28",
"@hotwired/turbo": "^8.0.13",
"@preact/preset-vite": "^2.10.2",
"@sentry/browser": "^9.34.0",
"@tailwindcss/vite": "^4.1.10",
"@sentry/browser": "^9.46.0",
"@tailwindcss/vite": "^4.1.13",
"@usewaypoint/email-builder": "^0.0.8",
"autoprefixer": "^10.4.21",
"body-scroll-lock": "^4.0.0-beta.0",
"react-email-editor": "^1.7.11",
"sortablejs": "^1.15.6",
"tailwindcss": "^4.1.10"
"tailwindcss": "^4.1.13"
}
}

View File

@@ -0,0 +1,14 @@
---
- hosts: all
connection: local
become: true
tasks:
- name: "Prepare hosts {{ path }}"
ansible.builtin.debug:
msg: "The path value is: {{ path }}"
- name: "Create template file"
ansible.builtin.template:
src: caddy.j2
dest: "/etc/caddy/sites/{{ path }}.conf"
mode: '0644'

View File

@@ -0,0 +1,31 @@
{{ path }}.esy-web.fr {
tls {
dns cloudflare bnbe6SmF2kYBnDi4rEeoPI0wNXeFDWn0xZv7Dnfp;
}
root /var/www/mainframe/public
file_server
header {
-Server
Server "Esy-Web"
-Via
-X-Robots-Tag
# Security headers
X-Content-Type-Options "nosniff"
X-XSS-Protection "0"
Referrer-Policy "no-referrer"
Permissions-Policy "geolocation=(), microphone=(), camera=()"
}
route {
php_fastcgi unix//run/php/php8.3-fpm.sock {
trusted_proxies private_ranges
read_timeout 300s
write_timeout 300s
dial_timeout 100s
env HTTP_PROXY ""
}
request_body {
max_size 100MB
}
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace App\Command;
use App\Entity\Revendeur;
use App\Repository\RevendeurRepository;
use App\Service\Revendeur\RevendeurService;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\HttpKernel\KernelInterface;
#[AsCommand(name: 'mainframe:demande', description: 'Command generate for hosted file')]
class DemandeCommand extends Command
{
public function __construct(
private readonly KernelInterface $kernelInterface,
private readonly RevendeurService $revendeurService,
?string $name = null)
{
parent::__construct($name);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$hosts =[];
$hosts[] = "[local_hosts]";
/** @var Revendeur $revendeur */
foreach ($this->revendeurService->list() as $revendeur) {
$hosts[] =$revendeur->getCode()."-demande ansible_host=127.0.0.1 path=".$revendeur->getCode()."-demande";
}
$pathFile = $this->kernelInterface->getProjectDir()."/script/demande/hosts.ini";
if(file_exists($pathFile)) {
unlink($pathFile);
}
file_put_contents($pathFile, implode("\n", $hosts));
return Command::SUCCESS;
}
}

View File

@@ -1,16 +0,0 @@
<?php
namespace App\Controller\Artemis\Newsletter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class CompaignController extends AbstractController
{
#[Route(path: '/artemis/newsletter/campaign',name: 'artemis_newsletter_campaign',methods: ['GET', 'POST'])]
public function artemis(): Response
{
}
}

View File

@@ -1,213 +0,0 @@
<?php
namespace App\Controller\Artemis\Newsletter;
use App\Entity\Newsletter\Contact;
use App\Entity\Newsletter\ContactLine;
use App\Form\Artemis\Newsletter\ContactLineType;
use App\Form\Artemis\Newsletter\ContactListType;
use App\Repository\Newsletter\ContactRepository;
use Doctrine\ORM\EntityManagerInterface;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Uid\Uuid;
class ContactController extends AbstractController
{
#[Route(path: '/artemis/newsletter/contact',name: 'artemis_newsletter_contact',methods: ['GET', 'POST'])]
public function contacts(ContactRepository $contactRepository): Response
{
$contacts = $contactRepository->findBy([],['id'=>'ASC']);
// Affiche dans un template Twig (à créer)
return $this->render('artemis/newsletter/contact.twig', [
'lists' => $contacts,
]);
}
#[Route(path: '/artemis/newsletter/contact/add', name: 'artemis_newsletter_contact_add', methods: ['GET', 'POST'])]
public function contactsAdd(Request $request, EntityManagerInterface $em): Response
{
$contactList = new Contact();
$form = $this->createForm(ContactListType::class, $contactList);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$contactList->setUuid(Uuid::v4());
$em->persist($contactList);
$em->flush();
$this->addFlash('success', 'La liste a été créée avec succès.');
// Redirige vers la liste des listes (à adapter selon ta route)
return $this->redirectToRoute('artemis_newsletter_contact_edit',['id'=>$contactList->getId()]);
}
return $this->render('artemis/newsletter/contact/add.twig', [
'form' => $form->createView(),
]);
}
#[Route(path: '/artemis/newsletter/contact/edit/{id}',name: 'artemis_newsletter_contact_edit',methods: ['GET', 'POST'])]
public function contactsEdit(?Contact $contact,Request $request,EntityManagerInterface $entityManager): Response
{
if(!$contact instanceof Contact){
return $this->redirectToRoute('artemis_newsletter_contact');
}
$form = $this->createForm(ContactListType::class, $contact);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager->persist($contact);
$entityManager->flush();
$this->addFlash('success', 'La liste a été mise à jour');
// Redirige vers la liste des listes (à adapter selon ta route)
return $this->redirectToRoute('artemis_newsletter_contact_edit',['id'=>$contact->getId()]);
}
if($request->files->has('contacts_file')) {
$contacts_file = $request->files->get('contacts_file');
$spreadsheet = IOFactory::load($contacts_file);
// Récupérer la feuille active (première feuille)
$sheet = $spreadsheet->getActiveSheet();
// Lire la première ligne (en-têtes)
$headerRow = $sheet->rangeToArray('A1:' . $sheet->getHighestColumn() . '1')[0];
// Normalisation : tout en minuscule, supprimer espaces avant/après
$headerRowNormalized = array_map(function($header) {
return mb_strtolower(trim((string) $header));
}, $headerRow);
// Colonnes attendues (nom, prénom, email)
$requiredColumns = ['nom','prenom', 'email'];
// Vérifier que chaque colonne attendue est bien présente dans l'en-tête
$missingColumns = [];
foreach ($requiredColumns as $col) {
// Pour "prénom" supporter aussi "prenom" (sans accent)
if (!in_array($col, $headerRowNormalized, true)) {
$missingColumns[] = $col;
}
}
if (count($missingColumns) > 0) {
// Colonnes manquantes : gérer l'erreur (throw, flash message, etc.)
$missingStr = implode(', ', $missingColumns);
$this->addFlash("error_import","Le fichier importé ne contient pas les colonnes obligatoires $missingStr");
return $this->redirectToRoute('artemis_newsletter_contact_edit',['id'=>$contact->getId()]);
}
$indexNom = array_search('nom', $headerRowNormalized, true);
$indexPrenom = array_search('prenom', $headerRowNormalized, true);
$indexEmail = array_search('email', $headerRowNormalized, true);
$contacts = 0;
// Parcourir les lignes à partir de la 2e (données)
$highestRow = $sheet->getHighestRow();
for ($row = 2; $row <= $highestRow; $row++) {
$rowData = $sheet->rangeToArray("A{$row}:" . $sheet->getHighestColumn() . $row)[0];
$nom = isset($rowData[$indexNom]) ? trim($rowData[$indexNom]) : null;
$prenom = isset($rowData[$indexPrenom]) ? trim($rowData[$indexPrenom]) : null;
$email = isset($rowData[$indexEmail]) ? trim($rowData[$indexEmail]) : null;
// Optionnel : valider email
if ($email && filter_var($email, FILTER_VALIDATE_EMAIL)) {
$contactExist = $entityManager->getRepository(ContactLine::class)->findOneBy(['list'=>$contact,'email'=>$email]);
if(!$contactExist instanceof ContactLine) {
$contactLine = new ContactLine();
$contactLine->setName($nom);
$contactLine->setEmail($email);
$contactLine->setSurname($prenom);
$contactLine->setUuid(Uuid::v4());
$entityManager->persist($contactLine);
$contact->addContactLine($contactLine);
$entityManager->persist($contact);
$contacts++;
}
}
$entityManager->flush();;
}
$this->addFlash('success', $contacts . ' contacts importés correctement.');
return $this->redirectToRoute('artemis_newsletter_contact_edit',['id'=>$contact->getId()]);
}
if($request->query->has('delete')) {
foreach ($contact->getContactLines() as $contactLine) {
$entityManager->remove($contactLine);
}
$entityManager->remove($contact);
$entityManager->flush();
$this->addFlash("success","Suppression effectuée");
return $this->redirectToRoute('artemis_newsletter_contact');
}
if($request->query->has('export')) {
$contactList =[
['nom','surname','email']
];
foreach ($contact->getContactLines() as $contactLine) {
$contactList[] = [$contactLine->getName(),$contactLine->getSurname(),$contactLine->getEmail()];
}
// Génération du CSV en mémoire
$handle = fopen('php://memory', 'r+');
foreach ($contactList as $row) {
fputcsv($handle, $row, ';'); // point-virgule comme séparateur, à adapter si besoin
}
rewind($handle);
$csvContent = stream_get_contents($handle);
fclose($handle);
// Création de la réponse HTTP avec le CSV
$response = new Response($csvContent);
$response->headers->set('Content-Type', 'text/csv; charset=utf-8');
// fichier téléchargé nommé contacts.csv
$response->headers->set('Content-Disposition', 'attachment; filename="'.$contact->getName().'.csv"');
return $response;
}
if($request->query->has('deleteContact')) {
$c = $entityManager->getRepository(ContactLine::class)->find($request->query->get('deleteContact'));
if($c instanceof ContactLine) {
$entityManager->remove($c);
$entityManager->flush();
}
$this->addFlash("success","Suppression effectuée");
return $this->redirectToRoute('artemis_newsletter_contact_edit',['id'=>$contact->getId()]);
}
$contactLine = new ContactLine();
$contactLine->setList($contact);
$contactLine->setUuid(Uuid::v4());
$formContact= $this->createForm(ContactLineType::class,$contactLine);
$formContact->handleRequest($request);
if ($formContact->isSubmitted() && $formContact->isValid()) {
$entityManager->persist($contactLine);
$entityManager->flush();
$this->addFlash("success","Création effectuée");
return $this->redirectToRoute('artemis_newsletter_contact_edit',['id'=>$contact->getId()]);
}
//import
return $this->render('artemis/newsletter/contact/edit.twig', [
'form' => $form->createView(),
'formContact' => $formContact->createView(),
'contact' => $contact,
]);
}
#[Route(path: '/artemis/newsletter/contact/delete',name: 'artemis_newsletter_contact_delete',methods: ['GET', 'POST'])]
public function contactsDelete(?Contact $contact): Response
{
}
}

View File

@@ -1,109 +0,0 @@
<?php
namespace App\Controller\Artemis\Newsletter;
use App\Entity\Newsletter\Template;
use App\Form\Artemis\Newsletter\TemplateType;
use App\Repository\Newsletter\TemplateRepository;
use App\Service\Mailer\AmazonSesClient;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
use Symfony\Component\Routing\Attribute\Route;
class TemplateController extends AbstractController
{
#[Route(path: '/artemis/newsletter/template',name: 'artemis_newsletter_template',methods: ['GET', 'POST'])]
public function templates(TemplateRepository $templateRepository): Response
{
$templates = $templateRepository->findBy([],['id'=>'ASC']);
// Affiche dans un template Twig (à créer)
return $this->render('artemis/newsletter/template.twig', [
'templates' => $templates,
]);
//load template listing
//create editor for created template
//header
//content modify only campain
//footer
//header sortable js
//footer sortabke js
//1 col 2 col 3col
//texte image network
//button link
}
#[Route(path: '/artemis/newsletter/template/add',name: 'artemis_newsletter_template_add',methods: ['GET', 'POST'])]
#[Route(path: '/artemis/newsletter/template/{id}',name: 'artemis_newsletter_template_edit',methods: ['GET', 'POST'])]
public function templateEditor(?Template $template,AmazonSesClient $amazonSesClient,EntityManagerInterface $entityManager,Request $request): Response
{
if(is_null($template)){
$template = new Template();
}
$form = $this->createForm(TemplateType::class,$template);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()){
$entityManager->persist($template);
$entityManager->flush();
$this->addFlash("success","Mise à jour effectuée");
return $this->redirectToRoute('artemis_newsletter_template_edit',['id'=>$template->getId()]);
}
return $this->render('artemis/newsletter/template/editor.twig', [
'form' => $form->createView(),
'template' => $template,
]);
}
#[Route(path: '/artemis/newsletter/template/{id}/preview',name: 'artemis_newsletter_template_preview',methods: ['GET', 'POST'])]
public function templatePreview(?Template $template): Response
{
$configs = json_decode($template->getContent(),true);
$mjml= $this->render('mails/preview.twig', [
'cg' => $configs,
]);
$html = $this->convertMjmlToHtml($mjml);
return new Response($html);
}
private function convertMjmlToHtml(string $mjmlContent): string
{
$command = ['mjml', '--stdin'];
$process = new Process($command);
try {
$process->setInput($mjmlContent);
$process->run();
// Exécute la commande et vérifie la réussite
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
return $process->getOutput();
} catch (ProcessFailedException $exception) {
return ''; // Retourne une chaîne vide en cas d'échec
} catch (Exception $e) {
return '';
}
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Controller\Artemis\Revendeur;
use App\Entity\Revendeur;
use App\Form\Artemis\Revendeur\RevendeurType;
use App\Service\Revendeur\RevendeurService;
use Cocur\Slugify\Slugify;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class RevendeurController extends AbstractController
{
#[Route(path: '/artemis/revendeur',name: 'artemis_revendeur')]
public function revendeur(Request $request,RevendeurService $revendeurService): Response
{
return $this->render('artemis/revendeur/revendeur.twig', [
'revendeurLists' => $revendeurService->list(),
]);
}
#[Route(path: '/artemis/revendeur/add',name: 'artemis_revendeur_add')]
public function revendeurAdd(Request $request,RevendeurService $revendeurService): Response
{
$r = new Revendeur();
$form = $this->createForm(RevendeurType::class,$r);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$slugify = new Slugify();
$r->setCode($slugify->slugify($r->getRaisonSocial()));
$revendeurService->create($r);
}
return $this->render('artemis/revendeur/revendeur_add.twig', [
'form' => $form->createView(),
]);
}
}

125
src/Entity/Revendeur.php Normal file
View File

@@ -0,0 +1,125 @@
<?php
namespace App\Entity;
use App\Repository\RevendeurRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: RevendeurRepository::class)]
class Revendeur
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private ?string $raisonSocial = null;
#[ORM\Column(length: 255)]
private ?string $code = null;
#[ORM\Column(length: 255)]
private ?string $name = null;
#[ORM\Column(length: 255)]
private ?string $surname = null;
#[ORM\Column(length: 255)]
private ?string $email = null;
#[ORM\Column(length: 255)]
private ?string $phone = null;
#[ORM\Column(nullable: true)]
private ?int $parent = null;
public function getId(): ?int
{
return $this->id;
}
public function getRaisonSocial(): ?string
{
return $this->raisonSocial;
}
public function setRaisonSocial(string $raisonSocial): static
{
$this->raisonSocial = $raisonSocial;
return $this;
}
public function getCode(): ?string
{
return $this->code;
}
public function setCode(string $code): static
{
$this->code = $code;
return $this;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
public function getSurname(): ?string
{
return $this->surname;
}
public function setSurname(string $surname): static
{
$this->surname = $surname;
return $this;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): static
{
$this->email = $email;
return $this;
}
public function getPhone(): ?string
{
return $this->phone;
}
public function setPhone(string $phone): static
{
$this->phone = $phone;
return $this;
}
public function getParent(): ?int
{
return $this->parent;
}
public function setParent(?int $parent): static
{
$this->parent = $parent;
return $this;
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace App\Form\Artemis\Revendeur;
use App\Entity\Customer;
use App\Entity\Revendeur;
use App\Service\Revendeur\RevendeurService;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class RevendeurType extends AbstractType
{
public function __construct(private RevendeurService $revendeurService)
{
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$choices = [];
foreach ($this->revendeurService->list() as $revendeur) {
$choices[$revendeur->getId()] = $revendeur->getRaisonSocial();
}
$builder->add('raisonSocial',TextType::class,[
'label' => 'Raison Sociale',
'required' => true,
])
->add('name',TextType::class,[
'label' => 'Nom',
'required' => true,
])
->add('surname',TextType::class,[
'label' => 'Prenom',
'required' => true,
])
->add('email',EmailType::class,[
'label' => 'Email',
'required' => true,
])
->add('phone',TextType::class,[
'label' => 'Telephone',
'required' => true,
])
->add('parent',ChoiceType::class,[
'label' => 'Revendeur Principal',
'choices' => $choices,
'required' => false,
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefault('data_class',Revendeur::class);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Repository;
use App\Entity\Revendeur;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Revendeur>
*/
class RevendeurRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Revendeur::class);
}
// /**
// * @return Revendeur[] Returns an array of Revendeur objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('r')
// ->andWhere('r.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('r.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?Revendeur
// {
// return $this->createQueryBuilder('r')
// ->andWhere('r.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

View File

@@ -0,0 +1,52 @@
<?php
namespace App\Service\Revendeur;
use App\Repository\RevendeurRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class RevendeurService
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly RevendeurRepository $revendeurRepository,
private readonly HttpClientInterface $httpClient
)
{
}
public function list(): array
{
$lists = [];
foreach ($this->revendeurRepository->findAll() as $list) {
$list->dns = $list->getCode() . "-demande.esy-web.fr";
$lists[] = $list;
}
return $lists;
}
public function create(\App\Entity\Revendeur $r)
{
$this->entityManager->persist($r);
$this->entityManager->flush();
$dns = $r->getCode() . "-demande.esy-web.fr";
$ZONE_ID = "7b8ae9e2a488d574b19dfa72a67326d9";
$this->httpClient->request('POST', 'https://api.cloudflare.com/client/v4/zones/' . $ZONE_ID . '/dns_records', [
"headers" => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $_ENV['CLOUDFLARE_TOKEN'],
],
'json' => [
'name' => $dns,
'ttl' => 1,
'type' => 'A',
'comment' => 'Link for ' . $r->getRaisonSocial() . " for created",
'content' => '35.204.191.160',
'proxied' => true,
]
]);
}
}

View File

@@ -81,33 +81,6 @@
</li>
</ul>
</li>
<li class="px-4 py-2">
<button class="flex items-center justify-between w-full p-2 text-base font-normal text-gray-900 dark:text-white rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none" data-submenu-toggle="newsletter">
<div class="flex items-center">
<svg class="w-6 h-6 text-gray-500 dark:text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path d="M2 10a8 8 0 018-8v8h8a8 8 0 11-16 0z"></path>
<path d="M12 2.252A8.014 8.014 0 0117.748 12H12V2.252z"></path>
</svg>
<span class="ml-3">Newsletter</span>
</div>
<svg class="w-4 h-4 text-gray-500 dark:text-gray-400 arrow-icon" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
</svg>
</button>
<ul id="submenu-newsletter" class="submenu ml-6 mt-2 space-y-2">
<li>
<a href="{{ path('artemis_newsletter_contact') }}" class="flex items-center p-2 text-base font-normal text-gray-900 dark:text-white {% if app.request.get('_route') == 'artemis_newsletter_contact' %}bg-gray-200 dark:bg-gray-700{% endif %} rounded-lg">
<span class="ml-3">Liste de contact</span>
</a>
</li>
<li>
<a href="{{ path('artemis_newsletter_template') }}" class="flex items-center p-2 text-base font-normal text-gray-900 dark:text-white {% if app.request.get('_route') == 'artemis_newsletter_contact' %}bg-gray-200 dark:bg-gray-700{% endif %} rounded-lg">
<span class="ml-3">Liste de template</span>
</a>
</li>
</ul>
</li>
<li class="px-4 py-2">
<button class="flex items-center justify-between w-full p-2 text-base font-normal text-gray-900 dark:text-white rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none" data-submenu-toggle="intranet">
<div class="flex items-center">
@@ -130,7 +103,22 @@
</ul>
</li>
<li class="px-4 py-2">
<button class="flex items-center justify-between w-full p-2 text-base font-normal text-gray-900 dark:text-white rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none" data-submenu-toggle="revendeur">
<div class="flex items-center">
<svg class="w-6 h-6 text-gray-500 dark:text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M2 10a8 8 0 018-8v8h8a8 8 0 11-16 0z"></path><path d="M12 2.252A8.014 8.014 0 0117.748 12H12V2.252z"></path></svg>
<span class="ml-3">Revendeur</span>
</div>
<svg class="w-4 h-4 text-gray-500 dark:text-gray-400 arrow-icon" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path></svg>
</button>
<ul id="submenu-revendeur" class="submenu ml-6 mt-2 space-y-2">
<li>
<a href="{{ path('artemis_revendeur') }}" class="flex items-center p-2 text-sm font-normal text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">
<span class="ml-3">Revendeur</span>
</a>
</li>
</ul>
</li>
{% if is_granted('ROLE_ADMIN') %}
<li class="px-4 py-2">
<button class="flex items-center justify-between w-full p-2 text-base font-normal text-gray-900 dark:text-white rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none" data-submenu-toggle="settings">

View File

@@ -25,6 +25,9 @@
{% if orderAdvert.state == "pay" and orderAdvert.customerOrder is null %}
<a href="{{ path('artemis_intranet_customer_view',{id:customer.id,current:'order',idAvis:orderAdvert.id,act:'createFacture'}) }}" class="block bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded">Crée la facture</a>
{% endif %}
{% if orderAdvert.state == "wait-bank" or orderAdvert.state == "wait-virement" %}
<button is="register-payment" id="{{ orderAdvert.id }}" class="block bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded">Enregistrée un paiement</button>
{% endif %}
</td>
</tr>
{% endfor %}

View File

@@ -24,7 +24,7 @@
{% endif %}
{% if orderOrder.state == "f-send" %}
<a href="{{ path('artemis_intranet_customer_view',{id:customer.id,current:'order',idFacture:orderOrder.id,act:'resend'}) }}" class="block bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded">Réenvoyée la facture</a>
{% endif %}}
{% endif %}
<a href="{{ vich_uploader_asset(orderOrder,'facture') }}" download="facture-{{ orderOrder.numOrder }}.pdf" class="block w-full mt-1 bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded">Télécharger la facture</a>
</td>

View File

@@ -1,34 +0,0 @@
{% extends 'artemis/base.twig' %}
{% block title %}Listes de contacts{% endblock %}
{% block content %}
<div class="flex justify-between items-center mb-6">
<h2 class="text-3xl font-semibold text-gray-800 dark:text-gray-200">Listes de contacts</h2>
<div>
<a href="{{ path('artemis_newsletter_contact_add') }}" class="px-4 py-2 bg-blue-600 text-white font-medium rounded-md shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900">
+ Crée une liste de contact
</a>
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
{% for list in lists %}
<a href="{{ path('artemis_newsletter_contact_edit',{id:list.id}) }}" class="card-contact mt-6">
<div class="card-body flex items-center">
<div class="px-3 py-2 rounded bg-indigo-600 text-white mr-3">
<i class="fad fa-user"></i>
</div>
<div class="flex flex-col">
<h1 class="font-semibold">{{ list.name }}</h1>
<p class="text-xs"><span class="num-2">{{ list.contactLines.count }}</span> Contact</p>
</div>
</div>
</a>
{% else %}
<div class="col-span-4 text-center text-gray-400 dark:text-gray-500 font-bold">Aucune liste trouvée.</div>
{% endfor %}
</div>
{% endblock %}

View File

@@ -1,22 +0,0 @@
{% extends 'artemis/base.twig' %}
{% block title %}Créer une nouvelle liste{% endblock %}
{% block content %}
<h1 class="text-2xl font-semibold mb-6">Créer une nouvelle liste de contacts</h1>
<div class="bg-gray-800 rounded-lg shadow p-6 mb-6">
{{ form_start(form, {'attr': {'class': 'max-w-md'}}) }}
<div class="mb-4">
{{ form_label(form.name, null, {'label_attr': {'class': 'block mb-1 font-medium text-gray-700 dark:text-gray-300'}}) }}
{{ form_widget(form.name, {'attr': {
'class': 'w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white'
}}) }}
{{ form_errors(form.name) }}
</div>
<button type="submit" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded transition font-semibold">
Créer la liste
</button>
{{ form_end(form) }}
</div>
{% endblock %}

View File

@@ -1,138 +0,0 @@
{% extends 'artemis/base.twig' %}
{% block title %}Liste - {{ contact.name }}{% endblock %}
{% block content %}
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-semibold mb-6">Liste - {{ contact.name }}</h1>
<div>
<a download="liste-{{ contact.name }}.csv" href="{{ path('artemis_newsletter_contact_edit',{id:contact.id,export:1}) }}" class="px-4 py-2 bg-blue-600 text-white font-medium rounded-md shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900">
Exporter la liste des contact
</a>
<a href="{{ path('artemis_newsletter_contact_edit',{id:contact.id,delete :1}) }}" class="ml-2 px-4 py-2 bg-red-600 text-white font-medium rounded-md shadow-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900">
Supprimer la liste des contact
</a>
</div>
</div>
<div class="bg-gray-800 rounded-lg shadow p-6 mb-6">
{{ form_start(form, {'attr': {'class': 'w-full'}}) }}
<div class="mb-4">
{{ form_label(form.name, null, {'label_attr': {'class': 'block mb-1 font-medium text-gray-700 dark:text-gray-300'}}) }}
{{ form_widget(form.name, {'attr': {
'class': 'w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white'
}}) }}
{{ form_errors(form.name) }}
</div>
<button type="submit" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded transition font-semibold w-full">
Modifié liste
</button>
{{ form_end(form) }}
</div>
<div class="bg-gray-800 rounded-lg shadow p-6 mb-6 flex flex-col items-center justify-center text-center border border-dashed border-blue-400">
<div class="mb-4">
<svg class="w-12 h-12 mx-auto text-blue-500" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 48 48">
<rect x="8" y="18" width="32" height="22" rx="3" fill="currentColor" class="text-blue-100"/>
<path stroke-linecap="round" stroke-linejoin="round" d="M24 6v22m0 0l-7-7m7 7l7-7" stroke="currentColor"/>
</svg>
</div>
<h2 class="text-lg font-semibold text-gray-100 mb-2">Importer des contacts</h2>
<p class="text-gray-400 mb-4 text-sm">
Téléversez un fichier <span class="font-semibold text-blue-300">CSV</span> ou <span class="font-semibold text-blue-300">Excel</span> pour ajouter plusieurs contacts à cette liste.<br>
<span class="text-xs text-gray-500">Le fichier doit contenir les colonnes: email, nom, prénom…</span>
</p>
<form method="post" enctype="multipart/form-data" action="{{ path('artemis_newsletter_contact_edit', {'id': contact.id}) }}" class="flex flex-col items-center gap-4 w-full">
<input
type="file"
name="contacts_file"
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
class="block file:mr-4 file:px-4 file:py-2 text-gray-900 dark:text-gray-100
file:rounded file:border-0 file:text-sm file:bg-blue-50 file:text-blue-700
hover:file:bg-blue-100 focus:file:outline-none
dark:file:bg-gray-700 dark:file:text-blue-300 dark:hover:file:bg-gray-600"
required
>
<button type="submit" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded font-semibold transition w-full max-w-xs">
Importer
</button>
</form>
</div>
<div class="bg-gray-800 rounded-lg shadow p-6 mb-6">
<h3>Ajouter un contact</h3>
{{ form_start(formContact, {'attr': {'class': 'w-full'}}) }}
<div class="grid grid-cols-3">
<div class="m-2">
{{ form_label(formContact.name, null, {'label_attr': {'class': 'block mb-1 font-medium text-gray-700 dark:text-gray-300'}}) }}
{{ form_widget(formContact.name, {'attr': {
'class': 'w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white'
}}) }}
{{ form_errors(formContact.name) }}
</div>
<div class="m-2">
{{ form_label(formContact.surname, null, {'label_attr': {'class': 'block mb-1 font-medium text-gray-700 dark:text-gray-300'}}) }}
{{ form_widget(formContact.surname, {'attr': {
'class': 'w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white'
}}) }}
{{ form_errors(formContact.surname) }}
</div>
<div class="m-2">
{{ form_label(formContact.email, null, {'label_attr': {'class': 'block mb-1 font-medium text-gray-700 dark:text-gray-300'}}) }}
{{ form_widget(formContact.email, {'attr': {
'class': 'w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white'
}}) }}
{{ form_errors(formContact.email) }}
</div>
</div>
<button type="submit" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded transition font-semibold w-full">
Crée le contact
</button>
{{ form_end(formContact) }}
</div>
<div class="bg-gray-800 rounded-lg shadow mt-2">
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th scope="col" class="px-6 py-3">
Email
</th>
<th scope="col" class="px-6 py-3">
Nom
</th>
<th scope="col" class="px-6 py-3">
Prénom
</th>
<th scope="col" class="px-6 py-3">
Status
</th>
<th scope="col" class="px-6 py-3">
Actions
</th>
</tr>
</thead>
<tbody>
{% for line in contact.contactLines %}
<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700 border-gray-200">
<th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
{{ line.email }}
</th>
<td class="px-6 py-4">
{{ line.name }}
</td>
<td class="px-6 py-4">
{{ line.surname }}
</td>
<td class="px-6 py-4">
<i class="fad fa-check-circle text-green-500"></i>
</td>
<td class="px-6 py-4">
<a class="button bg-red-900 text-white p-2" href="{{ app.request.pathInfo }}?deleteContact={{ line.id }}">Supprimer</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View File

@@ -1,34 +0,0 @@
{% extends 'artemis/base.twig' %}
{% block title %}Listes de contacts{% endblock %}
{% block content %}
<div class="flex justify-between items-center mb-6">
<h2 class="text-3xl font-semibold text-gray-800 dark:text-gray-200">Listes des templates</h2>
<div>
<a href="{{ path('artemis_newsletter_contact_add') }}" class="px-4 py-2 bg-blue-600 text-white font-medium rounded-md shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900">
+ Crée un templates
</a>
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
{% for template in templates %}
<a href="" class="card-contact mt-6">
<div class="card-body flex items-center">
<div class="px-3 py-2 rounded bg-indigo-600 text-white mr-3">
<i class="fad fa-user"></i>
</div>
<div class="flex flex-col">
<h1 class="font-semibold">{{ template.name }}</h1>
</div>
</div>
</a>
{% else %}
<div class="col-span-4 text-center text-gray-400 dark:text-gray-500 font-bold">Aucun template trouvée.</div>
{% endfor %}
</div>
{% endblock %}

View File

@@ -1,46 +0,0 @@
{% extends 'artemis/base.twig' %}
{% block title %}Template - {{ template.name }}{% endblock %}
{% block content %}
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-semibold mb-6">Template - {{ template.name }}</h1>
{% if template.id is not null %}
<div>
<a target="_blank" href="{{ path('artemis_newsletter_template_preview',{id:template.id}) }}" class="ml-2 px-4 py-2 bg-blue-600 text-white font-medium rounded-md shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900">
Voir le rendu
</a>
<a href="{{ path('artemis_newsletter_template_edit',{id:template.id,delete :1}) }}" class="ml-2 px-4 py-2 bg-red-600 text-white font-medium rounded-md shadow-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900">
Supprimer le template
</a>
</div>
{% endif %}
</div>
<div class="bg-gray-800 rounded-lg shadow p-6 mb-6">
{{ form_start(form, {'attr': {'class': 'w-full'}}) }}
<div class="mb-4">
{{ form_label(form.name, null, {'label_attr': {'class': 'block mb-1 font-medium text-gray-700 dark:text-gray-300'}}) }}
{{ form_widget(form.name, {'attr': {
'class': 'w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white'
}}) }}
{{ form_errors(form.name) }}
</div>
<button type="submit" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded transition font-semibold w-full">
Sauvegarder
</button>
{{ form_end(form) }}
</div>
<email-builder id="{{ template.id }}"></email-builder>
//content management
// left zone
// central editor
// right module
{% endblock %}

View File

@@ -0,0 +1,12 @@
{% extends 'artemis/base.twig' %}
{% block title %}Revendeur(s){% endblock %}
{% block content %}
<div class="flex justify-between items-center mb-6">
<h2 class="text-3xl font-semibold text-gray-800 dark:text-gray-200">Listes des revendeur</h2>
<div>
<a href="{{ path('artemis_revendeur_add') }}" class="px-4 py-2 bg-blue-600 text-white font-medium rounded-md shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900">
+ Crée un revendeur
</a>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,37 @@
{% extends 'artemis/base.twig' %}
{% block title %}Crée un Revendeur(s){% endblock %}
{% block content %}
<div class="flex justify-between items-center mb-6">
<h2 class="text-3xl font-semibold text-gray-800 dark:text-gray-200">Crée un Revendeur</h2>
</div>
<div class="bg-gray-800 rounded-lg shadow p-6 mb-6">
{{ form_start(form) }}
<div class="flex space-x-4">
<div class="flex-1">
{{ form_row(form.raisonSocial) }}
</div>
<div class="flex-1">
{{ form_row(form.name) }}
</div>
<div class="flex-1">
{{ form_row(form.surname) }}
</div>
</div>
<div class="flex space-x-4">
<div class="flex-1">
{{ form_row(form.email) }}
</div>
<div class="flex-1">
{{ form_row(form.phone) }}
</div>
<div class="flex-1">
{{ form_row(form.parent) }}
</div>
</div>
<button type="submit" class="w-full bg-purple-600 hover:bg-purple-700 text-white font-semibold px-4 py-2 rounded">Enregistrer</button>
{{ form_end(form) }}
</div>
{% endblock %}