```
✨ feat(facture): Ajoute la gestion des factures et paiements (CRUD, export).
Cette commit ajoute la fonctionnalité de gestion des factures et des paiements,
incluant l'affichage, la recherche, l'export Excel et la pagination.
```
This commit is contained in:
@@ -2,25 +2,179 @@
|
||||
|
||||
namespace App\Controller\Dashboard;
|
||||
|
||||
use App\Entity\ContratsPayments;
|
||||
use App\Logger\AppLogger;
|
||||
use App\Repository\AccountRepository;
|
||||
use App\Repository\ContratsPaymentsRepository;
|
||||
use Knp\Component\Pager\PaginatorInterface;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Fill;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Color;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Vich\UploaderBundle\Templating\Helper\UploaderHelper;
|
||||
|
||||
class FactureController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* Liste des administrateurs
|
||||
*/
|
||||
#[Route(path: '/crm/facture', name: 'app_crm_facture', options: ['sitemap' => false], methods: ['GET'])]
|
||||
public function contrats(AccountRepository $accountRepository, AppLogger $appLogger): Response
|
||||
{
|
||||
$appLogger->record('VIEW', 'Consultation de la liste des facture');
|
||||
return $this->render('dashboard/contrats/facture.twig',[
|
||||
'factures' => [],
|
||||
#[Route(path: '/crm/facture', name: 'app_crm_facture', methods: ['GET'])]
|
||||
public function index(
|
||||
Request $request,
|
||||
ContratsPaymentsRepository $contratsPaymentsRepo,
|
||||
AppLogger $appLogger,
|
||||
UploaderHelper $uploaderHelper,
|
||||
PaginatorInterface $paginator
|
||||
): Response {
|
||||
|
||||
// 1. Gestion des dates par défaut (Début et Fin du mois en cours)
|
||||
$startDateStr = $request->query->get('startDate');
|
||||
$endDateStr = $request->query->get('endDate');
|
||||
$searchTerm = $request->query->get('q', '');
|
||||
|
||||
if (!$startDateStr) {
|
||||
$startDate = new \DateTime('first day of this month 00:00:00');
|
||||
$startDateStr = $startDate->format('Y-m-d');
|
||||
} else {
|
||||
$startDate = new \DateTime($startDateStr . ' 00:00:00');
|
||||
}
|
||||
|
||||
if (!$endDateStr) {
|
||||
$endDate = new \DateTime('last day of this month 23:59:59');
|
||||
$endDateStr = $endDate->format('Y-m-d');
|
||||
} else {
|
||||
$endDate = new \DateTime($endDateStr . ' 23:59:59');
|
||||
}
|
||||
|
||||
// 2. Construction de la requête de recherche (QueryBuilder)
|
||||
// On récupère une instance de QueryBuilder pour la pagination ou l'export
|
||||
$queryBuilder = $contratsPaymentsRepo->createQueryBuilder('p')
|
||||
->leftJoin('p.contrat', 'c')
|
||||
->leftJoin('c.customer', 'u')
|
||||
->where('p.paymentAt BETWEEN :start AND :end')
|
||||
->setParameter('start', $startDate)
|
||||
->setParameter('end', $endDate)
|
||||
->orderBy('p.paymentAt', 'DESC');
|
||||
|
||||
if (!empty($searchTerm)) {
|
||||
$queryBuilder->andWhere('u.name LIKE :q OR u.surname LIKE :q OR c.numReservation LIKE :q OR p.type LIKE :q')
|
||||
->setParameter('q', '%' . $searchTerm . '%');
|
||||
}
|
||||
|
||||
// 3. Gestion de l'extraction Excel
|
||||
if ($request->query->has('extract')) {
|
||||
// Pour l'export, on récupère tous les résultats filtrés sans pagination
|
||||
$allFilteredPayments = $queryBuilder->getQuery()->getResult();
|
||||
|
||||
// On ne garde que les paiements complétés pour l'export comptable
|
||||
$dataToExport = array_filter($allFilteredPayments, function($p) {
|
||||
return $p->getState() === 'complete';
|
||||
});
|
||||
|
||||
if (empty($dataToExport)) {
|
||||
$this->addFlash('warning', 'Aucune donnée validée à exporter pour cette période.');
|
||||
return $this->redirectToRoute('app_crm_facture', [
|
||||
'startDate' => $startDateStr,
|
||||
'endDate' => $endDateStr,
|
||||
'q' => $searchTerm
|
||||
]);
|
||||
}
|
||||
|
||||
$appLogger->record('EXPORT_EXCEL', "Export des factures du {$startDateStr} au {$endDateStr}");
|
||||
|
||||
return $this->generateExcelExport(
|
||||
$dataToExport,
|
||||
$uploaderHelper,
|
||||
$request->getSchemeAndHttpHost(),
|
||||
$startDateStr,
|
||||
$endDateStr
|
||||
);
|
||||
}
|
||||
|
||||
// 4. Pagination des résultats pour l'affichage Web
|
||||
$pagination = $paginator->paginate(
|
||||
$queryBuilder, // Query object, pas le résultat final
|
||||
$request->query->getInt('page', 1), // Numéro de page
|
||||
15 // Nombre d'éléments par page
|
||||
);
|
||||
|
||||
return $this->render('dashboard/contrats/facture.twig', [
|
||||
'pagination' => $pagination,
|
||||
'startDate' => $startDateStr,
|
||||
'endDate' => $endDateStr,
|
||||
'searchTerm' => $searchTerm,
|
||||
'active' => $request->query->get('active', 'facture'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un fichier Excel XLSX formaté
|
||||
*/
|
||||
private function generateExcelExport(array $payments, UploaderHelper $uploaderHelper, string $host, string $start, string $end): StreamedResponse
|
||||
{
|
||||
$spreadsheet = new Spreadsheet();
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
$sheet->setTitle('Export Compta');
|
||||
|
||||
// Styles des en-têtes
|
||||
$headers = [
|
||||
'A1' => 'DATE PAIEMENT',
|
||||
'B1' => 'NOM DU CLIENT',
|
||||
'C1' => 'RÉF. RÉSERVATION',
|
||||
'D1' => 'MODE DE PAIEMENT',
|
||||
'E1' => 'MONTANT TTC',
|
||||
'F1' => 'STATUT',
|
||||
'G1' => 'LIEN JUSTIFICATIF'
|
||||
];
|
||||
|
||||
foreach ($headers as $cell => $label) {
|
||||
$sheet->setCellValue($cell, $label);
|
||||
$sheet->getStyle($cell)->getFont()->setBold(true)->setColor(new Color('FFFFFF'));
|
||||
$sheet->getStyle($cell)->getFill()->setFillType(Fill::FILL_SOLID)->getStartColor()->setARGB('4F46E5');
|
||||
}
|
||||
|
||||
$row = 2;
|
||||
foreach ($payments as $payment) {
|
||||
$contrat = $payment->getContrat();
|
||||
$customer = $contrat ? $contrat->getCustomer() : null;
|
||||
|
||||
$sheet->setCellValue('A' . $row, $payment->getPaymentAt() ? $payment->getPaymentAt()->format('d/m/Y H:i') : '');
|
||||
$sheet->setCellValue('B' . $row, $customer ? strtoupper((string)$customer->getName()) . ' ' . $customer->getSurname() : 'Inconnu');
|
||||
$sheet->setCellValue('C' . $row, $contrat ? $contrat->getNumReservation() : 'N/A');
|
||||
$sheet->setCellValue('D' . $row, strtoupper((string)$payment->getType()));
|
||||
$sheet->setCellValue('E' . $row, $payment->getAmount());
|
||||
$sheet->getStyle('E' . $row)->getNumberFormat()->setFormatCode('#,##0.00" €"');
|
||||
$sheet->setCellValue('F' . $row, $payment->getState());
|
||||
|
||||
// Gestion du lien vers le fichier justificatif (VichUploader)
|
||||
$assetPath = $uploaderHelper->asset($payment, 'paymentFile');
|
||||
if ($assetPath) {
|
||||
$sheet->setCellValue('G' . $row, 'Voir le document');
|
||||
$sheet->getCell('G' . $row)->getHyperlink()->setUrl($host . $assetPath);
|
||||
$sheet->getStyle('G' . $row)->getFont()->setUnderline(true)->setColor(new Color(Color::COLOR_BLUE));
|
||||
} else {
|
||||
$sheet->setCellValue('G' . $row, 'Aucun fichier');
|
||||
}
|
||||
|
||||
$row++;
|
||||
}
|
||||
|
||||
// Auto-size des colonnes
|
||||
foreach (range('A', 'G') as $col) {
|
||||
$sheet->getColumnDimension($col)->setAutoSize(true);
|
||||
}
|
||||
|
||||
$writer = new Xlsx($spreadsheet);
|
||||
$response = new StreamedResponse(function() use ($writer) {
|
||||
$writer->save('php://output');
|
||||
});
|
||||
|
||||
$filename = "compta_export_{$start}_au_{$end}.xlsx";
|
||||
$response->headers->set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
$response->headers->set('Content-Disposition', 'attachment;filename="' . $filename . '"');
|
||||
$response->headers->set('Cache-Control', 'max-age=0');
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,4 +40,13 @@ class ContratsPaymentsRepository extends ServiceEntityRepository
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
public function findByDateRange(\DateTime $startDate, \DateTime $endDate)
|
||||
{
|
||||
$this->createQueryBuilder('a')
|
||||
->andWhere('a.paymentAt BETWEEN :startDate AND :endDate')
|
||||
->setParameter('startDate', $startDate)
|
||||
->setParameter('endDate', $endDate)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ class StripeExtension extends AbstractExtension
|
||||
new TwigFilter('totalQuoto',[$this,'totalQuoto']),
|
||||
new TwigFilter('totalQuotoAccompte', [$this, 'totalQuotoAccompte']),
|
||||
new TwigFilter('totalContrat',[$this,'totalContrat']),
|
||||
new TwigFilter('totalPayContrat',[$this,'totalPayContrat']),
|
||||
new TwigFilter('totalRestpayContrat',[$this,'totalRestpayContrat']),
|
||||
new TwigFilter('totalContratAccompte',[$this,'totalContratAccompte']),
|
||||
new TwigFilter('devisSignUrl',[$this,'devisSignUrl']),
|
||||
];
|
||||
@@ -89,7 +91,8 @@ class StripeExtension extends AbstractExtension
|
||||
|
||||
// 1. Calcul des lignes de produits (Location)
|
||||
foreach ($devis->getDevisLines() as $line) {
|
||||
$totalHT = $totalHT + $line->getProduct()->getCaution();
|
||||
$p = $this->em->getRepository(Product::class)->findOneBy(['name'=>$line->getProduct()]);
|
||||
$totalHT = $totalHT + $p->getCaution();
|
||||
}
|
||||
|
||||
|
||||
@@ -97,6 +100,28 @@ class StripeExtension extends AbstractExtension
|
||||
return (float) $totalHT;
|
||||
}
|
||||
|
||||
public function totalPayContrat(Contrats $contrats)
|
||||
{
|
||||
$total = 0;
|
||||
foreach ($contrats->getContratsPayments() as $contratsPayment) {
|
||||
if($contratsPayment->getState() == "complete")
|
||||
$total += $contratsPayment->getAmount();
|
||||
}
|
||||
return $total;
|
||||
}
|
||||
|
||||
public function totalRestpayContrat(Contrats $contrats)
|
||||
{
|
||||
$total = 0;
|
||||
foreach ($contrats->getContratsPayments() as $contratsPayment) {
|
||||
if($contratsPayment->getState() == "complete" && $contratsPayment->getType() != "caution" && $contratsPayment->getType() != "accompte")
|
||||
$total += $contratsPayment->getAmount();
|
||||
}
|
||||
|
||||
$totalA = $this->totalContrat($contrats);
|
||||
return $totalA - $total ;
|
||||
}
|
||||
|
||||
public function totalContrat(Contrats $devis): float
|
||||
{
|
||||
$totalHT = 0;
|
||||
|
||||
@@ -44,10 +44,10 @@
|
||||
{{ menu.nav_link(path('app_crm_reservation'), 'Planing de réservation', '<path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>', 'app_crm_reservation') }}
|
||||
{{ menu.nav_link(path('app_crm_product'), 'Produits', '<path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>', 'app_crm_product') }}
|
||||
{{ menu.nav_link(path('app_crm_formules'), 'Formules', '<path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>', 'app_crm_formules') }}
|
||||
{# {{ menu.nav_link(path('app_crm_contrats'), 'Contrat de location', '<path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>', 'app_clients') }}#}
|
||||
{# {{ menu.nav_link(path('app_crm_facture'), 'Facture', '<path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>', 'app_clients') }}#}
|
||||
{# {{ menu.nav_link(path('app_crm_devis'), 'Devis', '<path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>', 'app_clients') }}#}
|
||||
{# {{ menu.nav_link(path('app_crm_customer'), 'Clients', '<path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>', 'app_clients') }}#}
|
||||
{# {{ menu.nav_link(path('app_crm_contrats'), 'Contrat de location', '<path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>', 'app_crm_contrats') }}#}
|
||||
{{ menu.nav_link(path('app_crm_facture'), 'Facture', '<path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>', 'app_crm_facture') }}
|
||||
{# {{ menu.nav_link(path('app_crm_devis'), 'Devis', '<path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>', 'app_crm_devis') }}#}
|
||||
{{ menu.nav_link(path('app_crm_customer'), 'Clients', '<path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>', 'app_clients') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1 +1,257 @@
|
||||
{% extends 'dashboard/base.twig' %}
|
||||
|
||||
{% block title %}Facturation & Paiements{% endblock %}
|
||||
{% block title_header %}Gestion <span class="text-blue-500">Financière</span>{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="w-full max-w-full mx-auto space-y-8 animate-in fade-in duration-700">
|
||||
|
||||
{# NAVIGATION DES ONGLETS #}
|
||||
<div class="flex flex-col lg:flex-row items-center justify-between gap-6 mb-8">
|
||||
<div class="inline-flex p-1.5 bg-[#1e293b]/60 backdrop-blur-xl border border-white/5 rounded-[2rem] shadow-2xl">
|
||||
<a data-turbo="false" href="{{ path('app_crm_facture', {active: 'facture'}) }}"
|
||||
class="flex items-center px-8 py-3.5 rounded-[1.5rem] transition-all duration-500 group {{ active == 'facture' ? 'bg-blue-600 shadow-lg shadow-blue-600/20' : 'hover:bg-white/5' }}">
|
||||
<svg class="w-4 h-4 mr-3 {{ active == 'facture' ? 'text-white' : 'text-slate-500 group-hover:text-blue-400' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||||
</svg>
|
||||
<span class="text-[10px] font-black uppercase tracking-[0.2em] {{ active == 'facture' ? 'text-white' : 'text-slate-400 group-hover:text-white' }}">
|
||||
Factures Émises
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<a data-turbo="false" href="{{ path('app_crm_facture', {active: 'confirmed_paiement'}) }}"
|
||||
class="flex items-center px-8 py-3.5 rounded-[1.5rem] transition-all duration-500 group {{ active == 'confirmed_paiement' ? 'bg-emerald-600 shadow-lg shadow-emerald-600/20' : 'hover:bg-white/5' }}">
|
||||
<svg class="w-4 h-4 mr-3 {{ active == 'confirmed_paiement' ? 'text-white' : 'text-slate-500 group-hover:text-emerald-400' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
<span class="text-[10px] font-black uppercase tracking-[0.2em] {{ active == 'confirmed_paiement' ? 'text-white' : 'text-slate-400 group-hover:text-white' }}">
|
||||
Confirmations de paiement
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{# ACTIONS RAPIDES #}
|
||||
<div class="flex items-center gap-3">
|
||||
{% if active == 'confirmed_paiement' %}
|
||||
<a data-turbo="false" href="{{ path('app_crm_facture',{active:active,'extract':true, 'startDate': startDate, 'endDate': endDate, 'q': searchTerm}) }}"
|
||||
class="px-6 py-3.5 bg-emerald-500/10 hover:bg-emerald-500 text-emerald-500 hover:text-white rounded-2xl border border-emerald-500/20 flex items-center transition-all duration-500 group shadow-xl shadow-emerald-500/5">
|
||||
<svg class="w-4 h-4 mr-2.5 opacity-80 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||||
</svg>
|
||||
<span class="text-[10px] font-black uppercase tracking-widest">Exporter .XLSX</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<button onclick="window.location.reload()" class="px-6 py-3.5 bg-slate-800/80 hover:bg-slate-700 text-slate-300 rounded-2xl border border-white/5 flex items-center transition-all group">
|
||||
<svg class="w-4 h-4 mr-2 opacity-50 group-hover:rotate-180 transition-transform duration-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
|
||||
</svg>
|
||||
<span class="text-[10px] font-black uppercase tracking-widest">Actualiser</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# MOTEUR DE RECHERCHE ET FILTRES #}
|
||||
<div class="bg-[#1e293b]/40 backdrop-blur-xl border border-white/5 rounded-[2rem] p-6 shadow-xl mb-6">
|
||||
<form method="GET" action="{{ path('app_crm_facture') }}" class="flex flex-col md:flex-row items-end gap-4">
|
||||
<input type="hidden" name="active" value="{{ active }}">
|
||||
|
||||
<div class="flex-1 w-full space-y-2">
|
||||
<label class="text-[9px] font-black text-slate-500 uppercase tracking-widest ml-2">Recherche globale</label>
|
||||
<div class="relative">
|
||||
<svg class="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
||||
</svg>
|
||||
<input type="text" name="q" value="{{ searchTerm }}"
|
||||
placeholder="Nom, Réservation, Email..."
|
||||
class="w-full bg-slate-900/50 border border-white/5 rounded-xl py-3 pl-11 pr-4 text-xs text-white placeholder:text-slate-600 focus:border-blue-500/50 focus:ring-0 transition-all">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full md:w-44 space-y-2">
|
||||
<label class="text-[9px] font-black text-slate-500 uppercase tracking-widest ml-2">Date début</label>
|
||||
<input type="date" name="startDate" value="{{ startDate }}"
|
||||
class="w-full bg-slate-900/50 border border-white/5 rounded-xl py-3 px-4 text-xs text-white focus:border-blue-500/50 focus:ring-0 transition-all">
|
||||
</div>
|
||||
|
||||
<div class="w-full md:w-44 space-y-2">
|
||||
<label class="text-[9px] font-black text-slate-500 uppercase tracking-widest ml-2">Date fin</label>
|
||||
<input type="date" name="endDate" value="{{ endDate }}"
|
||||
class="w-full bg-slate-900/50 border border-white/5 rounded-xl py-3 px-4 text-xs text-white focus:border-blue-500/50 focus:ring-0 transition-all">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="bg-blue-600 hover:bg-blue-500 text-white p-3.5 rounded-xl transition-all shadow-lg shadow-blue-600/20 group">
|
||||
<svg class="w-4 h-4 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{% if searchTerm or startDate or endDate %}
|
||||
<a href="{{ path('app_crm_facture', {active: active}) }}" class="bg-slate-800 hover:bg-slate-700 text-slate-400 p-3.5 rounded-xl transition-all border border-white/5">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{# CONTENU DYNAMIQUE #}
|
||||
<div class="backdrop-blur-xl bg-[#1e293b]/40 border border-white/5 rounded-[2.5rem] p-8 shadow-2xl">
|
||||
{% if active == 'facture' %}
|
||||
{# VUE FACTURES #}
|
||||
<div class="flex items-center space-x-4 mb-10">
|
||||
<span class="w-8 h-px bg-blue-500/30"></span>
|
||||
<span class="text-[10px] font-black text-blue-500 uppercase tracking-[0.3em]">Flux de facturation en cours</span>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left">
|
||||
<thead>
|
||||
<tr class="border-b border-white/5">
|
||||
<th class="pb-6 text-[10px] font-black text-slate-500 uppercase tracking-widest">Date</th>
|
||||
<th class="pb-6 text-[10px] font-black text-slate-500 uppercase tracking-widest">Client</th>
|
||||
<th class="pb-6 text-[10px] font-black text-slate-500 uppercase tracking-widest text-right">Montant TTC</th>
|
||||
<th class="pb-6 text-[10px] font-black text-slate-500 uppercase tracking-widest text-center">Statut</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-white/5 text-slate-400 italic text-xs">
|
||||
<tr><td colspan="4" class="py-10 text-center uppercase tracking-widest opacity-30">Section factures en développement</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
{# VUE PAIEMENTS CONFIRMÉS #}
|
||||
<div class="flex items-center space-x-4 mb-10">
|
||||
<span class="w-8 h-px bg-emerald-500/30"></span>
|
||||
<span class="text-[10px] font-black text-emerald-500 uppercase tracking-[0.3em]">Historique des encaissements</span>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left border-separate border-spacing-y-3">
|
||||
<thead>
|
||||
<tr class="text-slate-500 uppercase">
|
||||
<th class="pb-4 pl-6 text-[10px] font-black tracking-[0.2em]">Date</th>
|
||||
<th class="pb-4 text-[10px] font-black tracking-[0.2em]">Client</th>
|
||||
<th class="pb-4 text-[10px] font-black tracking-[0.2em]">N° Réservation</th>
|
||||
<th class="pb-4 text-[10px] font-black tracking-[0.2em]">Méthode</th>
|
||||
<th class="pb-4 text-[10px] font-black tracking-[0.2em]">État</th>
|
||||
<th class="pb-4 pr-6 text-[10px] font-black tracking-[0.2em] text-right">Preuve</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="space-y-4">
|
||||
{% for confirmedPaiement in pagination %}
|
||||
<tr class="group bg-white/[0.02] hover:bg-white/[0.05] transition-all duration-300">
|
||||
<td class="py-5 pl-6 rounded-l-2xl border-y border-l border-white/5">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-xs font-bold text-white">{{ confirmedPaiement.paymentAt|date('d/m/Y') }}</span>
|
||||
<span class="text-[9px] text-slate-500 font-medium">Encaissé</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="py-5 border-y border-white/5">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-xs font-bold text-slate-200 uppercase tracking-tight">
|
||||
{{ confirmedPaiement.contrat.customer.name }} {{ confirmedPaiement.contrat.customer.surname }}
|
||||
</span>
|
||||
<span class="text-[10px] text-slate-500 lowercase">{{ confirmedPaiement.contrat.customer.email }}</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="py-5 border-y border-white/5">
|
||||
<span class="px-3 py-1.5 bg-slate-900/50 rounded-lg border border-white/5 text-[10px] font-mono font-bold text-blue-400">
|
||||
{{ confirmedPaiement.contrat.numReservation }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td class="py-5 border-y border-white/5">
|
||||
<div class="inline-flex items-center px-2.5 py-1 rounded-md bg-white/5 border border-white/10">
|
||||
<span class="text-[9px] font-black text-slate-400 uppercase tracking-widest">{{ confirmedPaiement.type }}</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="py-5 border-y border-white/5">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500 shadow-[0_0_8px_rgba(16,185,129,0.6)]"></span>
|
||||
<span class="text-[10px] font-black text-emerald-500 uppercase tracking-widest">{{ confirmedPaiement.state }}</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="py-5 pr-6 rounded-r-2xl border-y border-r border-white/5 text-right">
|
||||
{% if confirmedPaiement.paymentFileName !="" %}
|
||||
<a href="{{ vich_uploader_asset(confirmedPaiement, 'paymentFile') }}"
|
||||
download
|
||||
class="inline-flex p-2.5 bg-emerald-500/10 hover:bg-emerald-500 text-emerald-500 hover:text-white rounded-xl transition-all duration-300 border border-emerald-500/20 group/btn"
|
||||
title="Télécharger le justificatif">
|
||||
<svg class="w-4 h-4 transform group-hover/btn:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a2 2 0 002 2h12a2 2 0 002-2v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
|
||||
</svg>
|
||||
</a>
|
||||
{% else %}
|
||||
<span class="text-[9px] font-black text-slate-600 uppercase tracking-tighter italic">Aucun fichier</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="6" class="py-20 text-center border-2 border-dashed border-white/5 rounded-[2rem]">
|
||||
<div class="flex flex-col items-center">
|
||||
<svg class="w-10 h-10 text-slate-600 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"/>
|
||||
</svg>
|
||||
<p class="text-slate-500 text-[10px] font-black uppercase tracking-[0.3em]">Aucune donnée trouvée pour cette recherche</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{# PAGINATION KNP BUNDLE #}
|
||||
<div class="mt-8 flex justify-center">
|
||||
<div class="knp-pagination-wrapper">
|
||||
{{ knp_pagination_render(pagination) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Style personnalisé pour la pagination KNP afin de correspondre au thème sombre #}
|
||||
<style>
|
||||
.knp-pagination-wrapper .pagination {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
.knp-pagination-wrapper .page-item .page-link {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
color: #94a3b8;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.75rem;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.knp-pagination-wrapper .page-item.active .page-link {
|
||||
background: #10b981;
|
||||
color: white;
|
||||
border-color: #10b981;
|
||||
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.2);
|
||||
}
|
||||
.knp-pagination-wrapper .page-item:hover:not(.active) .page-link {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: white;
|
||||
}
|
||||
/* Style spécifique pour les inputs de date (chrome/safari) */
|
||||
input[type="date"]::-webkit-calendar-picker-indicator {
|
||||
filter: invert(1);
|
||||
opacity: 0.5;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
{% block body %}
|
||||
<div class="space-y-8 pb-20">
|
||||
|
||||
{# --- RECHERCHE STYLE NÉO-GLASS --- #}
|
||||
{# --- RECHERCHE --- #}
|
||||
<div class="relative group max-w-2xl mx-auto">
|
||||
<div class="absolute -inset-1 bg-gradient-to-r from-blue-500 to-indigo-500 rounded-[2rem] blur opacity-20 group-focus-within:opacity-40 transition duration-1000"></div>
|
||||
<div class="relative flex items-center">
|
||||
@@ -38,9 +38,10 @@
|
||||
{% set acompteOk = contratPaymentPay(contrat, 'accompte') %}
|
||||
{% set cautionOk = contratPaymentPay(contrat, 'caution') %}
|
||||
{% set soldeOk = contratPaymentPay(contrat, 'solde') %}
|
||||
{% set cautionRelase = contratPaymentPay(contrat, 'caution_free') %}
|
||||
{% set cautionEncaisser = contratPaymentPay(contrat, 'caution_recup') %}
|
||||
|
||||
<div class="contrat-card relative overflow-hidden group">
|
||||
{# Background Glow Effect #}
|
||||
<div class="absolute -inset-px bg-gradient-to-r from-transparent via-white/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500 rounded-[2rem]"></div>
|
||||
|
||||
<div class="relative bg-white/[0.03] border border-white/10 backdrop-blur-md rounded-[2rem] transition-all duration-300 group-hover:bg-white/[0.06] group-hover:translate-y-[-2px] group-hover:shadow-2xl group-hover:shadow-blue-500/10">
|
||||
@@ -65,14 +66,12 @@
|
||||
{# 2. CLIENT #}
|
||||
<div class="lg:col-span-3 p-8 border-b lg:border-b-0 lg:border-r border-white/5">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="relative">
|
||||
<div class="w-12 h-12 bg-gradient-to-tr from-blue-600/20 to-indigo-600/20 rounded-2xl flex items-center justify-center border border-white/10 group-hover:scale-110 transition-transform">
|
||||
<svg class="w-6 h-6 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" stroke-width="1.5"></path></svg>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-gradient-to-tr from-blue-600/20 to-indigo-600/20 rounded-2xl flex items-center justify-center border border-white/10 group-hover:scale-110 transition-transform">
|
||||
<svg class="w-6 h-6 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" stroke-width="1.5"></path></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-white font-bold text-base tracking-tight uppercase">{{ contrat.customer.surname }} {{ contrat.customer.name }}</p>
|
||||
<p class="text-slate-500 text-xs font-medium">{{ contrat.customer.email }}</p>
|
||||
<p class="text-slate-500 text-xs font-medium line-clamp-1">{{ contrat.customer.email }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -80,7 +79,7 @@
|
||||
{# 3. LIEU #}
|
||||
<div class="lg:col-span-2 p-8 border-b lg:border-b-0 lg:border-r border-white/5">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-[10px] font-bold text-slate-500 uppercase tracking-widest mb-1 text-search">Destination</span>
|
||||
<span class="text-[10px] font-bold text-slate-500 uppercase tracking-widest mb-1">Destination</span>
|
||||
<p class="text-slate-200 font-bold text-sm">{{ contrat.townEvent }}</p>
|
||||
<p class="text-blue-500 font-black text-[11px]">{{ contrat.zipCodeEvent }}</p>
|
||||
</div>
|
||||
@@ -90,62 +89,54 @@
|
||||
<div class="lg:col-span-3 p-8 bg-black/10 lg:bg-transparent">
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
|
||||
{# --- ACOMPTE --- #}
|
||||
{# ACOMPTE #}
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<div class="w-8 h-8 rounded-xl flex items-center justify-center transition-all duration-300
|
||||
{{ acompteOk
|
||||
? 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30'
|
||||
: 'bg-rose-500/10 text-rose-500 border border-rose-500/20 shadow-[0_0_15px_rgba(244,63,94,0.1)]'
|
||||
}}">
|
||||
{% if acompteOk %}
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"></path></svg>
|
||||
{% else %}
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M6 18L18 6M6 6l12 12"></path></svg>
|
||||
{% endif %}
|
||||
{{ acompteOk ? 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30' : 'bg-rose-500/10 text-rose-500 border border-rose-500/20' }}">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="{{ acompteOk ? 'M5 13l4 4L19 7' : 'M6 18L18 6M6 6l12 12' }}"></path></svg>
|
||||
</div>
|
||||
<span class="text-[8px] font-black uppercase tracking-tighter {{ acompteOk ? 'text-emerald-500' : 'text-rose-500' }}">
|
||||
Acompte
|
||||
</span>
|
||||
<span class="text-[8px] font-black uppercase tracking-tighter {{ acompteOk ? 'text-emerald-500' : 'text-rose-500' }}">Acompte</span>
|
||||
</div>
|
||||
|
||||
{# --- CAUTION --- #}
|
||||
{# CAUTION (MULTI-ETATS) #}
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<div class="w-8 h-8 rounded-xl flex items-center justify-center transition-all duration-300
|
||||
{{ cautionOk
|
||||
? 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30'
|
||||
: 'bg-rose-500/20 text-rose-500 border border-rose-500/30 animate-pulse shadow-[0_0_15px_rgba(244,63,94,0.2)]'
|
||||
}}">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
{% if cautionOk %}
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path>
|
||||
{% else %}
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path>
|
||||
{% endif %}
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-[8px] font-black uppercase tracking-tighter {{ cautionOk ? 'text-emerald-500' : 'text-rose-500' }}">
|
||||
Caution
|
||||
</span>
|
||||
{% if cautionEncaisser %}
|
||||
{# ÉTAT : RÉCUPÉRÉE / ENCAISSÉE (Litige) #}
|
||||
<div class="w-8 h-8 rounded-xl flex items-center justify-center bg-rose-600/30 text-rose-400 border border-rose-500/50 shadow-[0_0_15px_rgba(225,29,72,0.3)]" title="Caution encaissée">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"></path></svg>
|
||||
</div>
|
||||
<span class="text-[8px] font-black uppercase tracking-tighter text-rose-400">Encaissée</span>
|
||||
|
||||
{% elseif cautionRelase %}
|
||||
{# ÉTAT : LIBÉRÉE / RENDUE #}
|
||||
<div class="w-8 h-8 rounded-xl flex items-center justify-center bg-blue-500/20 text-blue-400 border border-blue-500/30" title="Caution libérée">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M8 11V7a4 4 0 118 0m-4 8v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2z"></path></svg>
|
||||
</div>
|
||||
<span class="text-[8px] font-black uppercase tracking-tighter text-blue-400">Libérée</span>
|
||||
|
||||
{% elseif cautionOk %}
|
||||
{# ÉTAT : DÉTENUE (Payée mais pas encore libérée) #}
|
||||
<div class="w-8 h-8 rounded-xl flex items-center justify-center bg-emerald-500/20 text-emerald-400 border border-emerald-500/30" title="Caution détenue">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path></svg>
|
||||
</div>
|
||||
<span class="text-[8px] font-black uppercase tracking-tighter text-emerald-500">Détenue</span>
|
||||
|
||||
{% else %}
|
||||
{# ÉTAT : MANQUANTE #}
|
||||
<div class="w-8 h-8 rounded-xl flex items-center justify-center bg-rose-500/10 text-rose-500 border border-rose-500/20 animate-pulse" title="Caution manquante">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg>
|
||||
</div>
|
||||
<span class="text-[8px] font-black uppercase tracking-tighter text-rose-500">Requise</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# --- SOLDE --- #}
|
||||
{# SOLDE #}
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<div class="w-8 h-8 rounded-xl flex items-center justify-center transition-all duration-300
|
||||
{{ soldeOk
|
||||
? 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30'
|
||||
: 'bg-rose-500/10 text-rose-500 border border-rose-500/20'
|
||||
}}">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
{% if soldeOk %}
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"></path>
|
||||
{% else %}
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
{% endif %}
|
||||
</svg>
|
||||
{{ soldeOk ? 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30' : 'bg-rose-500/10 text-rose-500 border border-rose-500/20' }}">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="{{ soldeOk ? 'M5 13l4 4L19 7' : 'M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z' }}"></path></svg>
|
||||
</div>
|
||||
<span class="text-[8px] font-black uppercase tracking-tighter {{ soldeOk ? 'text-emerald-500' : 'text-rose-500' }}">
|
||||
Solde
|
||||
</span>
|
||||
<span class="text-[8px] font-black uppercase tracking-tighter {{ soldeOk ? 'text-emerald-500' : 'text-rose-500' }}">Solde</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -153,7 +144,7 @@
|
||||
|
||||
{# 5. ACTIONS #}
|
||||
<div class="lg:col-span-2 p-6 flex lg:flex-col items-center justify-center gap-3">
|
||||
<a data-turbo="false" href="{{ path('app_crm_contrats_view', {id: contrat.id}) }}"
|
||||
<a data-turbo="false" href="{{ path('app_crm_contrats_view', {id: contrat.id}) }}"
|
||||
class="w-full lg:w-12 h-12 rounded-2xl bg-white/5 border border-white/10 flex items-center justify-center text-white hover:bg-blue-600 hover:border-blue-400 transition-all group/btn" title="Voir">
|
||||
<svg class="w-5 h-5 group-hover/btn:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path></svg>
|
||||
</a>
|
||||
@@ -174,15 +165,5 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Optionnel : Custom style pour la pagination KNP pour matcher le glassmorphism */
|
||||
.glass-pagination nav ul { @apply flex justify-center gap-2; }
|
||||
.glass-pagination nav ul li span,
|
||||
.glass-pagination nav ul li a {
|
||||
@apply px-4 py-2 bg-white/5 border border-white/10 rounded-xl text-white text-sm transition-all backdrop-blur-md;
|
||||
}
|
||||
.glass-pagination nav ul li.active span { @apply bg-blue-600 border-blue-500 font-bold; }
|
||||
.glass-pagination nav ul li a:hover { @apply bg-white/10 border-white/20; }
|
||||
</style>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -281,27 +281,25 @@
|
||||
</thead>
|
||||
<tbody class="divide-y divide-white/5">
|
||||
{% for payment in contrat.contratsPayments %}
|
||||
{% if payment.state == 'complete' %}
|
||||
<tr class="group hover:bg-white/[0.02] transition-colors">
|
||||
<td class="px-8 py-5 text-xs text-white font-bold tracking-tight">{{ payment.paymentAt|date('d/m/Y H:i') }}</td>
|
||||
<td class="px-8 py-5">
|
||||
<tr class="group hover:bg-white/[0.02] transition-colors">
|
||||
<td class="px-8 py-5 text-xs text-white font-bold tracking-tight">{{ payment.paymentAt|date('d/m/Y H:i') }}</td>
|
||||
<td class="px-8 py-5">
|
||||
<span class="px-2 py-1 rounded-lg text-[8px] font-black uppercase italic
|
||||
{% if payment.type == 'caution' %}bg-purple-500/10 text-purple-400 border border-purple-500/20
|
||||
{% elseif payment.type == 'accompte' %}bg-blue-500/10 text-blue-400 border border-blue-500/20
|
||||
{% else %}bg-emerald-500/10 text-emerald-400 border border-emerald-500/20{% endif %}">
|
||||
{{ payment.type|replace({'_': ' '}) }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-8 py-5 text-sm text-white font-black italic">{{ payment.amount|number_format(2, ',', ' ') }}€</td>
|
||||
<td class="px-8 py-5 text-right">
|
||||
<a href="{{ path('app_crm_contrats_view', {id: contrat.id, idPaymentPdf: payment.id}) }}" target="_blank"
|
||||
class="inline-flex items-center gap-2 text-[9px] font-black uppercase text-slate-400 hover:text-white transition-all">
|
||||
<svg class="w-4 h-4 text-rose-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" stroke-width="2"/></svg>
|
||||
REÇU PDF
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="px-8 py-5 text-sm text-white font-black italic">{{ payment.amount|number_format(2, ',', ' ') }}€</td>
|
||||
<td class="px-8 py-5 text-right">
|
||||
<a href="{{ path('app_crm_contrats_view', {id: contrat.id, idPaymentPdf: payment.id}) }}" target="_blank"
|
||||
class="inline-flex items-center gap-2 text-[9px] font-black uppercase text-slate-400 hover:text-white transition-all">
|
||||
<svg class="w-4 h-4 text-rose-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" stroke-width="2"/></svg>
|
||||
REÇU PDF
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="4" class="py-12 text-center text-slate-600 text-[10px] font-black uppercase opacity-40">Aucun paiement effectué</td></tr>
|
||||
{% endfor %}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
{% block title_header %}Fiche <span class="text-blue-500">Client</span>{% endblock %}
|
||||
|
||||
{% block actions %}
|
||||
<a data-turbo="false" href="{{ path('app_crm_customer') }}" class="flex items-center px-4 py-2 text-[10px] font-black text-slate-400 hover:text-white uppercase tracking-widest transition-all group">
|
||||
<a data-turbo="false" href="{{ path('app_crm_customer') }}" class="flex items-center px-4 py-2 text-[10px] font-black text-slate-400 hover:text-white uppercase tracking-widest transition-all group">
|
||||
<svg class="w-4 h-4 mr-2 transform group-hover:-translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/></svg>
|
||||
Retour au listing
|
||||
</a>
|
||||
@@ -29,9 +29,9 @@
|
||||
<span class="text-[10px] font-bold text-blue-400 uppercase tracking-widest">ID #{{ customer.id }}</span>
|
||||
{% if customer.customerId %}
|
||||
<span class="flex items-center text-[10px] font-bold text-emerald-400 uppercase tracking-widest bg-emerald-500/10 px-2 py-1 rounded-lg border border-emerald-500/20">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500 mr-2 shadow-[0_0_8px_rgba(16,185,129,0.5)]"></span>
|
||||
Stripe Sync
|
||||
</span>
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500 mr-2 shadow-[0_0_8px_rgba(16,185,129,0.5)]"></span>
|
||||
Stripe Sync
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -44,6 +44,7 @@
|
||||
{# GRILLE PRINCIPALE INFOS #}
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<div class="lg:col-span-2 space-y-8">
|
||||
{# FORMULAIRE COORDONNÉES #}
|
||||
<div class="backdrop-blur-xl bg-[#1e293b]/40 border border-white/5 rounded-[2.5rem] p-10">
|
||||
<div class="flex items-center space-x-4 mb-10">
|
||||
<span class="w-8 h-px bg-blue-500/30"></span>
|
||||
@@ -66,30 +67,147 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# SECTION DOCUMENTS (DEVIS & CONTRATS) #}
|
||||
<div class="grid grid-cols-1 gap-8">
|
||||
{# DEVIS #}
|
||||
<div class="backdrop-blur-xl bg-[#1e293b]/20 border border-white/5 rounded-[2.5rem] p-8">
|
||||
<h3 class="text-sm font-black text-white uppercase tracking-widest mb-6 flex items-center">
|
||||
<span class="w-2 h-2 rounded-full bg-blue-500 mr-3"></span>
|
||||
Devis récents
|
||||
</h3>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left">
|
||||
<thead>
|
||||
<tr class="border-b border-white/5">
|
||||
<th class="pb-4 text-[10px] font-black text-slate-500 uppercase tracking-widest">Numéro</th>
|
||||
<th class="pb-4 text-[10px] font-black text-slate-500 uppercase tracking-widest text-center">Statut</th>
|
||||
<th class="pb-4 text-[10px] font-black text-slate-500 uppercase tracking-widest text-right">Montant HT</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-white/5">
|
||||
{% for devis in customer.devis %}
|
||||
<tr>
|
||||
<td class="py-4 text-xs font-bold text-white">
|
||||
<a target="_blank" href="{{ path('app_crm_devis_edit',{id:devis.id}) }}"> {{ devis.num }}</a>
|
||||
</td>
|
||||
<td class="py-4 text-center">
|
||||
<span class="px-2 py-1 bg-blue-500/10 text-blue-400 rounded-md text-[9px] font-black uppercase tracking-tighter">
|
||||
{{ devis.state|default('Brouillon') }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-4 text-right text-xs font-mono text-slate-300">
|
||||
{{ devis|totalQuoto|number_format(2, ',', ' ') }} €
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="3" class="py-6 text-center text-[10px] font-bold text-slate-600 uppercase">Aucun devis</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# CONTRATS #}
|
||||
<div class="backdrop-blur-xl bg-[#1e293b]/20 border border-white/5 rounded-[2.5rem] p-8">
|
||||
<h3 class="text-sm font-black text-white uppercase tracking-widest mb-6 flex items-center">
|
||||
<span class="w-2 h-2 rounded-full bg-emerald-500 mr-3"></span>
|
||||
Contrats & Réservations
|
||||
</h3>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left">
|
||||
<thead>
|
||||
<tr class="border-b border-white/5">
|
||||
<th class="pb-4 text-[10px] font-black text-slate-500 uppercase tracking-widest">Réservation</th>
|
||||
<th class="pb-4 text-[10px] font-black text-slate-500 uppercase tracking-widest text-right">Total</th>
|
||||
<th class="pb-4 text-[10px] font-black text-slate-500 uppercase tracking-widest text-right">Réglé</th>
|
||||
<th class="pb-4 text-[10px] font-black text-slate-500 uppercase tracking-widest text-right">Solde</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-white/5">
|
||||
{% for contrat in customer.contrats %}
|
||||
{% set total = contrat|totalContrat %}
|
||||
{% set paid = contrat|totalPayContrat %}
|
||||
{% set rest = contrat|totalRestpayContrat %}
|
||||
|
||||
<tr class="group hover:bg-white/[0.02] transition-colors">
|
||||
<td class="py-4 text-xs font-bold text-white">
|
||||
<a target="_blank" href="{{ path('app_crm_contrats_view',{id:contrat.id}) }}"> {{ contrat.numReservation }}</a>
|
||||
</td>
|
||||
<td class="py-4 text-right text-xs font-mono text-slate-400">
|
||||
{{ total|number_format(2, ',', ' ') }} €
|
||||
</td>
|
||||
<td class="py-4 text-right text-xs font-mono text-emerald-400">
|
||||
{{ paid|number_format(2, ',', ' ') }} €
|
||||
</td>
|
||||
<td class="py-4 text-right text-xs font-mono {{ rest > 0 ? 'text-rose-400' : 'text-emerald-500' }}">
|
||||
{{ rest|number_format(2, ',', ' ') }} €
|
||||
{% if rest > 0 %}
|
||||
<div class="text-[8px] font-black uppercase opacity-50 tracking-tighter">En attente</div>
|
||||
{% else %}
|
||||
<div class="text-[8px] font-black uppercase opacity-50 tracking-tighter italic">Soldé</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="4" class="py-6 text-center text-[10px] font-bold text-slate-600 uppercase tracking-widest">Aucun contrat actif</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-8">
|
||||
<div class="backdrop-blur-xl bg-emerald-500/5 border border-emerald-500/10 rounded-[2.5rem] p-8">
|
||||
{# CARTE RÉSUMÉ & CHIFFRE D'AFFAIRES #}
|
||||
<div class="backdrop-blur-xl bg-emerald-500/5 border border-emerald-500/10 rounded-[2.5rem] p-8 shadow-xl">
|
||||
<h3 class="text-[10px] font-black text-emerald-500 uppercase tracking-widest mb-6 flex items-center">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
Résumé
|
||||
Résumé financier
|
||||
</h3>
|
||||
<div class="space-y-4">
|
||||
<div class="flex justify-between items-center border-b border-white/5 pb-3">
|
||||
<span class="text-xs text-slate-400 font-medium">Contrat</span>
|
||||
<span class="text-xs text-white font-bold uppercase tracking-widest">
|
||||
{{ customer.type|default('Standard') }}
|
||||
</span>
|
||||
|
||||
<div class="space-y-6">
|
||||
{# CALCUL DU CA GLOBAL (Devis + Contrats) #}
|
||||
{% set totalCA = 0 %}
|
||||
{% for c in customer.contrats %}{% set totalCA = totalCA + (c|totalContrat) %}{% endfor %}
|
||||
|
||||
<div class="p-6 bg-emerald-500/10 rounded-3xl border border-emerald-500/20">
|
||||
<span class="text-[9px] font-black text-emerald-500/60 uppercase tracking-widest block mb-1">Total HT généré</span>
|
||||
<div class="flex items-baseline gap-1">
|
||||
<span class="text-3xl font-black text-white tracking-tighter">{{ totalCA|number_format(2, ',', ' ') }}</span>
|
||||
<span class="text-xs font-bold text-emerald-500">€</span>
|
||||
</div>
|
||||
<div class="mt-3 grid grid-cols-2 gap-2">
|
||||
<div class="text-[8px] text-slate-500 font-bold uppercase">
|
||||
{{ customer.devis|length }} Devis
|
||||
</div>
|
||||
<div class="text-[8px] text-slate-500 font-bold uppercase text-right">
|
||||
{{ customer.contrats|length }} Contrats
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center border-b border-white/5 pb-4">
|
||||
<span class="text-xs text-slate-400 font-medium">Type Client</span>
|
||||
<span class="text-[10px] text-white font-black uppercase tracking-widest bg-white/5 px-3 py-1 rounded-full border border-white/5">
|
||||
{{ customer.type|default('Standard') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# ZONE SENSIBLE #}
|
||||
<div class="backdrop-blur-xl bg-rose-500/5 border border-rose-500/10 rounded-[2.5rem] p-8">
|
||||
<h4 class="text-[10px] font-black text-rose-500 uppercase tracking-widest mb-4">Zone sensible</h4>
|
||||
<p class="text-[9px] text-slate-500 mb-6 leading-relaxed uppercase font-bold tracking-tight">
|
||||
La suppression est définitive sur l'intranet et Stripe.
|
||||
</p>
|
||||
<a data-turbo="false" href="{{ path('app_crm_customer_delete', {id: customer.id}) }}?_token={{ csrf_token('delete' ~ customer.id) }}"
|
||||
<a data-turbo="false" href="{{ path('app_crm_customer_delete', {id: customer.id}) }}?_token={{ csrf_token('delete' ~ customer.id) }}"
|
||||
data-turbo-method="post"
|
||||
data-turbo-confirm="Confirmer la suppression définitive ?"
|
||||
class="flex items-center justify-center w-full py-3 border border-rose-500/20 hover:bg-rose-600 text-rose-500 hover:text-white text-[9px] font-black uppercase tracking-widest rounded-xl transition-all shadow-lg shadow-rose-500/5">
|
||||
@@ -102,7 +220,6 @@
|
||||
|
||||
{# SECTION ADRESSES #}
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mt-12">
|
||||
|
||||
{# LISTE DES ADRESSES #}
|
||||
<div class="lg:col-span-2 space-y-6">
|
||||
<h3 class="text-lg font-bold text-white mb-6 flex items-center ml-4">
|
||||
@@ -122,7 +239,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex space-x-2">
|
||||
<a data-turbo="false" href="{{ path('app_crm_customer_edit', {id: customer.id, idAddr: address.id}) }}" class="p-2 bg-blue-500/10 text-blue-400 rounded-lg hover:bg-blue-500 hover:text-white transition-all">
|
||||
<a data-turbo="false" href="{{ path('app_crm_customer_edit', {id: customer.id, idAddr: address.id}) }}" class="p-2 bg-blue-500/10 text-blue-400 rounded-lg hover:bg-blue-500 hover:text-white transition-all">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
@@ -136,7 +253,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# FORMULAIRE D'ADRESSE (AJOUT/EDIT) #}
|
||||
{# FORMULAIRE D'ADRESSE #}
|
||||
<div class="lg:col-span-1">
|
||||
<div class="backdrop-blur-xl bg-[#1e293b]/40 border border-white/10 rounded-[2.5rem] p-8 shadow-2xl sticky top-8">
|
||||
<div class="mb-8">
|
||||
@@ -161,9 +278,8 @@
|
||||
<button type="submit" class="w-full py-4 bg-emerald-600 hover:bg-emerald-500 text-white text-[10px] font-black uppercase tracking-widest rounded-2xl shadow-lg shadow-emerald-600/10 transition-all">
|
||||
{{ editingAddress ? 'Enregistrer les modifications' : 'Ajouter au carnet' }}
|
||||
</button>
|
||||
|
||||
{% if editingAddress %}
|
||||
<a data-turbo="false" href="{{ path('app_crm_customer_edit', {id: customer.id}) }}" class="text-center py-2 text-[9px] font-black text-slate-500 uppercase tracking-widest hover:text-white transition-colors">
|
||||
<a data-turbo="false" href="{{ path('app_crm_customer_edit', {id: customer.id}) }}" class="text-center py-2 text-[9px] font-black text-slate-500 uppercase tracking-widest hover:text-white transition-colors">
|
||||
Annuler la modification
|
||||
</a>
|
||||
{% endif %}
|
||||
@@ -188,4 +304,6 @@
|
||||
background-size: 0.8rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -129,14 +129,27 @@
|
||||
<a download="AUDIT_{{ quote.num }}.pdf" href="{{ vich_uploader_asset(quote, 'devisAuditFile') }}" title="Télécharger le certificat d'audit" target="_blank" class="p-2 bg-purple-600/10 hover:bg-purple-600 text-purple-500 hover:text-white rounded-xl transition-all border border-purple-500/20 shadow-lg shadow-purple-600/5">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
</a>
|
||||
<a data-turbo="false" href="{{ path('app_crm_contrats_create', {idDevis: quote.id}) }}"
|
||||
title="Générer le contrat de location"
|
||||
class="flex items-center gap-2 px-4 py-2 bg-blue-600/10 hover:bg-blue-600 text-blue-500 hover:text-white rounded-xl transition-all border border-blue-500/20 shadow-lg shadow-blue-600/5 font-bold text-xs uppercase tracking-widest">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
<span>Créer le contrat</span>
|
||||
</a>
|
||||
{% if quote.contrats is null %}
|
||||
{# ÉTAT : AUCUN CONTRAT - BOUTON CRÉATION (BLEU) #}
|
||||
<a data-turbo="false" href="{{ path('app_crm_contrats_create', {idDevis: quote.id}) }}"
|
||||
title="Générer le contrat de location"
|
||||
class="group flex items-center gap-2 px-5 py-2.5 bg-blue-600/10 hover:bg-blue-600 text-blue-500 hover:text-white rounded-xl transition-all duration-300 border border-blue-500/20 shadow-lg shadow-blue-600/5 font-bold text-[10px] uppercase tracking-widest">
|
||||
<svg class="w-4 h-4 transition-transform group-hover:rotate-12" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
<span>Créer le contrat</span>
|
||||
</a>
|
||||
{% else %}
|
||||
{# ÉTAT : CONTRAT EXISTANT - BOUTON ACCÈS (VERT) #}
|
||||
<a data-turbo="false" href="{{ path('app_crm_contrats_view', {id: quote.contrats.id}) }}"
|
||||
title="Accéder au contrat de location"
|
||||
class="group flex items-center gap-2 px-5 py-2.5 bg-emerald-500/10 hover:bg-emerald-500 text-emerald-500 hover:text-white rounded-xl transition-all duration-300 border border-emerald-500/20 shadow-lg shadow-emerald-600/5 font-bold text-[10px] uppercase tracking-widest">
|
||||
<svg class="w-4 h-4 transition-transform group-hover:scale-110" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0" />
|
||||
</svg>
|
||||
<span>Voir le contrat</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{# PDF Brouillon #}
|
||||
<a download="{{ quote.num }}.pdf" href="{{ vich_uploader_asset(quote,'devisDocuSealFile') }}" target="_blank" class="p-2 bg-slate-600/10 hover:bg-slate-600 text-slate-500 hover:text-white rounded-xl transition-all border border-slate-500/20">
|
||||
|
||||
Reference in New Issue
Block a user