Files
crm_ecosplay/src/Service/MailerService.php

271 lines
9.9 KiB
PHP
Raw Normal View History

2026-04-01 15:42:52 +02:00
<?php
namespace App\Service;
use App\Entity\EmailTracking;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Mailer\Messenger\SendEmailMessage;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Mime\Crypto\SMimeSigner;
use Symfony\Component\Mime\Email;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class MailerService
{
public function __construct(
private MessageBusInterface $bus,
#[Autowire('%kernel.project_dir%')] private string $projectDir,
#[Autowire(env: 'SMIME_PASSPHRASE')] private string $smimePassphrase,
#[Autowire('%admin_email%')] private string $adminEmail,
private UrlGeneratorInterface $urlGenerator,
private UnsubscribeManager $unsubscribeManager,
private EntityManagerInterface $em,
) {
}
public function getAdminEmail(): string
{
return $this->adminEmail;
}
public function getAdminFrom(): string
{
feat: comptabilite + prestataires + rapport financier + stats dynamiques Comptabilite (Super Admin) : - ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE (journal ventes, grand livre, FEC, balance agee, reglements, commissions Stripe 1.5%+0.25E, couts services) - Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli, tableau pagine, champ signature DocuSeal - Signature electronique DocuSeal + callback + envoi email signe avec template dedie (compta_export_signed.html.twig) - Rapport financier public (RapportFinancierPdf) : recettes par service, depenses (Stripe, infra, prestataires), bilan excedent/deficit - Codes comptables clients EC-XXXX (plus de 411xxx) Prestataires (Super Admin) : - Entite Prestataire (raisonSociale, siret, email, phone, adresse) - Entite FacturePrestataire (numFacture, montantHt, montantTtc, year, month, isPaid, PDF via Vich) - CRUD complet avec recherche SIRET via proxy API data.gouv.fr - Commande cron app:reminder:factures-prestataire (5 du mois) - Factures prestataires integrees dans export couts services - Sidebar Super Admin : entree Prestataires + Comptabilite Stats (/admin/stats) : - Cout prestataire dynamique depuis FacturePrestataire - Fusion Infra + Prestataire en "Cout de fonctionnement" - Commission Stripe corrigee (1.5% + 0.25E par transaction) Divers : - DocuSealService::sendComptaForSignature() + getApi() - Customer::generateCodeComptable() format EC-XXXX-XXXXX - Protection double prefixe EC- a la creation client - Bouton regenerer PDF cache quand advert state=accepted - Modals sans script inline (data-modal-open/close dans app.js) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
return 'Association E-Cosplay <'.$this->adminEmail.'>';
2026-04-01 15:42:52 +02:00
}
public function send(Email $email): void
{
$publicKeyPath = $this->projectDir.'/key.asc';
if (file_exists($publicKeyPath)) {
$email->attachFromPath($publicKeyPath, 'public_key.asc', 'application/pgp-keys');
}
$certificate = $this->projectDir.'/config/cert/smime/certificate.pem';
$privateKey = $this->projectDir.'/config/cert/smime/private-key.pem';
// @codeCoverageIgnoreStart
2026-04-01 15:42:52 +02:00
if (file_exists($certificate) && file_exists($privateKey)) {
$signer = new SMimeSigner($certificate, $privateKey, $this->smimePassphrase);
$email = $signer->sign($email);
}
// @codeCoverageIgnoreEnd
2026-04-01 15:42:52 +02:00
$this->bus->dispatch(new SendEmailMessage($email));
}
/**
* @param array<array{path: string, name?: string}>|null $attachments
*/
public function sendEmail(string $to, string $subject, string $content, ?string $from = null, ?string $replyTo = null, bool $withUnsubscribe = true, ?array $attachments = null, int $priority = 3): void
2026-04-01 15:42:52 +02:00
{
$from ??= $this->getAdminFrom();
$canUnsubscribe = $withUnsubscribe && !$this->isWhitelisted($to);
if ($canUnsubscribe && $this->unsubscribeManager->isUnsubscribed($to)) {
return;
}
$email = (new Email())
->from($from)
->to($to)
->subject($subject)
->html($content)
->priority($priority);
2026-04-01 15:42:52 +02:00
if ($replyTo) {
$email->replyTo($replyTo);
}
if ($attachments) {
$processedAttachments = [];
2026-04-01 15:42:52 +02:00
foreach ($attachments as $attachment) {
$name = $attachment['name'] ?? basename($attachment['path']);
$email->attachFromPath($attachment['path'], $name);
$processedAttachments[] = [
'path' => $attachment['path'],
'name' => $name,
];
2026-04-01 15:42:52 +02:00
}
$attachments = $processedAttachments;
2026-04-01 15:42:52 +02:00
}
$messageId = bin2hex(random_bytes(16));
feat: comptabilite + prestataires + rapport financier + stats dynamiques Comptabilite (Super Admin) : - ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE (journal ventes, grand livre, FEC, balance agee, reglements, commissions Stripe 1.5%+0.25E, couts services) - Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli, tableau pagine, champ signature DocuSeal - Signature electronique DocuSeal + callback + envoi email signe avec template dedie (compta_export_signed.html.twig) - Rapport financier public (RapportFinancierPdf) : recettes par service, depenses (Stripe, infra, prestataires), bilan excedent/deficit - Codes comptables clients EC-XXXX (plus de 411xxx) Prestataires (Super Admin) : - Entite Prestataire (raisonSociale, siret, email, phone, adresse) - Entite FacturePrestataire (numFacture, montantHt, montantTtc, year, month, isPaid, PDF via Vich) - CRUD complet avec recherche SIRET via proxy API data.gouv.fr - Commande cron app:reminder:factures-prestataire (5 du mois) - Factures prestataires integrees dans export couts services - Sidebar Super Admin : entree Prestataires + Comptabilite Stats (/admin/stats) : - Cout prestataire dynamique depuis FacturePrestataire - Fusion Infra + Prestataire en "Cout de fonctionnement" - Commission Stripe corrigee (1.5% + 0.25E par transaction) Divers : - DocuSealService::sendComptaForSignature() + getApi() - Customer::generateCodeComptable() format EC-XXXX-XXXXX - Protection double prefixe EC- a la creation client - Bouton regenerer PDF cache quand advert state=accepted - Modals sans script inline (data-modal-open/close dans app.js) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
$email->getHeaders()->addIdHeader('Message-ID', $messageId.'@e-cosplay.fr');
2026-04-01 15:42:52 +02:00
$trackingUrl = $this->urlGenerator->generate('app_email_track', [
'messageId' => $messageId,
], UrlGeneratorInterface::ABSOLUTE_URL);
$viewUrl = $this->urlGenerator->generate('app_email_view', [
'messageId' => $messageId,
], UrlGeneratorInterface::ABSOLUTE_URL);
$html = $email->getHtmlBody();
feat: comptabilite + prestataires + rapport financier + stats dynamiques Comptabilite (Super Admin) : - ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE (journal ventes, grand livre, FEC, balance agee, reglements, commissions Stripe 1.5%+0.25E, couts services) - Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli, tableau pagine, champ signature DocuSeal - Signature electronique DocuSeal + callback + envoi email signe avec template dedie (compta_export_signed.html.twig) - Rapport financier public (RapportFinancierPdf) : recettes par service, depenses (Stripe, infra, prestataires), bilan excedent/deficit - Codes comptables clients EC-XXXX (plus de 411xxx) Prestataires (Super Admin) : - Entite Prestataire (raisonSociale, siret, email, phone, adresse) - Entite FacturePrestataire (numFacture, montantHt, montantTtc, year, month, isPaid, PDF via Vich) - CRUD complet avec recherche SIRET via proxy API data.gouv.fr - Commande cron app:reminder:factures-prestataire (5 du mois) - Factures prestataires integrees dans export couts services - Sidebar Super Admin : entree Prestataires + Comptabilite Stats (/admin/stats) : - Cout prestataire dynamique depuis FacturePrestataire - Fusion Infra + Prestataire en "Cout de fonctionnement" - Commission Stripe corrigee (1.5% + 0.25E par transaction) Divers : - DocuSealService::sendComptaForSignature() + getApi() - Customer::generateCodeComptable() format EC-XXXX-XXXXX - Protection double prefixe EC- a la creation client - Bouton regenerer PDF cache quand advert state=accepted - Modals sans script inline (data-modal-open/close dans app.js) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
$html = str_replace('https://crm.e-cosplay.fr/logo.jpg', $trackingUrl, $html);
2026-04-01 15:42:52 +02:00
$html = str_replace('__VIEW_URL__', $viewUrl, $html);
feat: page web de rapport DNS detaille + simplification du mail src/Controller/DnsReportController.php (nouveau): - Route /email/configuration/{token} accessible via le lien dans le mail - Utilise le messageId de l'EmailTracking comme token d'acces (seuls les destinataires du mail ont le lien) - Execute tous les checks en temps reel: DnsCheckService (SPF, DMARC, MX, Bounce via dig @1.1.1.1), AwsSesService (domaine, 3 DKIM CNAME, MAIL FROM MX/TXT, bounce notif), CloudflareService (zone, records), MailcowService (domaine, DKIM, MX, autodiscover, autoconfig, SRV, MTA-STS) - Enrichit chaque check avec la colonne Cloudflare - Passe les resultats au template Twig pour affichage complet templates/dns_report/index.html.twig (nouveau): - Page glassmorphism avec header glass - Resume en haut: 3 cards (verifications OK, erreurs, avertissements) avec bordures laterales colorees vert/rouge/jaune - Tableau par domaine avec 6 colonnes: Source (badge colore par type: orange AWS, violet Mailcow, bleu Cloudflare, gris DNS), Verification, Attendu, Dig (actuel), Cloudflare, Statut (rond colore) - Section erreurs detaillees avec liste - Section avertissements avec liste - Footer "Esy-Infra - Service de monitoring d'infra" templates/emails/dns_report.html.twig (simplifie): - Mail ne contient plus les details: seulement un tableau avec chaque domaine et son statut (OK vert / WARN jaune / KO rouge) - Bouton "Voir le rapport complet" avec lien vers la page web (VML fallback pour Outlook) - Le lien utilise le placeholder __DNS_REPORT_URL__ remplace par le MailerService avec le messageId du mail src/Service/MailerService.php: - Ajout du remplacement de __DNS_REPORT_URL__ par l'URL absolue /email/configuration/{messageId} dans sendEmail(), au meme endroit que __VIEW_URL__ Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 21:57:28 +02:00
$dnsReportUrl = $this->urlGenerator->generate('app_dns_report', [
'token' => $messageId,
], UrlGeneratorInterface::ABSOLUTE_URL);
$html = str_replace('__DNS_REPORT_URL__', $dnsReportUrl, $html);
feat: gestion complete Devis + Avis de paiement + DocuSeal signature + mails Devis : - Entity DevisLine (pos, title, description, priceHt) liee a Devis (OneToMany cascade/orphanRemoval) - Champs ajoutes sur Devis : customer (ManyToOne), submissionId, state machine (created/send/accepted/refused/cancel), raisonMessage, totaux HT/TVA/TTC, updatedAt, setUpdatedAt public - Relation Devis <-> Advert changee de ManyToOne a OneToOne nullable - Vich Attribute (migration Annotation -> Attribute) pour unsignedPdf/signedPdf/auditPdf - DevisController CRUD complet : create (form repeater lignes + boutons rapides TarificationService), edit, cancel (libere OrderNumber), generate-pdf, send, resend, create-advert, events - DevisPdf (FPDF/FPDI) : header legacy (logo, num, date, client), body lignes, summary totaux, footer SITECONSEIL + pagination, champ signature DocuSeal sur page devis + derniere page CGV - OrderNumberService : preview() et generate() reutilisent les OrderNumber non utilises (isUsed=false) en priorite - OrderNumber::markAsUnused() ajoute DocuSeal integration devis : - DocuSealService : sendDevisForSignature (avec completed_redirect_url), resendDevisSignature (archive ancienne submission), getSubmitterSlug, downloadSignedDevis (sauvegarde via Vich UploadedFile test=true) - WebhookDocuSealController : dispatch par doc_type devis/attestation, handleDevisEvent (form.completed -> STATE_ACCEPTED + download PDF signe/audit, form.declined -> STATE_REFUSED + raison) - DocusealEvent entity pour tracer form.viewed/started/completed/declined en temps reel - Page evenements admin /admin/devis/{id}/events avec badges et payload JSON Signature client : - DevisProcessController : page publique /devis/process/{id}/{hmac} securisee par HMAC, boutons Signer (redirect DocuSeal) / Refuser (motif optionnel) - Pages confirmation : signed.html.twig (merci + recap) et refused.html.twig (confirmation refus + motif) - Nelmio whitelist : signature.esy-web.dev + signature.siteconseil.fr Avis de paiement : - Entity AdvertLine (pos, title, description, priceHt) liee a Advert - Advert refactorise : customer, state, totaux, raisonMessage, submissionId, advertFile (Vich mapping advert_pdf), lines collection, updatedAt - AdvertController : generate-pdf, send (mail + PJ + lien paiement), resend (rappel), cancel (delie devis, libere OrderNumber), search Meilisearch - AdvertPdf (FPDF/FPDI) : QR code Endroid pointant vers /order/{numOrder}, texte "Scannez pour payer" - OrderPaymentController : page publique /order/{numOrder} avec detail prestations, totaux, options paiement (placeholder) - Creation auto depuis devis signe : copie client, totaux, lignes, meme OrderNumber Meilisearch : - Index customer_devis et customer_advert avec searchable (numOrder, customerName, customerEmail, state) et filterable (customerId, state) - CRUD indexation sur chaque action (create, edit, send, cancel, create-advert) - Recherche AJAX dans tabs Devis et Avis avec debounce + dropdown glassmorphism - Sync admin : boutons syncDevis / syncAdverts + compteurs dans /admin/sync Emails : - MailerService : VCF auto (fiche contact SARL SITECONSEIL) en PJ sur tous les mails, bloc HTML pieces jointes injecte automatiquement (exclut .asc/.p7z/smime) avec icone trombone + taille fichier - Templates : devis_to_sign, devis_signed_client/admin (PJ signed+audit), devis_refused_client/admin, advert_send (PJ + bouton paiement), ndd_expiration - TestMailCommand : option --force-dsn pour envoyer via un DSN SMTP specifique (test prod depuis dev) Commande NDD : - app:ndd:check : verifie expiration domaines <= 30j, envoie mail groupe a monitor@siteconseil.fr - Cron quotidien 8h (docker + ansible) Divers : - Titles templates : CRM SITECONSEIL -> SARL SITECONSEIL (52 fichiers) - VAULT_URL dev = https://kms.esy-web.dev (comme prod) - app.js : initDevisLines (repeater + drag & drop), initTabSearch, toggle refus devis - app.scss : styles drag & drop - setasign/fpdi-fpdf installe pour fusion PDF - 5 migrations Doctrine Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:44:35 +02:00
// Injection du bloc liste des pieces jointes (hors .asc, .p7z, smime)
if ($attachments) {
$html = $this->injectAttachmentsList($html, $attachments);
}
2026-04-01 15:42:52 +02:00
$email->html($html);
$tracking = new EmailTracking($messageId, $to, $subject, $html, $attachments);
$this->em->persist($tracking);
$this->em->flush();
feat: comptabilite + prestataires + rapport financier + stats dynamiques Comptabilite (Super Admin) : - ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE (journal ventes, grand livre, FEC, balance agee, reglements, commissions Stripe 1.5%+0.25E, couts services) - Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli, tableau pagine, champ signature DocuSeal - Signature electronique DocuSeal + callback + envoi email signe avec template dedie (compta_export_signed.html.twig) - Rapport financier public (RapportFinancierPdf) : recettes par service, depenses (Stripe, infra, prestataires), bilan excedent/deficit - Codes comptables clients EC-XXXX (plus de 411xxx) Prestataires (Super Admin) : - Entite Prestataire (raisonSociale, siret, email, phone, adresse) - Entite FacturePrestataire (numFacture, montantHt, montantTtc, year, month, isPaid, PDF via Vich) - CRUD complet avec recherche SIRET via proxy API data.gouv.fr - Commande cron app:reminder:factures-prestataire (5 du mois) - Factures prestataires integrees dans export couts services - Sidebar Super Admin : entree Prestataires + Comptabilite Stats (/admin/stats) : - Cout prestataire dynamique depuis FacturePrestataire - Fusion Infra + Prestataire en "Cout de fonctionnement" - Commission Stripe corrigee (1.5% + 0.25E par transaction) Divers : - DocuSealService::sendComptaForSignature() + getApi() - Customer::generateCodeComptable() format EC-XXXX-XXXXX - Protection double prefixe EC- a la creation client - Bouton regenerer PDF cache quand advert state=accepted - Modals sans script inline (data-modal-open/close dans app.js) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
// Ajout automatique du fichier VCF (fiche contact E-Cosplay)
feat: gestion complete Devis + Avis de paiement + DocuSeal signature + mails Devis : - Entity DevisLine (pos, title, description, priceHt) liee a Devis (OneToMany cascade/orphanRemoval) - Champs ajoutes sur Devis : customer (ManyToOne), submissionId, state machine (created/send/accepted/refused/cancel), raisonMessage, totaux HT/TVA/TTC, updatedAt, setUpdatedAt public - Relation Devis <-> Advert changee de ManyToOne a OneToOne nullable - Vich Attribute (migration Annotation -> Attribute) pour unsignedPdf/signedPdf/auditPdf - DevisController CRUD complet : create (form repeater lignes + boutons rapides TarificationService), edit, cancel (libere OrderNumber), generate-pdf, send, resend, create-advert, events - DevisPdf (FPDF/FPDI) : header legacy (logo, num, date, client), body lignes, summary totaux, footer SITECONSEIL + pagination, champ signature DocuSeal sur page devis + derniere page CGV - OrderNumberService : preview() et generate() reutilisent les OrderNumber non utilises (isUsed=false) en priorite - OrderNumber::markAsUnused() ajoute DocuSeal integration devis : - DocuSealService : sendDevisForSignature (avec completed_redirect_url), resendDevisSignature (archive ancienne submission), getSubmitterSlug, downloadSignedDevis (sauvegarde via Vich UploadedFile test=true) - WebhookDocuSealController : dispatch par doc_type devis/attestation, handleDevisEvent (form.completed -> STATE_ACCEPTED + download PDF signe/audit, form.declined -> STATE_REFUSED + raison) - DocusealEvent entity pour tracer form.viewed/started/completed/declined en temps reel - Page evenements admin /admin/devis/{id}/events avec badges et payload JSON Signature client : - DevisProcessController : page publique /devis/process/{id}/{hmac} securisee par HMAC, boutons Signer (redirect DocuSeal) / Refuser (motif optionnel) - Pages confirmation : signed.html.twig (merci + recap) et refused.html.twig (confirmation refus + motif) - Nelmio whitelist : signature.esy-web.dev + signature.siteconseil.fr Avis de paiement : - Entity AdvertLine (pos, title, description, priceHt) liee a Advert - Advert refactorise : customer, state, totaux, raisonMessage, submissionId, advertFile (Vich mapping advert_pdf), lines collection, updatedAt - AdvertController : generate-pdf, send (mail + PJ + lien paiement), resend (rappel), cancel (delie devis, libere OrderNumber), search Meilisearch - AdvertPdf (FPDF/FPDI) : QR code Endroid pointant vers /order/{numOrder}, texte "Scannez pour payer" - OrderPaymentController : page publique /order/{numOrder} avec detail prestations, totaux, options paiement (placeholder) - Creation auto depuis devis signe : copie client, totaux, lignes, meme OrderNumber Meilisearch : - Index customer_devis et customer_advert avec searchable (numOrder, customerName, customerEmail, state) et filterable (customerId, state) - CRUD indexation sur chaque action (create, edit, send, cancel, create-advert) - Recherche AJAX dans tabs Devis et Avis avec debounce + dropdown glassmorphism - Sync admin : boutons syncDevis / syncAdverts + compteurs dans /admin/sync Emails : - MailerService : VCF auto (fiche contact SARL SITECONSEIL) en PJ sur tous les mails, bloc HTML pieces jointes injecte automatiquement (exclut .asc/.p7z/smime) avec icone trombone + taille fichier - Templates : devis_to_sign, devis_signed_client/admin (PJ signed+audit), devis_refused_client/admin, advert_send (PJ + bouton paiement), ndd_expiration - TestMailCommand : option --force-dsn pour envoyer via un DSN SMTP specifique (test prod depuis dev) Commande NDD : - app:ndd:check : verifie expiration domaines <= 30j, envoie mail groupe a monitor@siteconseil.fr - Cron quotidien 8h (docker + ansible) Divers : - Titles templates : CRM SITECONSEIL -> SARL SITECONSEIL (52 fichiers) - VAULT_URL dev = https://kms.esy-web.dev (comme prod) - app.js : initDevisLines (repeater + drag & drop), initTabSearch, toggle refus devis - app.scss : styles drag & drop - setasign/fpdi-fpdf installe pour fusion PDF - 5 migrations Doctrine Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:44:35 +02:00
$vcfPath = $this->generateVcf();
if (null !== $vcfPath) {
feat: comptabilite + prestataires + rapport financier + stats dynamiques Comptabilite (Super Admin) : - ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE (journal ventes, grand livre, FEC, balance agee, reglements, commissions Stripe 1.5%+0.25E, couts services) - Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli, tableau pagine, champ signature DocuSeal - Signature electronique DocuSeal + callback + envoi email signe avec template dedie (compta_export_signed.html.twig) - Rapport financier public (RapportFinancierPdf) : recettes par service, depenses (Stripe, infra, prestataires), bilan excedent/deficit - Codes comptables clients EC-XXXX (plus de 411xxx) Prestataires (Super Admin) : - Entite Prestataire (raisonSociale, siret, email, phone, adresse) - Entite FacturePrestataire (numFacture, montantHt, montantTtc, year, month, isPaid, PDF via Vich) - CRUD complet avec recherche SIRET via proxy API data.gouv.fr - Commande cron app:reminder:factures-prestataire (5 du mois) - Factures prestataires integrees dans export couts services - Sidebar Super Admin : entree Prestataires + Comptabilite Stats (/admin/stats) : - Cout prestataire dynamique depuis FacturePrestataire - Fusion Infra + Prestataire en "Cout de fonctionnement" - Commission Stripe corrigee (1.5% + 0.25E par transaction) Divers : - DocuSealService::sendComptaForSignature() + getApi() - Customer::generateCodeComptable() format EC-XXXX-XXXXX - Protection double prefixe EC- a la creation client - Bouton regenerer PDF cache quand advert state=accepted - Modals sans script inline (data-modal-open/close dans app.js) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
$email->attachFromPath($vcfPath, 'Association-E-Cosplay.vcf', 'text/vcard');
feat: gestion complete Devis + Avis de paiement + DocuSeal signature + mails Devis : - Entity DevisLine (pos, title, description, priceHt) liee a Devis (OneToMany cascade/orphanRemoval) - Champs ajoutes sur Devis : customer (ManyToOne), submissionId, state machine (created/send/accepted/refused/cancel), raisonMessage, totaux HT/TVA/TTC, updatedAt, setUpdatedAt public - Relation Devis <-> Advert changee de ManyToOne a OneToOne nullable - Vich Attribute (migration Annotation -> Attribute) pour unsignedPdf/signedPdf/auditPdf - DevisController CRUD complet : create (form repeater lignes + boutons rapides TarificationService), edit, cancel (libere OrderNumber), generate-pdf, send, resend, create-advert, events - DevisPdf (FPDF/FPDI) : header legacy (logo, num, date, client), body lignes, summary totaux, footer SITECONSEIL + pagination, champ signature DocuSeal sur page devis + derniere page CGV - OrderNumberService : preview() et generate() reutilisent les OrderNumber non utilises (isUsed=false) en priorite - OrderNumber::markAsUnused() ajoute DocuSeal integration devis : - DocuSealService : sendDevisForSignature (avec completed_redirect_url), resendDevisSignature (archive ancienne submission), getSubmitterSlug, downloadSignedDevis (sauvegarde via Vich UploadedFile test=true) - WebhookDocuSealController : dispatch par doc_type devis/attestation, handleDevisEvent (form.completed -> STATE_ACCEPTED + download PDF signe/audit, form.declined -> STATE_REFUSED + raison) - DocusealEvent entity pour tracer form.viewed/started/completed/declined en temps reel - Page evenements admin /admin/devis/{id}/events avec badges et payload JSON Signature client : - DevisProcessController : page publique /devis/process/{id}/{hmac} securisee par HMAC, boutons Signer (redirect DocuSeal) / Refuser (motif optionnel) - Pages confirmation : signed.html.twig (merci + recap) et refused.html.twig (confirmation refus + motif) - Nelmio whitelist : signature.esy-web.dev + signature.siteconseil.fr Avis de paiement : - Entity AdvertLine (pos, title, description, priceHt) liee a Advert - Advert refactorise : customer, state, totaux, raisonMessage, submissionId, advertFile (Vich mapping advert_pdf), lines collection, updatedAt - AdvertController : generate-pdf, send (mail + PJ + lien paiement), resend (rappel), cancel (delie devis, libere OrderNumber), search Meilisearch - AdvertPdf (FPDF/FPDI) : QR code Endroid pointant vers /order/{numOrder}, texte "Scannez pour payer" - OrderPaymentController : page publique /order/{numOrder} avec detail prestations, totaux, options paiement (placeholder) - Creation auto depuis devis signe : copie client, totaux, lignes, meme OrderNumber Meilisearch : - Index customer_devis et customer_advert avec searchable (numOrder, customerName, customerEmail, state) et filterable (customerId, state) - CRUD indexation sur chaque action (create, edit, send, cancel, create-advert) - Recherche AJAX dans tabs Devis et Avis avec debounce + dropdown glassmorphism - Sync admin : boutons syncDevis / syncAdverts + compteurs dans /admin/sync Emails : - MailerService : VCF auto (fiche contact SARL SITECONSEIL) en PJ sur tous les mails, bloc HTML pieces jointes injecte automatiquement (exclut .asc/.p7z/smime) avec icone trombone + taille fichier - Templates : devis_to_sign, devis_signed_client/admin (PJ signed+audit), devis_refused_client/admin, advert_send (PJ + bouton paiement), ndd_expiration - TestMailCommand : option --force-dsn pour envoyer via un DSN SMTP specifique (test prod depuis dev) Commande NDD : - app:ndd:check : verifie expiration domaines <= 30j, envoie mail groupe a monitor@siteconseil.fr - Cron quotidien 8h (docker + ansible) Divers : - Titles templates : CRM SITECONSEIL -> SARL SITECONSEIL (52 fichiers) - VAULT_URL dev = https://kms.esy-web.dev (comme prod) - app.js : initDevisLines (repeater + drag & drop), initTabSearch, toggle refus devis - app.scss : styles drag & drop - setasign/fpdi-fpdf installe pour fusion PDF - 5 migrations Doctrine Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:44:35 +02:00
}
2026-04-01 15:42:52 +02:00
if ($canUnsubscribe) {
$this->addUnsubscribeHeaders($email, $to);
}
$this->send($email);
feat: gestion complete Devis + Avis de paiement + DocuSeal signature + mails Devis : - Entity DevisLine (pos, title, description, priceHt) liee a Devis (OneToMany cascade/orphanRemoval) - Champs ajoutes sur Devis : customer (ManyToOne), submissionId, state machine (created/send/accepted/refused/cancel), raisonMessage, totaux HT/TVA/TTC, updatedAt, setUpdatedAt public - Relation Devis <-> Advert changee de ManyToOne a OneToOne nullable - Vich Attribute (migration Annotation -> Attribute) pour unsignedPdf/signedPdf/auditPdf - DevisController CRUD complet : create (form repeater lignes + boutons rapides TarificationService), edit, cancel (libere OrderNumber), generate-pdf, send, resend, create-advert, events - DevisPdf (FPDF/FPDI) : header legacy (logo, num, date, client), body lignes, summary totaux, footer SITECONSEIL + pagination, champ signature DocuSeal sur page devis + derniere page CGV - OrderNumberService : preview() et generate() reutilisent les OrderNumber non utilises (isUsed=false) en priorite - OrderNumber::markAsUnused() ajoute DocuSeal integration devis : - DocuSealService : sendDevisForSignature (avec completed_redirect_url), resendDevisSignature (archive ancienne submission), getSubmitterSlug, downloadSignedDevis (sauvegarde via Vich UploadedFile test=true) - WebhookDocuSealController : dispatch par doc_type devis/attestation, handleDevisEvent (form.completed -> STATE_ACCEPTED + download PDF signe/audit, form.declined -> STATE_REFUSED + raison) - DocusealEvent entity pour tracer form.viewed/started/completed/declined en temps reel - Page evenements admin /admin/devis/{id}/events avec badges et payload JSON Signature client : - DevisProcessController : page publique /devis/process/{id}/{hmac} securisee par HMAC, boutons Signer (redirect DocuSeal) / Refuser (motif optionnel) - Pages confirmation : signed.html.twig (merci + recap) et refused.html.twig (confirmation refus + motif) - Nelmio whitelist : signature.esy-web.dev + signature.siteconseil.fr Avis de paiement : - Entity AdvertLine (pos, title, description, priceHt) liee a Advert - Advert refactorise : customer, state, totaux, raisonMessage, submissionId, advertFile (Vich mapping advert_pdf), lines collection, updatedAt - AdvertController : generate-pdf, send (mail + PJ + lien paiement), resend (rappel), cancel (delie devis, libere OrderNumber), search Meilisearch - AdvertPdf (FPDF/FPDI) : QR code Endroid pointant vers /order/{numOrder}, texte "Scannez pour payer" - OrderPaymentController : page publique /order/{numOrder} avec detail prestations, totaux, options paiement (placeholder) - Creation auto depuis devis signe : copie client, totaux, lignes, meme OrderNumber Meilisearch : - Index customer_devis et customer_advert avec searchable (numOrder, customerName, customerEmail, state) et filterable (customerId, state) - CRUD indexation sur chaque action (create, edit, send, cancel, create-advert) - Recherche AJAX dans tabs Devis et Avis avec debounce + dropdown glassmorphism - Sync admin : boutons syncDevis / syncAdverts + compteurs dans /admin/sync Emails : - MailerService : VCF auto (fiche contact SARL SITECONSEIL) en PJ sur tous les mails, bloc HTML pieces jointes injecte automatiquement (exclut .asc/.p7z/smime) avec icone trombone + taille fichier - Templates : devis_to_sign, devis_signed_client/admin (PJ signed+audit), devis_refused_client/admin, advert_send (PJ + bouton paiement), ndd_expiration - TestMailCommand : option --force-dsn pour envoyer via un DSN SMTP specifique (test prod depuis dev) Commande NDD : - app:ndd:check : verifie expiration domaines <= 30j, envoie mail groupe a monitor@siteconseil.fr - Cron quotidien 8h (docker + ansible) Divers : - Titles templates : CRM SITECONSEIL -> SARL SITECONSEIL (52 fichiers) - VAULT_URL dev = https://kms.esy-web.dev (comme prod) - app.js : initDevisLines (repeater + drag & drop), initTabSearch, toggle refus devis - app.scss : styles drag & drop - setasign/fpdi-fpdf installe pour fusion PDF - 5 migrations Doctrine Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:44:35 +02:00
// Nettoyage du fichier VCF temporaire
if (null !== $vcfPath) {
@unlink($vcfPath);
}
2026-04-01 15:42:52 +02:00
}
private function isWhitelisted(string $email): bool
{
return strtolower(trim($email)) === strtolower($this->adminEmail);
}
feat: gestion complete Devis + Avis de paiement + DocuSeal signature + mails Devis : - Entity DevisLine (pos, title, description, priceHt) liee a Devis (OneToMany cascade/orphanRemoval) - Champs ajoutes sur Devis : customer (ManyToOne), submissionId, state machine (created/send/accepted/refused/cancel), raisonMessage, totaux HT/TVA/TTC, updatedAt, setUpdatedAt public - Relation Devis <-> Advert changee de ManyToOne a OneToOne nullable - Vich Attribute (migration Annotation -> Attribute) pour unsignedPdf/signedPdf/auditPdf - DevisController CRUD complet : create (form repeater lignes + boutons rapides TarificationService), edit, cancel (libere OrderNumber), generate-pdf, send, resend, create-advert, events - DevisPdf (FPDF/FPDI) : header legacy (logo, num, date, client), body lignes, summary totaux, footer SITECONSEIL + pagination, champ signature DocuSeal sur page devis + derniere page CGV - OrderNumberService : preview() et generate() reutilisent les OrderNumber non utilises (isUsed=false) en priorite - OrderNumber::markAsUnused() ajoute DocuSeal integration devis : - DocuSealService : sendDevisForSignature (avec completed_redirect_url), resendDevisSignature (archive ancienne submission), getSubmitterSlug, downloadSignedDevis (sauvegarde via Vich UploadedFile test=true) - WebhookDocuSealController : dispatch par doc_type devis/attestation, handleDevisEvent (form.completed -> STATE_ACCEPTED + download PDF signe/audit, form.declined -> STATE_REFUSED + raison) - DocusealEvent entity pour tracer form.viewed/started/completed/declined en temps reel - Page evenements admin /admin/devis/{id}/events avec badges et payload JSON Signature client : - DevisProcessController : page publique /devis/process/{id}/{hmac} securisee par HMAC, boutons Signer (redirect DocuSeal) / Refuser (motif optionnel) - Pages confirmation : signed.html.twig (merci + recap) et refused.html.twig (confirmation refus + motif) - Nelmio whitelist : signature.esy-web.dev + signature.siteconseil.fr Avis de paiement : - Entity AdvertLine (pos, title, description, priceHt) liee a Advert - Advert refactorise : customer, state, totaux, raisonMessage, submissionId, advertFile (Vich mapping advert_pdf), lines collection, updatedAt - AdvertController : generate-pdf, send (mail + PJ + lien paiement), resend (rappel), cancel (delie devis, libere OrderNumber), search Meilisearch - AdvertPdf (FPDF/FPDI) : QR code Endroid pointant vers /order/{numOrder}, texte "Scannez pour payer" - OrderPaymentController : page publique /order/{numOrder} avec detail prestations, totaux, options paiement (placeholder) - Creation auto depuis devis signe : copie client, totaux, lignes, meme OrderNumber Meilisearch : - Index customer_devis et customer_advert avec searchable (numOrder, customerName, customerEmail, state) et filterable (customerId, state) - CRUD indexation sur chaque action (create, edit, send, cancel, create-advert) - Recherche AJAX dans tabs Devis et Avis avec debounce + dropdown glassmorphism - Sync admin : boutons syncDevis / syncAdverts + compteurs dans /admin/sync Emails : - MailerService : VCF auto (fiche contact SARL SITECONSEIL) en PJ sur tous les mails, bloc HTML pieces jointes injecte automatiquement (exclut .asc/.p7z/smime) avec icone trombone + taille fichier - Templates : devis_to_sign, devis_signed_client/admin (PJ signed+audit), devis_refused_client/admin, advert_send (PJ + bouton paiement), ndd_expiration - TestMailCommand : option --force-dsn pour envoyer via un DSN SMTP specifique (test prod depuis dev) Commande NDD : - app:ndd:check : verifie expiration domaines <= 30j, envoie mail groupe a monitor@siteconseil.fr - Cron quotidien 8h (docker + ansible) Divers : - Titles templates : CRM SITECONSEIL -> SARL SITECONSEIL (52 fichiers) - VAULT_URL dev = https://kms.esy-web.dev (comme prod) - app.js : initDevisLines (repeater + drag & drop), initTabSearch, toggle refus devis - app.scss : styles drag & drop - setasign/fpdi-fpdf installe pour fusion PDF - 5 migrations Doctrine Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:44:35 +02:00
/**
* Injecte un bloc HTML listant les pieces jointes dans le corps du mail,
* juste avant le footer dark (#111827). Exclut .asc, .p7z et smime.
*
* @param array<array{path: string, name: string}> $attachments
*/
private function injectAttachmentsList(string $html, array $attachments): string
{
$excluded = ['.asc', '.p7z'];
$filtered = [];
foreach ($attachments as $a) {
$name = $a['name'] ?? basename($a['path']);
$path = $a['path'] ?? '';
$ext = strtolower(pathinfo($name, \PATHINFO_EXTENSION));
if (\in_array('.'.$ext, $excluded, true) || str_contains(strtolower($name), 'smime')) {
continue;
}
$size = file_exists($path) ? filesize($path) : 0;
$filtered[] = ['name' => $name, 'size' => $size];
}
if ([] === $filtered) {
return $html;
}
$items = '';
foreach ($filtered as $f) {
$sizeStr = $this->formatFileSize($f['size']);
$items .= '<tr>'
.'<td style="background-color: #f9fafb; border: 1px solid #e5e5e5; padding-top: 12px; padding-bottom: 12px; padding-left: 16px; padding-right: 16px;">'
.'<table role="presentation" cellpadding="0" cellspacing="0" border="0"><tbody><tr>'
.'<td style="padding-right: 12px; vertical-align: middle;"><span style="font-size: 24px;">&#128206;</span></td>'
.'<td style="vertical-align: middle;">'
.'<p style="font-family: Arial, Helvetica, sans-serif; font-size: 13px; font-weight: 700; margin-top: 0; margin-right: 0; margin-bottom: 2px; margin-left: 0;">'.htmlspecialchars($f['name'], \ENT_QUOTES, 'UTF-8').'</p>'
.'<p style="font-family: Arial, Helvetica, sans-serif; font-size: 11px; color: #888888; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0;">Piece jointe ('.$sizeStr.')</p>'
.'</td>'
.'</tr></tbody></table>'
.'</td>'
.'</tr>';
}
$block = '<tr><td style="padding-top: 0; padding-bottom: 24px; padding-left: 32px; padding-right: 32px;">'
.'<p style="font-family: Arial, Helvetica, sans-serif; font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px; color: #9ca3af; margin-top: 0; margin-bottom: 8px;">Pieces jointes</p>'
.'<table role="presentation" width="100%" cellpadding="0" cellspacing="4" border="0">'
.$items
.'</table>'
.'</td></tr>';
// Injecte avant le footer dark
$marker = '<td align="center" style="background-color: #111827';
$pos = strpos($html, $marker);
if (false !== $pos) {
$trPos = strrpos(substr($html, 0, $pos), '<tr>');
if (false !== $trPos) {
return substr($html, 0, $trPos).$block.substr($html, $trPos);
}
}
return $html;
}
/**
feat: comptabilite + prestataires + rapport financier + stats dynamiques Comptabilite (Super Admin) : - ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE (journal ventes, grand livre, FEC, balance agee, reglements, commissions Stripe 1.5%+0.25E, couts services) - Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli, tableau pagine, champ signature DocuSeal - Signature electronique DocuSeal + callback + envoi email signe avec template dedie (compta_export_signed.html.twig) - Rapport financier public (RapportFinancierPdf) : recettes par service, depenses (Stripe, infra, prestataires), bilan excedent/deficit - Codes comptables clients EC-XXXX (plus de 411xxx) Prestataires (Super Admin) : - Entite Prestataire (raisonSociale, siret, email, phone, adresse) - Entite FacturePrestataire (numFacture, montantHt, montantTtc, year, month, isPaid, PDF via Vich) - CRUD complet avec recherche SIRET via proxy API data.gouv.fr - Commande cron app:reminder:factures-prestataire (5 du mois) - Factures prestataires integrees dans export couts services - Sidebar Super Admin : entree Prestataires + Comptabilite Stats (/admin/stats) : - Cout prestataire dynamique depuis FacturePrestataire - Fusion Infra + Prestataire en "Cout de fonctionnement" - Commission Stripe corrigee (1.5% + 0.25E par transaction) Divers : - DocuSealService::sendComptaForSignature() + getApi() - Customer::generateCodeComptable() format EC-XXXX-XXXXX - Protection double prefixe EC- a la creation client - Bouton regenerer PDF cache quand advert state=accepted - Modals sans script inline (data-modal-open/close dans app.js) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
* Genere un fichier VCF (vCard 3.0) pour la fiche contact Association E-Cosplay.
feat: gestion complete Devis + Avis de paiement + DocuSeal signature + mails Devis : - Entity DevisLine (pos, title, description, priceHt) liee a Devis (OneToMany cascade/orphanRemoval) - Champs ajoutes sur Devis : customer (ManyToOne), submissionId, state machine (created/send/accepted/refused/cancel), raisonMessage, totaux HT/TVA/TTC, updatedAt, setUpdatedAt public - Relation Devis <-> Advert changee de ManyToOne a OneToOne nullable - Vich Attribute (migration Annotation -> Attribute) pour unsignedPdf/signedPdf/auditPdf - DevisController CRUD complet : create (form repeater lignes + boutons rapides TarificationService), edit, cancel (libere OrderNumber), generate-pdf, send, resend, create-advert, events - DevisPdf (FPDF/FPDI) : header legacy (logo, num, date, client), body lignes, summary totaux, footer SITECONSEIL + pagination, champ signature DocuSeal sur page devis + derniere page CGV - OrderNumberService : preview() et generate() reutilisent les OrderNumber non utilises (isUsed=false) en priorite - OrderNumber::markAsUnused() ajoute DocuSeal integration devis : - DocuSealService : sendDevisForSignature (avec completed_redirect_url), resendDevisSignature (archive ancienne submission), getSubmitterSlug, downloadSignedDevis (sauvegarde via Vich UploadedFile test=true) - WebhookDocuSealController : dispatch par doc_type devis/attestation, handleDevisEvent (form.completed -> STATE_ACCEPTED + download PDF signe/audit, form.declined -> STATE_REFUSED + raison) - DocusealEvent entity pour tracer form.viewed/started/completed/declined en temps reel - Page evenements admin /admin/devis/{id}/events avec badges et payload JSON Signature client : - DevisProcessController : page publique /devis/process/{id}/{hmac} securisee par HMAC, boutons Signer (redirect DocuSeal) / Refuser (motif optionnel) - Pages confirmation : signed.html.twig (merci + recap) et refused.html.twig (confirmation refus + motif) - Nelmio whitelist : signature.esy-web.dev + signature.siteconseil.fr Avis de paiement : - Entity AdvertLine (pos, title, description, priceHt) liee a Advert - Advert refactorise : customer, state, totaux, raisonMessage, submissionId, advertFile (Vich mapping advert_pdf), lines collection, updatedAt - AdvertController : generate-pdf, send (mail + PJ + lien paiement), resend (rappel), cancel (delie devis, libere OrderNumber), search Meilisearch - AdvertPdf (FPDF/FPDI) : QR code Endroid pointant vers /order/{numOrder}, texte "Scannez pour payer" - OrderPaymentController : page publique /order/{numOrder} avec detail prestations, totaux, options paiement (placeholder) - Creation auto depuis devis signe : copie client, totaux, lignes, meme OrderNumber Meilisearch : - Index customer_devis et customer_advert avec searchable (numOrder, customerName, customerEmail, state) et filterable (customerId, state) - CRUD indexation sur chaque action (create, edit, send, cancel, create-advert) - Recherche AJAX dans tabs Devis et Avis avec debounce + dropdown glassmorphism - Sync admin : boutons syncDevis / syncAdverts + compteurs dans /admin/sync Emails : - MailerService : VCF auto (fiche contact SARL SITECONSEIL) en PJ sur tous les mails, bloc HTML pieces jointes injecte automatiquement (exclut .asc/.p7z/smime) avec icone trombone + taille fichier - Templates : devis_to_sign, devis_signed_client/admin (PJ signed+audit), devis_refused_client/admin, advert_send (PJ + bouton paiement), ndd_expiration - TestMailCommand : option --force-dsn pour envoyer via un DSN SMTP specifique (test prod depuis dev) Commande NDD : - app:ndd:check : verifie expiration domaines <= 30j, envoie mail groupe a monitor@siteconseil.fr - Cron quotidien 8h (docker + ansible) Divers : - Titles templates : CRM SITECONSEIL -> SARL SITECONSEIL (52 fichiers) - VAULT_URL dev = https://kms.esy-web.dev (comme prod) - app.js : initDevisLines (repeater + drag & drop), initTabSearch, toggle refus devis - app.scss : styles drag & drop - setasign/fpdi-fpdf installe pour fusion PDF - 5 migrations Doctrine Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:44:35 +02:00
*/
private function generateVcf(): ?string
{
$vcf = implode("\r\n", [
'BEGIN:VCARD',
'VERSION:3.0',
feat: comptabilite + prestataires + rapport financier + stats dynamiques Comptabilite (Super Admin) : - ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE (journal ventes, grand livre, FEC, balance agee, reglements, commissions Stripe 1.5%+0.25E, couts services) - Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli, tableau pagine, champ signature DocuSeal - Signature electronique DocuSeal + callback + envoi email signe avec template dedie (compta_export_signed.html.twig) - Rapport financier public (RapportFinancierPdf) : recettes par service, depenses (Stripe, infra, prestataires), bilan excedent/deficit - Codes comptables clients EC-XXXX (plus de 411xxx) Prestataires (Super Admin) : - Entite Prestataire (raisonSociale, siret, email, phone, adresse) - Entite FacturePrestataire (numFacture, montantHt, montantTtc, year, month, isPaid, PDF via Vich) - CRUD complet avec recherche SIRET via proxy API data.gouv.fr - Commande cron app:reminder:factures-prestataire (5 du mois) - Factures prestataires integrees dans export couts services - Sidebar Super Admin : entree Prestataires + Comptabilite Stats (/admin/stats) : - Cout prestataire dynamique depuis FacturePrestataire - Fusion Infra + Prestataire en "Cout de fonctionnement" - Commission Stripe corrigee (1.5% + 0.25E par transaction) Divers : - DocuSealService::sendComptaForSignature() + getApi() - Customer::generateCodeComptable() format EC-XXXX-XXXXX - Protection double prefixe EC- a la creation client - Bouton regenerer PDF cache quand advert state=accepted - Modals sans script inline (data-modal-open/close dans app.js) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
'N:E-Cosplay;Association;;;',
'FN:Association E-Cosplay',
'ORG:Association E-Cosplay',
'TEL;TYPE=WORK,VOICE:+33679348802',
'EMAIL;TYPE=INTERNET,PREF:contact@e-cosplay.fr',
'EMAIL;TYPE=INTERNET:contact@e-cosplay.fr',
'ADR;TYPE=WORK:;;42 rue de Saint-Quentin;Beautor;;02800;France',
'URL:https://www.e-cosplay.fr',
'URL:https://crm.e-cosplay.fr',
'NOTE:SIREN 943121517 - SIRET 943 121 517 00011 - APE 9329Z',
'CATEGORIES:Association,Cosplay,Evenementiel',
feat: gestion complete Devis + Avis de paiement + DocuSeal signature + mails Devis : - Entity DevisLine (pos, title, description, priceHt) liee a Devis (OneToMany cascade/orphanRemoval) - Champs ajoutes sur Devis : customer (ManyToOne), submissionId, state machine (created/send/accepted/refused/cancel), raisonMessage, totaux HT/TVA/TTC, updatedAt, setUpdatedAt public - Relation Devis <-> Advert changee de ManyToOne a OneToOne nullable - Vich Attribute (migration Annotation -> Attribute) pour unsignedPdf/signedPdf/auditPdf - DevisController CRUD complet : create (form repeater lignes + boutons rapides TarificationService), edit, cancel (libere OrderNumber), generate-pdf, send, resend, create-advert, events - DevisPdf (FPDF/FPDI) : header legacy (logo, num, date, client), body lignes, summary totaux, footer SITECONSEIL + pagination, champ signature DocuSeal sur page devis + derniere page CGV - OrderNumberService : preview() et generate() reutilisent les OrderNumber non utilises (isUsed=false) en priorite - OrderNumber::markAsUnused() ajoute DocuSeal integration devis : - DocuSealService : sendDevisForSignature (avec completed_redirect_url), resendDevisSignature (archive ancienne submission), getSubmitterSlug, downloadSignedDevis (sauvegarde via Vich UploadedFile test=true) - WebhookDocuSealController : dispatch par doc_type devis/attestation, handleDevisEvent (form.completed -> STATE_ACCEPTED + download PDF signe/audit, form.declined -> STATE_REFUSED + raison) - DocusealEvent entity pour tracer form.viewed/started/completed/declined en temps reel - Page evenements admin /admin/devis/{id}/events avec badges et payload JSON Signature client : - DevisProcessController : page publique /devis/process/{id}/{hmac} securisee par HMAC, boutons Signer (redirect DocuSeal) / Refuser (motif optionnel) - Pages confirmation : signed.html.twig (merci + recap) et refused.html.twig (confirmation refus + motif) - Nelmio whitelist : signature.esy-web.dev + signature.siteconseil.fr Avis de paiement : - Entity AdvertLine (pos, title, description, priceHt) liee a Advert - Advert refactorise : customer, state, totaux, raisonMessage, submissionId, advertFile (Vich mapping advert_pdf), lines collection, updatedAt - AdvertController : generate-pdf, send (mail + PJ + lien paiement), resend (rappel), cancel (delie devis, libere OrderNumber), search Meilisearch - AdvertPdf (FPDF/FPDI) : QR code Endroid pointant vers /order/{numOrder}, texte "Scannez pour payer" - OrderPaymentController : page publique /order/{numOrder} avec detail prestations, totaux, options paiement (placeholder) - Creation auto depuis devis signe : copie client, totaux, lignes, meme OrderNumber Meilisearch : - Index customer_devis et customer_advert avec searchable (numOrder, customerName, customerEmail, state) et filterable (customerId, state) - CRUD indexation sur chaque action (create, edit, send, cancel, create-advert) - Recherche AJAX dans tabs Devis et Avis avec debounce + dropdown glassmorphism - Sync admin : boutons syncDevis / syncAdverts + compteurs dans /admin/sync Emails : - MailerService : VCF auto (fiche contact SARL SITECONSEIL) en PJ sur tous les mails, bloc HTML pieces jointes injecte automatiquement (exclut .asc/.p7z/smime) avec icone trombone + taille fichier - Templates : devis_to_sign, devis_signed_client/admin (PJ signed+audit), devis_refused_client/admin, advert_send (PJ + bouton paiement), ndd_expiration - TestMailCommand : option --force-dsn pour envoyer via un DSN SMTP specifique (test prod depuis dev) Commande NDD : - app:ndd:check : verifie expiration domaines <= 30j, envoie mail groupe a monitor@siteconseil.fr - Cron quotidien 8h (docker + ansible) Divers : - Titles templates : CRM SITECONSEIL -> SARL SITECONSEIL (52 fichiers) - VAULT_URL dev = https://kms.esy-web.dev (comme prod) - app.js : initDevisLines (repeater + drag & drop), initTabSearch, toggle refus devis - app.scss : styles drag & drop - setasign/fpdi-fpdf installe pour fusion PDF - 5 migrations Doctrine Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:44:35 +02:00
'REV:'.date('Ymd\THis\Z'),
'END:VCARD',
]);
$tmpPath = tempnam(sys_get_temp_dir(), 'vcf_');
if (false === $tmpPath) {
return null;
}
file_put_contents($tmpPath, $vcf);
return $tmpPath;
}
private function formatFileSize(int $bytes): string
{
if ($bytes >= 1048576) {
return number_format($bytes / 1048576, 1, ',', ' ').' Mo';
}
if ($bytes >= 1024) {
return number_format($bytes / 1024, 0, ',', ' ').' Ko';
}
return $bytes.' o';
}
2026-04-01 15:42:52 +02:00
private function addUnsubscribeHeaders(Email $email, string $to): void
{
$token = $this->unsubscribeManager->generateToken($to);
$unsubscribeUrl = $this->urlGenerator->generate('app_unsubscribe', [
'email' => $to,
'token' => $token,
], UrlGeneratorInterface::ABSOLUTE_URL);
$email->getHeaders()->addTextHeader(
'List-Unsubscribe',
feat: comptabilite + prestataires + rapport financier + stats dynamiques Comptabilite (Super Admin) : - ComptabiliteController avec 7 exports CSV/JSON compatibles SAGE (journal ventes, grand livre, FEC, balance agee, reglements, commissions Stripe 1.5%+0.25E, couts services) - Export PDF via ComptaPdf (FPDF) avec bloc legal pre-rempli, tableau pagine, champ signature DocuSeal - Signature electronique DocuSeal + callback + envoi email signe avec template dedie (compta_export_signed.html.twig) - Rapport financier public (RapportFinancierPdf) : recettes par service, depenses (Stripe, infra, prestataires), bilan excedent/deficit - Codes comptables clients EC-XXXX (plus de 411xxx) Prestataires (Super Admin) : - Entite Prestataire (raisonSociale, siret, email, phone, adresse) - Entite FacturePrestataire (numFacture, montantHt, montantTtc, year, month, isPaid, PDF via Vich) - CRUD complet avec recherche SIRET via proxy API data.gouv.fr - Commande cron app:reminder:factures-prestataire (5 du mois) - Factures prestataires integrees dans export couts services - Sidebar Super Admin : entree Prestataires + Comptabilite Stats (/admin/stats) : - Cout prestataire dynamique depuis FacturePrestataire - Fusion Infra + Prestataire en "Cout de fonctionnement" - Commission Stripe corrigee (1.5% + 0.25E par transaction) Divers : - DocuSealService::sendComptaForSignature() + getApi() - Customer::generateCodeComptable() format EC-XXXX-XXXXX - Protection double prefixe EC- a la creation client - Bouton regenerer PDF cache quand advert state=accepted - Modals sans script inline (data-modal-open/close dans app.js) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:39:31 +02:00
sprintf('<%s>, <mailto:unsubscribe@e-cosplay.fr?subject=unsubscribe-%s>', $unsubscribeUrl, urlencode($to))
2026-04-01 15:42:52 +02:00
);
$email->getHeaders()->addTextHeader('List-Unsubscribe-Post', 'List-Unsubscribe=One-Click');
}
}