Files
crm_ecosplay/templates/order/payment.html.twig

112 lines
5.9 KiB
Twig
Raw Normal View History

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
{% extends 'base.html.twig' %}
{% block title %}Paiement - {{ advert.orderNumber.numOrder }} - SARL SITECONSEIL{% endblock %}
{% block body %}
<main class="max-w-3xl mx-auto px-4 py-10">
<div class="glass p-8 mb-6">
<div class="flex items-center justify-between mb-6">
<div>
<p class="text-[10px] font-bold uppercase tracking-widest text-gray-400">Avis de paiement</p>
<h1 class="text-2xl font-bold heading-page font-mono">{{ advert.orderNumber.numOrder }}</h1>
<p class="text-xs text-gray-500 mt-1">Date : {{ advert.createdAt|date('d/m/Y') }}</p>
</div>
<div class="text-right">
{% if advert.state == 'cancel' %}
<span class="px-3 py-1 bg-gray-100 text-gray-600 font-bold uppercase text-xs rounded-lg">Annule</span>
{% else %}
<span class="px-3 py-1 bg-yellow-100 text-yellow-800 font-bold uppercase text-xs rounded-lg">En attente de paiement</span>
{% endif %}
</div>
</div>
{% if customer %}
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8">
<div class="glass p-4">
<p class="text-[10px] font-bold uppercase tracking-widest text-gray-400 mb-2">Emetteur</p>
<p class="text-sm font-bold">SARL SITECONSEIL</p>
<p class="text-xs text-gray-500 mt-1">
27 rue Le Sérurier<br>
02100 Saint-Quentin, France<br>
SIREN 943121517
</p>
</div>
<div class="glass p-4">
<p class="text-[10px] font-bold uppercase tracking-widest text-gray-400 mb-2">Client</p>
<p class="text-sm font-bold">{{ customer.fullName }}</p>
<p class="text-xs text-gray-500 mt-1">
{% if customer.raisonSociale %}{{ customer.raisonSociale }}<br>{% endif %}
{% if customer.address %}{{ customer.address }}<br>{% endif %}
{% if customer.zipCode or customer.city %}{{ customer.zipCode }} {{ customer.city }}<br>{% endif %}
{% if customer.email %}{{ customer.email }}{% endif %}
</p>
</div>
</div>
{% endif %}
<h2 class="text-sm font-bold uppercase tracking-wider mb-3">Detail des prestations</h2>
<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-3 text-left font-bold uppercase text-xs tracking-widest">#</th>
<th class="px-4 py-3 text-left font-bold uppercase text-xs tracking-widest">Prestation</th>
<th class="px-4 py-3 text-right font-bold uppercase text-xs tracking-widest">Prix HT</th>
</tr>
</thead>
<tbody>
{% for line in advert.lines %}
<tr class="border-b border-white/20">
<td class="px-4 py-3 text-gray-400 text-xs">{{ loop.index }}</td>
<td class="px-4 py-3">
<div class="font-bold">{{ line.title }}</div>
{% if line.description %}
<div class="text-xs text-gray-500 whitespace-pre-wrap mt-1">{{ line.description }}</div>
{% endif %}
</td>
<td class="px-4 py-3 text-right font-mono">{{ line.priceHt }} &euro;</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="flex justify-end mb-8">
<div class="w-full max-w-xs glass p-4">
<div class="flex justify-between text-xs text-gray-500 mb-1">
<span class="font-bold uppercase tracking-widest">Total HT</span>
<span class="font-mono">{{ advert.totalHt }} &euro;</span>
</div>
<div class="flex justify-between text-xs text-gray-500 mb-2">
<span class="font-bold uppercase tracking-widest">TVA 20%</span>
<span class="font-mono">{{ advert.totalTva }} &euro;</span>
</div>
<div class="flex justify-between text-base font-bold border-t border-white/30 pt-2">
<span class="uppercase tracking-widest">Total TTC</span>
<span class="font-mono">{{ advert.totalTtc }} &euro;</span>
</div>
</div>
</div>
{% if advert.state != 'cancel' %}
<div class="glass p-6 text-center">
<h2 class="text-sm font-bold uppercase tracking-wider mb-4">Options de paiement</h2>
<p class="text-xs text-gray-500 mb-6">Les options de paiement seront disponibles prochainement.</p>
<div class="flex flex-col sm:flex-row gap-3 justify-center">
<button disabled class="px-6 py-4 bg-gray-200 text-gray-400 font-bold uppercase text-xs tracking-widest rounded-lg cursor-not-allowed">
Carte bancaire (bientot)
</button>
<button disabled class="px-6 py-4 bg-gray-200 text-gray-400 font-bold uppercase text-xs tracking-widest rounded-lg cursor-not-allowed">
Virement (bientot)
</button>
</div>
</div>
{% endif %}
</div>
<p class="text-center text-[10px] text-gray-400 uppercase tracking-widest">
Une question ? <a href="mailto:contact@siteconseil.fr" class="text-[#fabf04] hover:underline">contact@siteconseil.fr</a>
</p>
</main>
{% endblock %}