src/Controller/HomeController.php line 49

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Entity\Bondesortie;
  4. use App\Entity\Client;
  5. use App\Entity\Devis;
  6. use App\Entity\User;
  7. use App\Form\AdminEditType;
  8. use App\Form\ClientType;
  9. use App\Form\EmployeType;
  10. use App\Form\RegistrationFormType;
  11. use App\Form\TechnicienType;
  12. use App\Repository\BondesortieRepository;
  13. use App\Repository\ClientRepository;
  14. use App\Repository\DevisRepository;
  15. use App\Repository\InterventionRepository;
  16. use App\Repository\UserRepository;
  17. use Doctrine\ORM\EntityManagerInterface;
  18. use DateTimeImmutable;
  19. use DateTimeInterface;
  20. use NumberFormatter;
  21. use PhpOffice\PhpSpreadsheet\Spreadsheet;
  22. use PhpOffice\PhpSpreadsheet\Style\Alignment;
  23. use PhpOffice\PhpSpreadsheet\Style\Border;
  24. use PhpOffice\PhpSpreadsheet\Style\Fill;
  25. use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
  26. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
  27. use Symfony\Bridge\Twig\Mime\TemplatedEmail;
  28. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  29. use Symfony\Component\DomCrawler\Crawler;
  30. use Symfony\Component\HttpClient\HttpClient;
  31. use Symfony\Component\HttpFoundation\File\Exception\FileException;
  32. use Symfony\Component\HttpFoundation\File\File;
  33. use Symfony\Component\HttpFoundation\JsonResponse;
  34. use Symfony\Component\HttpFoundation\Request;
  35. use Symfony\Component\HttpFoundation\Response;
  36. use Symfony\Component\HttpFoundation\ResponseHeaderBag;
  37. use Symfony\Component\HttpFoundation\StreamedResponse;
  38. use Symfony\Component\Mailer\MailerInterface;
  39. use Symfony\Component\Mime\Address;
  40. use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
  41. use Symfony\Component\Routing\Annotation\Route;
  42. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  43. class HomeController extends AbstractController
  44. {
  45.     #[Route('admin/home'name'app_home')]
  46.     public function scraping(DevisRepository $devisRepositoryBondesortieRepository $bondesortieRepositoryClientRepository $clientRepositoryRequest $request): Response
  47.     {
  48.         return $this->render('home/index.html.twig'$this->buildDashboardData(
  49.             $devisRepository,
  50.             $bondesortieRepository,
  51.             $clientRepository,
  52.             $request
  53.         ));
  54.     }
  55.     #[Route('admin/home/export-excel'name'app_home_export_excel'methods: ['GET'])]
  56.     public function exportExcel(DevisRepository $devisRepositoryBondesortieRepository $bondesortieRepositoryClientRepository $clientRepositoryRequest $request): StreamedResponse
  57.     {
  58.         $data $this->buildDashboardData(
  59.             $devisRepository,
  60.             $bondesortieRepository,
  61.             $clientRepository,
  62.             $request
  63.         );
  64.         $spreadsheet $this->createDashboardSpreadsheet($data);
  65.         $writer = new Xlsx($spreadsheet);
  66.         $filename $this->buildDashboardExportFilename($data['filters']);
  67.         $response = new StreamedResponse(static function () use ($writer): void {
  68.             $writer->save('php://output');
  69.         });
  70.         $response->headers->set('Content-Type''application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
  71.         $response->headers->set('Content-Disposition'$response->headers->makeDisposition(
  72.             ResponseHeaderBag::DISPOSITION_ATTACHMENT,
  73.             $filename
  74.         ));
  75.         return $response;
  76.     }
  77.     private function buildDashboardData(DevisRepository $devisRepositoryBondesortieRepository $bondesortieRepositoryClientRepository $clientRepositoryRequest $request): array
  78.     {
  79.         $selectedClientId trim((string) $request->query->get('client'''));
  80.         $selectedDateFrom trim((string) $request->query->get('date_from'''));
  81.         $selectedDateTo trim((string) $request->query->get('date_to'''));
  82.         $selectedDateRange trim((string) $request->query->get('dateRangePicker'''));
  83.         $selectedType trim((string) $request->query->get('type'''));
  84.         $selectedUnpaidOnly in_array((string) $request->query->get('unpaid_only'''), ['1''true''on''yes'], true);
  85.         if ($selectedDateRange !== '') {
  86.             [$selectedDateFrom$selectedDateTo] = $this->parseDashboardDateRange($selectedDateRange);
  87.         } else {
  88.             $selectedDateRange $this->buildDashboardDateRangeValue($selectedDateFrom$selectedDateTo);
  89.         }
  90.         $selectedClient $selectedClientId !== '' $clientRepository->find($selectedClientId) : null;
  91.         $dateFrom $this->createDateFilter($selectedDateFromfalse);
  92.         $dateTo $this->createDateFilter($selectedDateTotrue);
  93.         $stats $this->createDashboardStats();
  94.         if ($selectedType !== '' && !isset($stats[$selectedType])) {
  95.             $selectedType '';
  96.         }
  97.         $devisStatuts $selectedType === ''
  98.             ? ['Facture''BL''BLP']
  99.             : (in_array($selectedType, ['Facture''BL''BLP'], true) ? [$selectedType] : []);
  100.         $devis $devisStatuts !== []
  101.             ? $this->findDashboardDevis($devisRepository$selectedClient$dateFrom$dateTo$devisStatuts)
  102.             : [];
  103.         $bondesorties $selectedType === '' || $selectedType === 'BS'
  104.             $this->findDashboardBondesorties($bondesortieRepository$selectedClient$dateFrom$dateTo)
  105.             : [];
  106.         $documents = [];
  107.         foreach ($devis as $devi) {
  108.             $this->addDevisDashboardDocument($devi$stats$documents$selectedUnpaidOnly);
  109.         }
  110.         foreach ($bondesorties as $bondesortie) {
  111.             $this->addBondesortieDashboardDocument($bondesortie$stats$documents$selectedUnpaidOnly);
  112.         }
  113.         usort($documents, static function (array $left, array $right): int {
  114.             return $right['dateTimestamp'] <=> $left['dateTimestamp'];
  115.         });
  116.         $totals = [
  117.             'count' => array_sum(array_column($stats'count')),
  118.             'total' => array_sum(array_column($stats'total')),
  119.             'paid' => array_sum(array_column($stats'paid')),
  120.             'remaining' => array_sum(array_column($stats'remaining')),
  121.         ];
  122.         return [
  123.             'clients' => $clientRepository->findBy([], ['raisonsocial' => 'ASC''nom' => 'ASC''prenom' => 'ASC']),
  124.             'filters' => [
  125.                 'client' => $selectedClientId,
  126.                 'client_label' => $selectedClient instanceof Client $this->formatClientLabel($selectedClient) : 'Tous les clients',
  127.                 'date_from' => $selectedDateFrom,
  128.                 'date_to' => $selectedDateTo,
  129.                 'date_range' => $selectedDateRange,
  130.                 'type' => $selectedType,
  131.                 'type_label' => $selectedType !== '' $stats[$selectedType]['shortLabel'] : 'Tous les types',
  132.                 'unpaid_only' => $selectedUnpaidOnly,
  133.                 'payment_label' => $selectedUnpaidOnly 'Non payes seulement' 'Tous les paiements',
  134.             ],
  135.             'stats' => $stats,
  136.             'totals' => $totals,
  137.             'documents' => $documents,
  138.             'chart' => [
  139.                 'labels' => array_values(array_map(static fn (array $item): string => $item['shortLabel'], $stats)),
  140.                 'remaining' => array_values(array_map(static fn (array $item): float => round($item['remaining'], 3), $stats)),
  141.                 'paid' => array_values(array_map(static fn (array $item): float => round($item['paid'], 3), $stats)),
  142.                 'total' => array_values(array_map(static fn (array $item): float => round($item['total'], 3), $stats)),
  143.             ],
  144.         ];
  145.     }
  146.     private function parseDashboardDateRange(string $dateRange): array
  147.     {
  148.         if (str_contains($dateRange' to ')) {
  149.             $parts explode(' to '$dateRange);
  150.         } elseif (str_contains($dateRange' au ')) {
  151.             $parts explode(' au '$dateRange);
  152.         } else {
  153.             $parts = [$dateRange];
  154.         }
  155.         $dateFrom $this->normalizeDashboardDateString($parts[0] ?? '');
  156.         $dateTo $this->normalizeDashboardDateString($parts[1] ?? ($parts[0] ?? ''));
  157.         return [$dateFrom$dateTo];
  158.     }
  159.     private function normalizeDashboardDateString(string $date): string
  160.     {
  161.         $date trim($date);
  162.         if ($date === '') {
  163.             return '';
  164.         }
  165.         $months = [
  166.             'janv' => 'Jan''janvier' => 'Jan',
  167.             'fevr' => 'Feb''fevrier' => 'Feb''févr' => 'Feb''février' => 'Feb',
  168.             'mars' => 'Mar',
  169.             'avr' => 'Apr''avril' => 'Apr',
  170.             'mai' => 'May',
  171.             'juin' => 'Jun',
  172.             'juil' => 'Jul''juillet' => 'Jul',
  173.             'aout' => 'Aug''août' => 'Aug',
  174.             'sept' => 'Sep''septembre' => 'Sep',
  175.             'oct' => 'Oct''octobre' => 'Oct',
  176.             'nov' => 'Nov''novembre' => 'Nov',
  177.             'dec' => 'Dec''decembre' => 'Dec''déc' => 'Dec''décembre' => 'Dec',
  178.         ];
  179.         $lowerDate mb_strtolower($date);
  180.         $mappedDate strtr($lowerDate$months);
  181.         $formats = ['Y-m-d''d-m-Y''d/m/Y''d M, Y''d M Y'];
  182.         foreach ($formats as $format) {
  183.             $dateTime DateTimeImmutable::createFromFormat($format$format === 'Y-m-d' || $format === 'd-m-Y' || $format === 'd/m/Y' $date $mappedDate);
  184.             if ($dateTime instanceof DateTimeImmutable) {
  185.                 return $dateTime->format('Y-m-d');
  186.             }
  187.         }
  188.         return '';
  189.     }
  190.     private function buildDashboardDateRangeValue(string $dateFromstring $dateTo): string
  191.     {
  192.         if ($dateFrom !== '' && $dateTo !== '') {
  193.             return $dateFrom.' to '.$dateTo;
  194.         }
  195.         if ($dateFrom !== '') {
  196.             return $dateFrom;
  197.         }
  198.         return $dateTo;
  199.     }
  200.     private function createDashboardSpreadsheet(array $data): Spreadsheet
  201.     {
  202.         $spreadsheet = new Spreadsheet();
  203.         $sheet $spreadsheet->getActiveSheet();
  204.         $sheet->setTitle('Reste a payer');
  205.         $filters $data['filters'];
  206.         $documents $data['documents'];
  207.         $totals $data['totals'];
  208.         $sheet->mergeCells('A1:G1');
  209.         $sheet->setCellValue('A1''Tableau de bord des restes a payer');
  210.         $sheet->getStyle('A1')->getFont()->setBold(true)->setSize(16)->getColor()->setRGB('FFFFFF');
  211.         $sheet->getStyle('A1')->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
  212.         $sheet->getStyle('A1:G1')->getFill()->setFillType(Fill::FILL_SOLID)->getStartColor()->setRGB('405189');
  213.         $sheet->mergeCells('A2:G2');
  214.         $sheet->setCellValue('A2'sprintf(
  215.             'Type: %s | Client: %s | Periode: %s | Paiement: %s',
  216.             $filters['type_label'],
  217.             $filters['client_label'],
  218.             $this->formatDashboardPeriodLabel($filters['date_from'], $filters['date_to']),
  219.             $filters['payment_label']
  220.         ));
  221.         $sheet->getStyle('A2')->getFont()->setItalic(true)->getColor()->setRGB('495057');
  222.         $sheet->getStyle('A2')->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
  223.         $headers = ['Type''Numero''Date''Client''Total TTC''Paye''Reste a payer'];
  224.         $sheet->fromArray($headersnull'A4');
  225.         $sheet->getStyle('A4:G4')->getFont()->setBold(true)->getColor()->setRGB('000000');
  226.         $sheet->getStyle('A4:G4')->getFill()->setFillType(Fill::FILL_SOLID)->getStartColor()->setRGB('D0E3FA');
  227.         $sheet->getStyle('A4:G4')->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
  228.         $row 5;
  229.         foreach ($documents as $document) {
  230.             $sheet->setCellValue("A$row"$document['typeLabel']);
  231.             $sheet->setCellValue("B$row"$document['number']);
  232.             if ($document['date'] instanceof DateTimeInterface) {
  233.                 $sheet->setCellValue("C$row"\PhpOffice\PhpSpreadsheet\Shared\Date::PHPToExcel($document['date']));
  234.             } else {
  235.                 $sheet->setCellValue("C$row"'');
  236.             }
  237.             $sheet->setCellValue("D$row"$document['client']);
  238.             $sheet->setCellValue("E$row"round((float) $document['total'], 3));
  239.             $sheet->setCellValue("F$row"round((float) $document['paid'], 3));
  240.             $sheet->setCellValue("G$row"round((float) $document['remaining'], 3));
  241.             if ($row === 0) {
  242.                 $sheet->getStyle("A$row:G$row")->getFill()->setFillType(Fill::FILL_SOLID)->getStartColor()->setRGB('F8F9FA');
  243.             }
  244.             $row++;
  245.         }
  246.         $totalRow $row;
  247.         $sheet->setCellValue("A$totalRow"'Total');
  248.         $sheet->mergeCells("A$totalRow:D$totalRow");
  249.         $sheet->setCellValue("E$totalRow"round((float) $totals['total'], 3));
  250.         $sheet->setCellValue("F$totalRow"round((float) $totals['paid'], 3));
  251.         $sheet->setCellValue("G$totalRow"round((float) $totals['remaining'], 3));
  252.         $sheet->getStyle("A$totalRow:G$totalRow")->getFont()->setBold(true)->getColor()->setRGB('9C0006');
  253.         $sheet->getStyle("A$totalRow:G$totalRow")->getFill()->setFillType(Fill::FILL_SOLID)->getStartColor()->setRGB('FFD9D9');
  254.         $sheet->getStyle("A$totalRow:D$totalRow")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
  255.         $lastRow max($totalRow5);
  256.         $sheet->getStyle("C5:C$lastRow")->getNumberFormat()->setFormatCode('dd/mm/yyyy');
  257.         $sheet->getStyle("E5:G$lastRow")->getNumberFormat()->setFormatCode('#,##0.000');
  258.         $sheet->getStyle("A4:G$lastRow")->getBorders()->getAllBorders()->setBorderStyle(Border::BORDER_THIN)->getColor()->setRGB('E9EBEC');
  259.         $sheet->getStyle("E5:G$lastRow")->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT);
  260.         $sheet->getStyle("A4:G$lastRow")->getAlignment()->setVertical(Alignment::VERTICAL_CENTER);
  261.         foreach (range('A''G') as $column) {
  262.             $sheet->getColumnDimension($column)->setAutoSize(true);
  263.         }
  264.         $sheet->freezePane('A5');
  265.         $sheet->setAutoFilter("A4:G$lastRow");
  266.         return $spreadsheet;
  267.     }
  268.     private function formatDashboardPeriodLabel(string $dateFromstring $dateTo): string
  269.     {
  270.         if ($dateFrom === '' && $dateTo === '') {
  271.             return 'Toutes les dates';
  272.         }
  273.         if ($dateFrom !== '' && $dateTo !== '') {
  274.             return $this->formatFilterDate($dateFrom).' au '.$this->formatFilterDate($dateTo);
  275.         }
  276.         if ($dateFrom !== '') {
  277.             return 'A partir du '.$this->formatFilterDate($dateFrom);
  278.         }
  279.         return 'Jusqu au '.$this->formatFilterDate($dateTo);
  280.     }
  281.     private function formatFilterDate(string $date): string
  282.     {
  283.         $dateTime DateTimeImmutable::createFromFormat('Y-m-d'$date);
  284.         return $dateTime instanceof DateTimeImmutable $dateTime->format('d/m/Y') : $date;
  285.     }
  286.     private function buildDashboardExportFilename(array $filters): string
  287.     {
  288.         $parts = [
  289.             'reste_a_payer',
  290.             $filters['type'] !== '' $filters['type'] : 'tous_types',
  291.             $filters['client_label'] !== 'Tous les clients' $filters['client_label'] : 'tous_clients',
  292.         ];
  293.         if ($filters['date_from'] !== '' || $filters['date_to'] !== '') {
  294.             $parts[] = ($filters['date_from'] !== '' str_replace('-'''$filters['date_from']) : 'debut')
  295.                 .'_au_'
  296.                 .($filters['date_to'] !== '' str_replace('-'''$filters['date_to']) : 'fin');
  297.         } else {
  298.             $parts[] = 'toutes_dates';
  299.         }
  300.         if ($filters['unpaid_only']) {
  301.             $parts[] = 'non_payes';
  302.         }
  303.         $filename strtolower(implode('_'$parts));
  304.         $filename preg_replace('/[^a-z0-9_]+/i''_'$filename) ?? 'reste_a_payer';
  305.         $filename trim($filename'_');
  306.         return $filename.'.xlsx';
  307.     }
  308.     private function createDateFilter(string $datebool $endOfDay): ?DateTimeImmutable
  309.     {
  310.         if ($date === '') {
  311.             return null;
  312.         }
  313.         $dateTime DateTimeImmutable::createFromFormat('Y-m-d'$date);
  314.         if (!$dateTime instanceof DateTimeImmutable) {
  315.             return null;
  316.         }
  317.         return $endOfDay $dateTime->setTime(235959) : $dateTime->setTime(000);
  318.     }
  319.     /**
  320.      * @return Devis[]
  321.      */
  322.     private function findDashboardDevis(DevisRepository $devisRepository, ?Client $client, ?DateTimeInterface $dateFrom, ?DateTimeInterface $dateTo, array $statuts): array
  323.     {
  324.         $queryBuilder $devisRepository->createQueryBuilder('d')
  325.             ->leftJoin('d.client''c')
  326.             ->addSelect('c')
  327.             ->leftJoin('d.paymentdevis''paymentdevis')
  328.             ->addSelect('paymentdevis')
  329.             ->andWhere('d.statut IN (:statuts)')
  330.             ->setParameter('statuts'$statuts)
  331.             ->orderBy('d.date''DESC')
  332.             ->addOrderBy('d.datecreation''DESC')
  333.             ->addOrderBy('d.id''DESC');
  334.         if ($client instanceof Client) {
  335.             $queryBuilder
  336.                 ->andWhere('d.client = :client')
  337.                 ->setParameter('client'$client);
  338.         }
  339.         if ($dateFrom instanceof DateTimeInterface) {
  340.             $queryBuilder
  341.                 ->andWhere('COALESCE(d.date, d.datecreation) >= :dateFrom')
  342.                 ->setParameter('dateFrom'$dateFrom);
  343.         }
  344.         if ($dateTo instanceof DateTimeInterface) {
  345.             $queryBuilder
  346.                 ->andWhere('COALESCE(d.date, d.datecreation) <= :dateTo')
  347.                 ->setParameter('dateTo'$dateTo);
  348.         }
  349.         return $queryBuilder->getQuery()->getResult();
  350.     }
  351.     /**
  352.      * @return Bondesortie[]
  353.      */
  354.     private function findDashboardBondesorties(BondesortieRepository $bondesortieRepository, ?Client $client, ?DateTimeInterface $dateFrom, ?DateTimeInterface $dateTo): array
  355.     {
  356.         $queryBuilder $bondesortieRepository->createQueryBuilder('b')
  357.             ->leftJoin('b.client''c')
  358.             ->addSelect('c')
  359.             ->orderBy('b.date''DESC')
  360.             ->addOrderBy('b.datecreation''DESC')
  361.             ->addOrderBy('b.id''DESC');
  362.         if ($client instanceof Client) {
  363.             $queryBuilder
  364.                 ->andWhere('b.client = :client')
  365.                 ->setParameter('client'$client);
  366.         }
  367.         if ($dateFrom instanceof DateTimeInterface) {
  368.             $queryBuilder
  369.                 ->andWhere('COALESCE(b.date, b.datecreation) >= :dateFrom')
  370.                 ->setParameter('dateFrom'$dateFrom);
  371.         }
  372.         if ($dateTo instanceof DateTimeInterface) {
  373.             $queryBuilder
  374.                 ->andWhere('COALESCE(b.date, b.datecreation) <= :dateTo')
  375.                 ->setParameter('dateTo'$dateTo);
  376.         }
  377.         return $queryBuilder->getQuery()->getResult();
  378.     }
  379.     private function createDashboardStats(): array
  380.     {
  381.         return [
  382.             'Facture' => [
  383.                 'label' => 'Factures',
  384.                 'shortLabel' => 'Facture',
  385.                 'route' => 'app_devis_liste_facture',
  386.                 'color' => 'primary',
  387.                 'icon' => 'ri-file-list-3-line',
  388.                 'count' => 0,
  389.                 'total' => 0.0,
  390.                 'paid' => 0.0,
  391.                 'remaining' => 0.0,
  392.             ],
  393.             'BL' => [
  394.                 'label' => 'Bons de livraison',
  395.                 'shortLabel' => 'BL',
  396.                 'route' => 'app_devis_liste_bl',
  397.                 'color' => 'success',
  398.                 'icon' => 'ri-truck-line',
  399.                 'count' => 0,
  400.                 'total' => 0.0,
  401.                 'paid' => 0.0,
  402.                 'remaining' => 0.0,
  403.             ],
  404.             'BLP' => [
  405.                 'label' => 'BL particuliers',
  406.                 'shortLabel' => 'BLP',
  407.                 'route' => 'app_devis_particulier_index',
  408.                 'color' => 'warning',
  409.                 'icon' => 'ri-user-received-2-line',
  410.                 'count' => 0,
  411.                 'total' => 0.0,
  412.                 'paid' => 0.0,
  413.                 'remaining' => 0.0,
  414.             ],
  415.             'BS' => [
  416.                 'label' => 'Bons de sortie',
  417.                 'shortLabel' => 'BS',
  418.                 'route' => 'app_bondesortie_index',
  419.                 'color' => 'danger',
  420.                 'icon' => 'ri-archive-drawer-line',
  421.                 'count' => 0,
  422.                 'total' => 0.0,
  423.                 'paid' => 0.0,
  424.                 'remaining' => 0.0,
  425.             ],
  426.         ];
  427.     }
  428.     private function addDevisDashboardDocument(Devis $devis, array &$stats, array &$documentsbool $unpaidOnly): void
  429.     {
  430.         $type $devis->getStatut();
  431.         if (!isset($stats[$type])) {
  432.             return;
  433.         }
  434.         $total $this->firstPositiveAmount([$devis->getMontantNet(), $devis->getTotalttc(), $devis->getTotal()]);
  435.         $payment $devis->getPaymentdevis()->first();
  436.         $remaining $total;
  437.         if ($payment !== false) {
  438.             $remaining $this->firstAvailableAmount([$payment->getMontant(), $payment->getReste()], $total);
  439.         }
  440.         $remaining max(0.0$remaining);
  441.         if (!$this->shouldKeepDashboardDocument($remaining$unpaidOnly)) {
  442.             return;
  443.         }
  444.         $paid max(0.0$total $remaining);
  445.         $date $devis->getDate() ?? $devis->getDatecreation();
  446.         $this->addDashboardAmounts($stats[$type], $total$paid$remaining);
  447.         $documents[] = [
  448.             'id' => $devis->getId(),
  449.             'type' => $type,
  450.             'typeLabel' => $stats[$type]['shortLabel'],
  451.             'typeColor' => $stats[$type]['color'],
  452.             'number' => $devis->getNum() ?: '#'.$devis->getId(),
  453.             'date' => $date,
  454.             'dateTimestamp' => $date instanceof DateTimeInterface $date->getTimestamp() : 0,
  455.             'client' => $this->getDevisClientLabel($devis),
  456.             'total' => $total,
  457.             'paid' => $paid,
  458.             'remaining' => $remaining,
  459.             'url' => $type === 'BLP'
  460.                 $this->generateUrl('app_devis_particulier_show', ['id' => $devis->getId()])
  461.                 : $this->generateUrl('app_devis_show', ['id' => $devis->getId()]),
  462.         ];
  463.     }
  464.     private function addBondesortieDashboardDocument(Bondesortie $bondesortie, array &$stats, array &$documentsbool $unpaidOnly): void
  465.     {
  466.         $type 'BS';
  467.         $total $this->firstPositiveAmount([$bondesortie->getTotalttc(), $bondesortie->getTotaleht()]);
  468.         $remaining $total;
  469.         if (!$this->shouldKeepDashboardDocument($remaining$unpaidOnly)) {
  470.             return;
  471.         }
  472.         $paid 0.0;
  473.         $date $bondesortie->getDate() ?? $bondesortie->getDatecreation();
  474.         $this->addDashboardAmounts($stats[$type], $total$paid$remaining);
  475.         $documents[] = [
  476.             'id' => $bondesortie->getId(),
  477.             'type' => $type,
  478.             'typeLabel' => $stats[$type]['shortLabel'],
  479.             'typeColor' => $stats[$type]['color'],
  480.             'number' => $bondesortie->getNumbs() ?: '#'.$bondesortie->getId(),
  481.             'date' => $date,
  482.             'dateTimestamp' => $date instanceof DateTimeInterface $date->getTimestamp() : 0,
  483.             'client' => $this->getBondesortieClientLabel($bondesortie),
  484.             'total' => $total,
  485.             'paid' => $paid,
  486.             'remaining' => $remaining,
  487.             'url' => $this->generateUrl('app_bondesortie_show', ['id' => $bondesortie->getId()]),
  488.         ];
  489.     }
  490.     private function shouldKeepDashboardDocument(float $remainingbool $unpaidOnly): bool
  491.     {
  492.         return !$unpaidOnly || round($remaining3) > 0.001;
  493.     }
  494.     private function addDashboardAmounts(array &$statfloat $totalfloat $paidfloat $remaining): void
  495.     {
  496.         $stat['count']++;
  497.         $stat['total'] += $total;
  498.         $stat['paid'] += $paid;
  499.         $stat['remaining'] += $remaining;
  500.     }
  501.     private function firstPositiveAmount(array $amounts): float
  502.     {
  503.         foreach ($amounts as $amount) {
  504.             $parsedAmount $this->parseAmount($amount);
  505.             if ($parsedAmount 0) {
  506.                 return $parsedAmount;
  507.             }
  508.         }
  509.         return 0.0;
  510.     }
  511.     private function firstAvailableAmount(array $amountsfloat $fallback): float
  512.     {
  513.         foreach ($amounts as $amount) {
  514.             if ($amount !== null && trim((string) $amount) !== '') {
  515.                 return $this->parseAmount($amount);
  516.             }
  517.         }
  518.         return $fallback;
  519.     }
  520.     private function parseAmount($value): float
  521.     {
  522.         $normalized trim((string) $value);
  523.         if ($normalized === '') {
  524.             return 0.0;
  525.         }
  526.         $normalized preg_replace('/[^0-9,\.\-]/'''$normalized) ?? '';
  527.         if (str_contains($normalized',') && !str_contains($normalized'.')) {
  528.             $normalized str_replace(',''.'$normalized);
  529.         } else {
  530.             $normalized str_replace(','''$normalized);
  531.         }
  532.         return is_numeric($normalized) ? (float) $normalized 0.0;
  533.     }
  534.     private function getDevisClientLabel(Devis $devis): string
  535.     {
  536.         if ($devis->getClient() instanceof Client) {
  537.             return $this->formatClientLabel($devis->getClient());
  538.         }
  539.         $nomClient trim((string) $devis->getNomclient());
  540.         return $nomClient !== '' $nomClient '-';
  541.     }
  542.     private function getBondesortieClientLabel(Bondesortie $bondesortie): string
  543.     {
  544.         if ($bondesortie->getClient() instanceof Client) {
  545.             return $this->formatClientLabel($bondesortie->getClient());
  546.         }
  547.         $nomPrenom trim((string) $bondesortie->getNomprenom());
  548.         return $nomPrenom !== '' $nomPrenom '-';
  549.     }
  550.     private function formatClientLabel(Client $client): string
  551.     {
  552.         $raisonSociale trim((string) $client->getRaisonsocial());
  553.         if ($raisonSociale !== '') {
  554.             return $raisonSociale;
  555.         }
  556.         $nomComplet trim(sprintf('%s %s', (string) $client->getNom(), (string) $client->getPrenom()));
  557.         return $nomComplet !== '' $nomComplet '-';
  558.     }
  559.     #[Route('admin/Listes_techniciens'name'app_listeassitant')]
  560.     public function listeassitant(Request $requestUserRepository $userRepository): Response
  561.     {
  562.         $assistant$userRepository->findByRoleTechnicien();
  563.         return $this->render('registration/listeassistant.html.twig', [
  564.                 'assistants' =>$assistant,
  565.         ]);
  566.     }
  567.     #[Route('admin/Listes_clients'name'app_clients_liste')]
  568.     public function listeclient(Request $requestClientRepository $clientRepository): Response
  569.     {
  570.         $assistant$clientRepository->findAll();
  571.         return $this->render('registration/listeclient.html.twig', [
  572.             'clients' =>$assistant,
  573.         ]);
  574.     }
  575.     #[Route('admin/technicien'name'app_assistant')]
  576.     public function addadmin(Request $requestUserPasswordHasherInterface $userPasswordHasherMailerInterface $mailerEntityManagerInterface $entityManager): Response
  577.     {
  578.         $user = new User();
  579.         $form $this->createForm(TechnicienType::class, $user);
  580.         $form->handleRequest($request);
  581.         if ($form->isSubmitted() && $form->isValid()) {
  582.             // Generate a password
  583.             $password $this->generatePassword(12);
  584.             // Encode the plain password
  585.             $user->setPassword(
  586.                 $userPasswordHasher->hashPassword(
  587.                     $user,
  588.                     $password
  589.                 )
  590.             );
  591.             $user->setRoles(['ROLE_TECHNICIEN']);
  592.             $user->setPhoto("user.png");
  593.             $entityManager->persist($user);
  594.             $entityManager->flush();
  595.             // Send email
  596.             $email = (new TemplatedEmail())
  597.                 ->from(new Address('support@smart-it-partner.com''Tricotage '))
  598.                 ->to($user->getEmail())
  599.                 ->subject('Démarrez Votre Parcours avec Tricotage en tant que Technicien')
  600.                 ->htmlTemplate('registration/send_email.html.twig')
  601.                 ->context([
  602.                     'emails' => $user->getEmail(),
  603.                     'password' => $password,'user'=>$user,
  604.                 ]);
  605.             $mailer->send($email);
  606.             return $this->redirectToRoute('app_listeassitant');
  607.         }
  608.         return $this->render('registration/ajouterassistant.html.twig', [
  609.             'registrationForm' => $form->createView(),
  610.         ]);
  611.     }
  612.     #[Route('/techniciens/{id}/edit'name'app_techniciens_edit'methods: ['GET''POST'])]
  613.     public function edittechniciens(Request $requestUser $user,UserRepository $userRepository): Response
  614.     {
  615.         $form $this->createForm(TechnicienType::class, $user);
  616.         $form->handleRequest($request);
  617.         if ($form->isSubmitted() && $form->isValid()) {
  618.             $userRepository->save($usertrue);
  619.             return $this->redirectToRoute('app_listeassitant', [], Response::HTTP_SEE_OTHER);
  620.         }
  621.         return $this->renderForm('registration/edittechniciens.html.twig', [
  622.             'user' => $user,
  623.             'registrationForm' => $form,
  624.         ]);
  625.     }
  626.     #[Route('admin/clients'name'app_clients')]
  627.     public function addclients(Request $requestUserPasswordHasherInterface $userPasswordHasherMailerInterface $mailerEntityManagerInterface $entityManager): Response
  628.     {
  629.         $user = new Client();
  630.         $form $this->createForm(ClientType::class, $user);
  631.         $form->handleRequest($request);
  632.         if ($form->isSubmitted() && $form->isValid()) {
  633.             // Generate a password
  634.             $user->setPhoto("user.png");
  635.             $entityManager->persist($user);
  636.             $entityManager->flush();
  637.             return $this->redirectToRoute('app_clients_liste');
  638.         }
  639.         return $this->render('registration/ajouterclient.html.twig', [
  640.             'registrationForm' => $form->createView(),
  641.         ]);
  642.     }
  643.     #[Route('/verifier-client'name'verifier_client'methods: ['POST'])]
  644.     public function verifierClient(Request $requestClientRepository $clientRepository): JsonResponse
  645.     {
  646.         // Récupérer la Raison Sociale envoyée par la requête AJAX
  647.         $raisonSociale $request->request->get('raisonSociale');
  648. //        dump($raisonSociale);
  649.         // Vérifier si un client avec cette Raison Sociale existe
  650.         if ($raisonSociale) {
  651.             $client $clientRepository->findOneBy(['raisonsocial' => $raisonSociale]);
  652.             // Si le client existe, retourner une réponse JSON
  653.             if ($client) {
  654.                 return new JsonResponse([
  655.                     'exists' => true,
  656.                     'message' => 'Le client existe déjà avec cette Raison Sociale.'
  657.                 ]);
  658.             }
  659.             // Si le client n'existe pas, retourner une réponse JSON indiquant cela
  660.             return new JsonResponse([
  661.                 'exists' => false,
  662.                 'message' => 'Aucun client trouvé avec cette Raison Sociale.'
  663.             ]);
  664.         }
  665.         // En cas de requête invalide ou sans Raison Sociale
  666.         return new JsonResponse([
  667.             'exists' => false,
  668.             'message' => 'Raison Sociale non fournie.'
  669.         ], 400);  // Code HTTP 400 pour indiquer une requête invalide
  670.     }
  671.     private function generatePassword($size)
  672.     {
  673.         $characters = array(0123456789"a""b""c""d""e""f""g""h""i""j""k""l""m""n""o""p""q""r""s""t""u""v""w""x""y""z");
  674.         $password '';
  675.         for ($i 0$i $size$i++) {
  676.             $password .= ($i 2) ? strtoupper($characters[array_rand($characters)]) : $characters[array_rand($characters)];
  677.         }
  678.         return $password;
  679.     }
  680.     #[Route('admin/edit_profil'name'app_editprofile',methods: ['GET''POST'])]
  681.     public function editprofile(Request $requestUserRepository $userRepository): Response
  682.     {
  683.         $user$userRepository->findOneBy(['id'=>$this->getUser()->getid()]) ;
  684.         $name $user->getPhoto();
  685.         $form $this->createForm(AdminEditType::class, $user);
  686.         $form->handleRequest($request);
  687.         if ($form->isSubmitted() && $form->isValid()) {
  688.             $logo $form->get('photo')->getData();
  689.             if ($logo) {
  690.                 $originalLogoname pathinfo$logo->getClientOriginalName(), PATHINFO_FILENAME);
  691.                 $newLogoname =   $originalLogoname.'-'.uniqid().'.'.$logo->guessExtension();
  692.                 // Move the file to the directory where brochures are stored
  693.                 try {
  694.                     $logo->move(
  695.                         $this->getParameter('admin_directory'),
  696.                         $newLogoname
  697.                     );
  698.                 } catch (FileException $e) {
  699.                     // ... handle exception if something happens during file upload
  700.                 }
  701.                 $user->setPhoto($newLogoname);
  702.             }
  703.             else
  704.             {
  705.                 $user->setPhoto($name);
  706.             }
  707.             $userRepository->save($user,true);
  708.             $this->addFlash('success''Votre compte a été modifié avec succès');
  709.             return $this->redirectToRoute('app_editprofile');
  710.         }
  711.         return $this->render('editprofile/editprofile.html.twig', [
  712.             'users' => $user,
  713.             'form' => $form->createView(),
  714.         ]);
  715.     }
  716.     #[Route('admin/edit_password'name'modifier-password')]
  717.     public function editpassword(Request $requestUserRepository $userRepository ,UserPasswordHasherInterface $userPasswordHasher): Response
  718.     {
  719.         $user $this->getUser();
  720.         // Vérifier si l'utilisateur est connecté
  721.         if (!$user) {
  722.             throw $this->createAccessDeniedException('Vous devez être connecté pour modifier le mot de passe.');
  723.         }
  724.         $user$userRepository->findOneBy(['id'=>$this->getUser()->getid()]) ;
  725. // Récupérer les données du formulaire
  726.         $currentPassword $request->request->get('current-password');
  727.         $newPassword $request->request->get('new-password');
  728. // Vérifier si le mot de passe actuel est correct
  729.         if (!$userPasswordHasher->isPasswordValid($user$currentPassword)) {
  730.             $this->addFlash('error''Mot de passe actuel incorrect.');
  731.             return $this->redirectToRoute('app_editprofile');
  732.         }
  733. // Encoder et définir le nouveau mot de passe
  734.         $encodedPassword $userPasswordHasher->hashPassword($user$newPassword);
  735.         $user->setPassword($encodedPassword);
  736. // Enregistrer les modifications dans la base de données
  737.         $userRepository->save($user,true);
  738.         $this->addFlash('success''Le mot de passe a été modifié avec succès.');
  739.         return $this->redirectToRoute('app_editprofile');
  740.     }
  741.     #[Route('admin/intervention'name'app_intervention')]
  742.     public function intervention(Request $requestUserRepository $userRepository): Response
  743.     {
  744.         $assistant$userRepository->findByRoleclient();
  745.         return $this->render('intervention/intervention.html.twig', [
  746.             'assistants' =>$assistant,
  747.         ]);
  748.     }
  749.     #[Route('/deletetechnicien/{id}'name'app_tecnicien_delete')]
  750.     public function deletetechniciens(Request $requestUser $user UserRepository $userRepository): Response
  751.     {
  752. //        if ($this->isCsrfTokenValid('delete'.$devi->getId(), $request->request->get('_token'))) {
  753.         $userRepository->remove($usertrue);
  754. //        }
  755.         return $this->redirectToRoute('app_listeassitant', [], Response::HTTP_SEE_OTHER);
  756.     }
  757.     //Fonction mettre a jous les deux table intervention et devis a jours: si on click sur les liste dans espace admin le champs lu sera true avec la fonction ajax dans base.html.twig à l'aide de data-value
  758.     #[Route('/update_elements'name'update_elements'methods: ['POST','GET'])]
  759.     public function updateElements(DevisRepository $devisRepository,InterventionRepository $interventionRepository,Request $request,EntityManagerInterface $entityManager): JsonResponse
  760.     {
  761.         $url$request->request->get('url');
  762.         // Mettre à jour les demande devis
  763.         if ($url === 'app_devis_demande') {
  764. //            dump($url);
  765.             $devis $devisRepository->findBy(['lu' => false'statut'=>'Demande devis']);
  766.             foreach ($devis as $devi) {
  767.                 $devi->setLu(true);
  768.                 // Peut-être d'autres opérations ici, selon vos besoins
  769.             }
  770.         }
  771.         if ($url === 'app_devis_index') {
  772.             $devis $devisRepository->findBy(['lu' => false'statut'=>'Demande devis']);
  773.             foreach ($devis as $devi) {
  774.                 $devi->setLu(true);
  775.                 // Peut-être d'autres opérations ici, selon vos besoins
  776.             }
  777.         }
  778.         // Mettre à jour les contacts
  779.         if ($url === 'app_intervention_index') {
  780.             $intervention $interventionRepository->findBy(['lu' => false]);
  781.             foreach ($intervention as $interventions) {
  782.                 $interventions->setLu(true);
  783.                 // Peut-être d'autres opérations ici, selon vos besoins
  784.             }
  785.         }
  786.         // Enregistrer les modifications dans la base de données
  787.         $entityManager->flush();
  788.         // Répondre avec un JSON pour indiquer le succès de l'opération
  789.         return new JsonResponse(['success' => true]);
  790.     }
  791.     //La fonction compte le nombre total de notifications dans l'espace administrateur, demandes de devis et interventions signalées par le technicien.
  792.     #[Route('/totalnotif'name'app_total_notif'methods: ['GET'])]
  793.     public function totalnotif(DevisRepository $devisRepository,InterventionRepository $interventionRepository,Request $request): Response
  794.     {
  795.         $totaldevis $devisRepository->getDevisByTechnicianRoleNonLu();
  796.         $totalintervention=$interventionRepository->findBy(['lu'=>false]);
  797.         $output[]=array(count($totaldevis));
  798.         $output[]=array(count($totalintervention) );
  799.         $output[]=array(count($totaldevis) + count($totalintervention) );
  800. //        dump($output);
  801.         return  new JsonResponse($output);
  802.     }
  803.     #[Route('/notifsintervention'name'app_notifsintervention')]
  804.     public function notifsmessage(UrlGeneratorInterface $router,InterventionRepository $interventionRepository)
  805.     {
  806.         $intervention $interventionRepository->getInterventionsByTechnicianRoleNonLu();
  807.         $interventions = array();
  808.         foreach ($intervention as $r) {
  809.             $datee $r->getDatecreation() ? $r->getDatecreation()->format('d-m-Y H:i') : null;
  810.             $data = [
  811.                 'id' => $r->getId(),
  812.                 'nomtech' => $r->getUser()->getNom() ,
  813.                 'prenomtech' => $r->getUser()->getPrenom() ,
  814.                 'datee' => $datee,
  815. //                'url' => $router->generate('app_message_repondre', ['id' => $r->getId()])
  816.                 // Ajoutez d'autres champs si nécessaire
  817.             ];
  818.             $interventions[] = $data;
  819.         }
  820.         return new JsonResponse($interventions);
  821.     }
  822.     #[Route('/notifsdemandedevis'name'app_notifsdemande_devis')]
  823.     public function notifsdemandedevis(UrlGeneratorInterface $router,DevisRepository $devisRepository)
  824.     {
  825.         $devis $devisRepository->getDevisByTechnicianRoleNonLu();
  826.         $deviss = array();
  827.         foreach ($devis as $r) {
  828.             $datee $r->getDatecreation() ? $r->getDatecreation()->format('d-m-Y H:i') : null;
  829.             $data = [
  830.                 'id' => $r->getId(),
  831.                 'nomtech' => $r->getUser()->getNom() ,
  832.                 'prenomtech' => $r->getUser()->getPrenom() ,
  833.                 'nomclient' => $r->getClient()->getNom() ,
  834.                 'prenomclient' => $r->getClient()->getPrenom() ,
  835.                 'datee' => $datee,
  836.                 'url' => $router->generate('app_confirmation_demande_devis', ['id' => $r->getId()])
  837.                 // Ajoutez d'autres champs si nécessaire
  838.             ];
  839.             $deviss[] = $data;
  840.         }
  841.         return new JsonResponse($deviss);
  842.     }
  843. }