feat: page process echeancier + bouton signature unique + email details
- Page publique /echeancier/process/{id} avec detail complet avant signature
(motif, resume, tableau echeances, conditions)
- Redirige vers page signed si deja signe/actif/termine
- Bouton unique "Envoyer pour signature" / "Renvoyer signature" selon state
- Suppression bouton "Envoyer proposition" (remplace par signature directe)
- Email signature : ajout bouton "Voir les details" + lien processUrl
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -294,10 +294,15 @@ class EcheancierController extends AbstractController
|
||||
$slug = $docuSeal->getSubmitterSlug($submitterId);
|
||||
$signUrl = null !== $slug ? rtrim($docuSealUrl, '/').'/s/'.$slug : null;
|
||||
|
||||
$processUrl = $urlGenerator->generate('app_echeancier_process', [
|
||||
'id' => $echeancier->getId(),
|
||||
], UrlGeneratorInterface::ABSOLUTE_URL);
|
||||
|
||||
$html = $twig->render('emails/echeancier_signature.html.twig', [
|
||||
'customer' => $customer,
|
||||
'echeancier' => $echeancier,
|
||||
'signUrl' => $signUrl,
|
||||
'processUrl' => $processUrl,
|
||||
]);
|
||||
|
||||
$mailer->sendEmail(
|
||||
|
||||
@@ -15,6 +15,30 @@ class EcheancierProcessController extends AbstractController
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Page de detail echeancier avant signature (publique).
|
||||
*/
|
||||
#[Route('/echeancier/process/{id}', name: 'app_echeancier_process', requirements: ['id' => '\d+'])]
|
||||
public function process(int $id): Response
|
||||
{
|
||||
$echeancier = $this->em->getRepository(Echeancier::class)->find($id);
|
||||
if (null === $echeancier) {
|
||||
throw $this->createNotFoundException('Echeancier introuvable.');
|
||||
}
|
||||
|
||||
if (\in_array($echeancier->getState(), [Echeancier::STATE_SIGNED, Echeancier::STATE_ACTIVE, Echeancier::STATE_COMPLETED], true)) {
|
||||
return $this->render('echeancier/signed.html.twig', [
|
||||
'echeancier' => $echeancier,
|
||||
'customer' => $echeancier->getCustomer(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->render('echeancier/process.html.twig', [
|
||||
'echeancier' => $echeancier,
|
||||
'customer' => $echeancier->getCustomer(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback DocuSeal apres signature du client.
|
||||
*/
|
||||
|
||||
@@ -84,19 +84,9 @@
|
||||
Voir PDF
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if echeancier.state == 'draft' and echeancier.pdfUnsigned %}
|
||||
<form method="post" action="{{ path('app_admin_echeancier_send', {id: echeancier.id}) }}" data-confirm="Envoyer la proposition d'echeancier au client ?">
|
||||
<button type="submit" class="btn-gold px-4 py-2 font-bold uppercase text-[10px] tracking-wider text-gray-900">Envoyer proposition</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if echeancier.state in ['draft', 'send'] and echeancier.pdfUnsigned %}
|
||||
<form method="post" action="{{ path('app_admin_echeancier_send_signature', {id: echeancier.id}) }}" data-confirm="Envoyer le PDF pour signature au client via DocuSeal ?">
|
||||
<button type="submit" class="px-4 py-2 bg-purple-500/20 text-purple-700 hover:bg-purple-500 hover:text-white font-bold uppercase text-[10px] tracking-wider transition-all">Envoyer pour signature</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if echeancier.state == 'send' %}
|
||||
<form method="post" action="{{ path('app_admin_echeancier_resend', {id: echeancier.id}) }}" data-confirm="Renvoyer l'email de proposition au client ?">
|
||||
<button type="submit" class="px-4 py-2 bg-purple-500/20 text-purple-700 hover:bg-purple-500 hover:text-white font-bold uppercase text-[10px] tracking-wider transition-all">Renvoyer email</button>
|
||||
<form method="post" action="{{ path('app_admin_echeancier_send_signature', {id: echeancier.id}) }}" data-confirm="{{ echeancier.state == 'send' ? 'Renvoyer le lien de signature au client ?' : 'Envoyer le PDF pour signature au client via DocuSeal ?' }}">
|
||||
<button type="submit" class="px-4 py-2 bg-purple-500/20 text-purple-700 hover:bg-purple-500 hover:text-white font-bold uppercase text-[10px] tracking-wider transition-all">{{ echeancier.state == 'send' ? 'Renvoyer signature' : 'Envoyer pour signature' }}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if echeancier.state == 'signed' %}
|
||||
|
||||
87
templates/echeancier/process.html.twig
Normal file
87
templates/echeancier/process.html.twig
Normal file
@@ -0,0 +1,87 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Echeancier de paiement - Association E-Cosplay{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="min-h-screen flex items-center justify-center p-4" style="background: linear-gradient(135deg, #f5f5f0 0%, #e8e8e0 100%);">
|
||||
<div class="glass-heavy w-full max-w-2xl overflow-hidden">
|
||||
<div class="glass-dark text-white px-8 py-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<img src="/logo.jpg" alt="E-Cosplay" class="h-10 w-auto">
|
||||
<div>
|
||||
<h1 class="text-lg font-bold uppercase tracking-widest">Echeancier de paiement</h1>
|
||||
<p class="text-xs text-white/60">Association E-Cosplay</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-8">
|
||||
<p class="text-sm text-gray-600 mb-6">
|
||||
{% if customer.raisonSociale %}Chez {{ customer.raisonSociale }}{% else %}Bonjour {{ customer.firstName }}{% endif %},
|
||||
veuillez trouver ci-dessous le detail de votre echeancier de paiement.
|
||||
</p>
|
||||
|
||||
{# Motif #}
|
||||
<div class="glass p-4 mb-6">
|
||||
<h2 class="text-[9px] font-bold uppercase tracking-wider text-gray-400 mb-1">Motif</h2>
|
||||
<p class="text-sm font-bold">{{ echeancier.description }}</p>
|
||||
</div>
|
||||
|
||||
{# Resume #}
|
||||
<div class="grid grid-cols-3 gap-3 mb-6">
|
||||
<div class="glass p-3 text-center">
|
||||
<p class="text-[9px] font-bold uppercase tracking-wider text-gray-400">Total HT</p>
|
||||
<p class="text-lg font-bold mt-1">{{ echeancier.totalAmountHt|number_format(2, ',', ' ') }} €</p>
|
||||
</div>
|
||||
<div class="glass p-3 text-center">
|
||||
<p class="text-[9px] font-bold uppercase tracking-wider text-gray-400">Echeances</p>
|
||||
<p class="text-lg font-bold mt-1">{{ echeancier.nbLines }} mois</p>
|
||||
</div>
|
||||
<div class="glass p-3 text-center">
|
||||
<p class="text-[9px] font-bold uppercase tracking-wider text-gray-400">Mensualite</p>
|
||||
<p class="text-lg font-bold mt-1" style="color: #fabf04;">{{ echeancier.monthlyAmount|number_format(2, ',', ' ') }} €</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Tableau echeances #}
|
||||
<div class="glass overflow-hidden mb-6">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="glass-dark text-white">
|
||||
<th class="px-4 py-2 text-left font-bold uppercase text-[10px] tracking-widest">N</th>
|
||||
<th class="px-4 py-2 text-left font-bold uppercase text-[10px] tracking-widest">Date prevue</th>
|
||||
<th class="px-4 py-2 text-right font-bold uppercase text-[10px] tracking-widest">Montant</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for line in echeancier.lines %}
|
||||
<tr class="border-b border-white/20 {{ loop.index is odd ? 'bg-white/30' : '' }}">
|
||||
<td class="px-4 py-2 font-bold">{{ line.position }}</td>
|
||||
<td class="px-4 py-2 text-xs">{{ line.scheduledAt|date('d/m/Y') }}</td>
|
||||
<td class="px-4 py-2 text-right font-bold">{{ line.amount|number_format(2, ',', ' ') }} €</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{# Conditions #}
|
||||
<div class="text-xs text-gray-500 mb-6 space-y-1">
|
||||
<p class="font-bold uppercase text-[9px] tracking-wider text-gray-400 mb-2">Conditions</p>
|
||||
<p>1. Le prelevement sera effectue automatiquement a chaque date prevue via Stripe.</p>
|
||||
<p>2. En cas d'echec de prelevement, une relance sera envoyee par email.</p>
|
||||
<p>3. Apres 2 echecs consecutifs, l'echeancier sera considere en defaut.</p>
|
||||
<p>4. Majoration de 5% du montant total conformement aux CGV (article 11).</p>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-gray-600 mb-4">
|
||||
En signant ce document, vous autorisez le prelevement automatique mensuel du montant indique.
|
||||
</p>
|
||||
|
||||
<p class="text-center text-xs text-gray-400 mt-6">
|
||||
Pour toute question : <a href="mailto:contact@e-cosplay.fr" class="font-bold" style="color: #fabf04;">contact@e-cosplay.fr</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -26,8 +26,18 @@
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
{% if processUrl is defined and processUrl %}
|
||||
<table cellpadding="0" cellspacing="0" style="margin: 16px auto;">
|
||||
<tr>
|
||||
<td style="background-color: #111827; padding: 12px 28px;">
|
||||
<a href="{{ processUrl }}" style="font-family: Arial, Helvetica, sans-serif; font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px; color: #ffffff; text-decoration: none;">Voir les details</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% if signUrl %}
|
||||
<table cellpadding="0" cellspacing="0" style="margin: 24px auto;">
|
||||
<table cellpadding="0" cellspacing="0" style="margin: 16px auto;">
|
||||
<tr>
|
||||
<td style="background-color: #fabf04; padding: 14px 32px;">
|
||||
<a href="{{ signUrl }}" style="font-family: Arial, Helvetica, sans-serif; font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px; color: #111827; text-decoration: none;">Signer l'echeancier</a>
|
||||
|
||||
Reference in New Issue
Block a user