src/Controller/AjaxController.php line 178

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Entity\HistoriqueUtilisateur;
  4. use App\Entity\InfosSuivi;
  5. use App\Entity\Proposition;
  6. use App\Repository\HistoriqueUtilisateurRepository;
  7. use App\Repository\PropositionRepository;
  8. use DateTime;
  9. use App\Entity\Lot;
  10. use App\Entity\Prospect;
  11. use App\Service\Geocoder;
  12. use App\Service\ImagePdf;
  13. use App\Entity\Historique;
  14. use App\Entity\Impression;
  15. use App\Entity\PieceJointe;
  16. use App\Entity\Utilisateur;
  17. use App\Service\Notification;
  18. use Doctrine\ORM\EntityManager;
  19. use App\Repository\LotRepository;
  20. use App\Repository\VilleRepository;
  21. use App\Repository\ProspectRepository;
  22. use App\Repository\HistoriqueRepository;
  23. use Doctrine\ORM\EntityManagerInterface;
  24. use App\Repository\UtilisateurRepository;
  25. use App\Repository\ProgrammeRepository;
  26. use App\Repository\DepartementRepository;
  27. use phpDocumentor\Reflection\Types\This;
  28. use Symfony\Component\HttpFoundation\Request;
  29. use Symfony\Component\HttpFoundation\Response;
  30. use Doctrine\ORM\Query\AST\NewObjectExpression;
  31. use Symfony\Component\Routing\Annotation\Route;
  32. use Symfony\Component\HttpFoundation\JsonResponse;
  33. use Symfony\Component\HttpFoundation\Session\Session;
  34. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  35. /**
  36.  * @Route("/ajax")
  37.  */
  38. class AjaxController extends AbstractController
  39. {
  40.     protected $notificationService;
  41.     public function __construct(Notification $notificationService)
  42.     {
  43.         $this->notificationService $notificationService;
  44.     }
  45.     /**
  46.      * @Route("/ville", name="ajax_ville")
  47.      */
  48.     public function ville(Request $requestVilleRepository $villeRepository): Response
  49.     {
  50.         $searchTerm $request->query->get('search'null);
  51.         $villes $villeRepository->findByNomReel($searchTerm);
  52.         $data = [];
  53.         foreach ($villes as $ville) {
  54.             $cpRaw = (string) ($ville->getCodePostal() ?? '');
  55.             $cpRaw trim($cpRaw);
  56.             $cpFirst $cpRaw;
  57.             if ($cpRaw !== '' && strpos($cpRaw'-') !== false) {
  58.                 $parts explode('-'$cpRaw2);
  59.                 $cpFirst trim($parts[0] ?? '');
  60.             }
  61.             $data[] = [
  62.                 'value' => $ville->getNomReel(),
  63.                 'text' => $ville->getNomReel() . ($cpFirst !== '' ' (' $cpFirst ')' ''),
  64.                 'code_postal' => $cpFirst,
  65.             ];
  66.         }
  67.         return $this->json($data);
  68.     }
  69.     /**
  70.      * @Route("/departement", name="ajax_departement")
  71.      */
  72.     public function departement(Request $requestDepartementRepository $departementRepository): Response
  73.     {
  74.         $departements $departementRepository->findByNomOrCode($request->query->get('search'null));
  75.         $data = [];
  76.         foreach($departements as $departement) {
  77.             $data[] = [
  78.                 'id' => $departement->getId(),
  79.                 'text' => $departement->getNom() . ' (' $departement->getCode() . ')'
  80.             ];
  81.         }
  82.         return $this->json($data);
  83.     }
  84.     /**
  85.      * @Route("/code-postal", name="ajax_code_postal")
  86.      */
  87.     public function codePostal(Request $requestVilleRepository $villeRepository): Response
  88.     {
  89.         $searchTerm $request->query->get('search'null);
  90.         $villes $villeRepository->findByCodePostal($searchTerm);
  91.         $data = [];
  92.         foreach ($villes as $ville) {
  93.             $data[] = [
  94.                 'id' => $ville->getCodePostal(),
  95.                 'text' => $ville->getNomReel() . ' (' $ville->getCodePostal() . ')'
  96.             ];
  97.         }
  98.         return $this->json($data);
  99.     }
  100.     /**
  101.      * @Route("/lot", name="ajax_lot")
  102.      */
  103.     public function lot(Request $requestLotRepository $lotRepository): Response
  104.     {
  105.         $user $this->getUser();
  106.         $entreprise $user->getEntreprise();
  107.     
  108.         $lots $lotRepository->findByProgrammeRefTitreAndEntreprise($request->query->get('search'null), $entreprise$user);
  109.     
  110.         $data = [];
  111.         foreach($lots as $lot) {
  112.             $data[] = [
  113.                 'id' => $lot->getId(),
  114.                 'text' => (string)$lot,
  115.                 'reference' => $lot->getReference(),
  116.                 'programme' => $lot->getProgramme(),
  117.                 'type' => $lot->getType(),
  118.                 'ville' => $lot->getVille(),
  119.                 'codePostal' => $lot->getCodePostal()
  120.             ];
  121.         }
  122.         return $this->json([
  123.             'results' => $data
  124.         ]);
  125.     }
  126.     /**
  127.      * @Route("/prospect", name="ajax_prospect")
  128.      */
  129.     public function prospect(Request $requestProspectRepository $prospectRepository): Response
  130.     {
  131.         $prospects $prospectRepository->findByPrenomNom($request->query->get('search'null));
  132.         //dd($prospects);
  133.         $data = [];
  134.         foreach($prospects as $prospect) {
  135.             $data[] = [
  136.                 'id' => $prospect->getId(),
  137.                 'text' => $prospect->getEmprunteurPrenom()." ".$prospect->getEmprunteurNom(),
  138.                 'nom' => $prospect->getEmprunteurNom(),
  139.                 'prenom' => $prospect->getEmprunteurPrenom()
  140.             ];
  141.         }
  142.         return $this->json([
  143.             'results' => $data
  144.         ]);
  145.     }
  146.     /**
  147.      * @Route("/programme", name="ajax_programme")
  148.      */
  149.     public function programme(Request $requestProgrammeRepository $programmeRepository): Response
  150.     {
  151.         $user $this->getUser();
  152.         $entreprise $user->getEntreprise();
  153.     
  154.         $programmes $programmeRepository->findByNomOrPromoteurOrVilleAndEntreprise($request->query->get('search'null), $entreprise$user);
  155.         foreach($programmes as $p) {
  156.             $data[] = [
  157.                 'id' => $p->getId(),
  158.                 'text' => (string)$p . (trim((string)$p->getPromoteur()) != '' ' (' $p->getPromoteur() . ')' ''),
  159.                 'ville' =>(string)$p->getVille(),
  160.                 'programme' => (string)$p,
  161.                 'promoteur' => $p->getPromoteur(),
  162.             ];
  163.         }
  164.         return $this->json([
  165.             'results' => $data
  166.         ]);
  167.     }
  168.     /**
  169.      * @Route("/prospect/add/lot/{lotId}", name="ajax_prospect_add_lot", methods={"POST"})
  170.      */
  171.     public function addLotToProspect(Lot $lotId ,Request $requestProspectRepository $prospectRepositoryPropositionRepository $propositionRepositoryEntityManagerInterface $em): Response
  172.     {
  173.         $prospect $prospectRepository->findBy(['id' => $request->request->get('prospectId')]);
  174.         if(!empty($propositionRepository->findByLotAndProspect($lotId$prospect))){
  175.             return $this->json([
  176.                 'warning' => 'Ce lot est déja proposé à ce client'
  177.             ]);
  178.         }
  179.         $newProposition = new Proposition();
  180.         $newProposition->setLot($lotId);
  181.         $prospect[0]->addProposition($newProposition);
  182.         $em->persist($newProposition);
  183.         $em->flush();
  184.         return $this->json([
  185.             'success' => 'Proposition ajoutée'
  186.         ]);
  187.     }
  188.     /**
  189.      * @Route("/geocode/adresse", name="ajax_geocode_adresse")
  190.      */
  191.     public function geocodeAdresse(Request $requestGeocoder $geocoder): Response
  192.     {
  193.         $search $request->query->get('query');
  194.         $coords $geocoder->encode($search);
  195.         if($coords) {
  196.             return $this->json([
  197.                 'latitude' => $coords['lat'],
  198.                 'longitude' => $coords['lng']
  199.             ]);
  200.         } else {
  201.             return $this->json(false);
  202.         }
  203.     }
  204.     /**
  205.      * @Route("/send/impression/{lot}", name="ajax_send_impression")
  206.      */
  207.     public function sendImpression(Lot $lotRequest $requestUtilisateurRepository $utilisateurRepositoryProspectRepository $prospectRepEntityManagerInterface $emNotification $serviceNotificationHistoriqueRepository $historiqueRepository): Response
  208.     {
  209.         if ($request->isXmlHttpRequest()) {
  210.             $token $request->getSession()->get('token');
  211.             $data $request->request->get('form');
  212.             $commentaire $data['commentaire'];
  213.             if(trim($commentaire) == '')
  214.                 return $this->json([
  215.                     'status' => 'fail',
  216.                     'message' => 'Veuillez renseigner le champ de commentaire.'
  217.                 ]);
  218.             $prospect $prospectRep->findOneBy(['token' => $token]);
  219.             $historique $historiqueRepository->findOneBy(['prospect' => $prospect->getId()], ['dateheure' => 'DESC']);
  220.             if($historique == null)
  221.             {
  222.                 $historique = new Historique();
  223.                 $historique->setProspect($prospect);
  224.                 $historique->setModeEnvoi('automatique');
  225.                 $historique->setDateheure(new DateTime());
  226.                 foreach ($prospect->getPropositions() as $p) {
  227.                     $historique->addLot($p->getLot());
  228.                 }
  229.                 $em->persist($historique);
  230.             }
  231.             $impression = new Impression();
  232.             $impression->setHistorique($historique);
  233.             $impression->setProspect($prospect);
  234.             $impression->setCommentaire($commentaire);
  235.             $impression->setDateheure(new \DateTime());
  236.             $em->persist($impression);
  237.             $em->flush();
  238.             $res $serviceNotification->alertInterne($prospect$impression$lot$utilisateurRepository);
  239.             if(!$res)
  240.                 return $this->json([
  241.                     'status' => 'fail',
  242.                     'message' => 'Une erreur s\'est produite lors de l\'envoi de votre message.'
  243.                 ]);
  244.             return $this->json([
  245.                 'status' => 'success',
  246.                 'message' => 'Votre commentaire a bien été envoyé.'
  247.             ]);
  248.         }
  249.         return $this->json([
  250.             'status' => 'fail',
  251.             'message' => 'Une erreur s\'est produite lors de l\'envoi de votre message.'
  252.         ]);
  253.     }
  254.     /**
  255.      * @Route("/new/historique/{prospect}", name="ajax_new_historique")
  256.      */
  257.     public function newHistorique(Prospect $prospect nullRequest $requestEntityManagerInterface $emHistoriqueUtilisateurRepository $historiqueUtilisateurRepository):Response
  258.     {
  259.         $modeEnvoi $request->query->get('modeEnvoi');
  260.         $contenu $request->query->get('msg');
  261.         $contenu str_replace('%0a'PHP_EOL$contenu);
  262.         $expediteur $this->container->get('security.token_storage')->getToken()->getUser();
  263.         $historique = new Historique();
  264.         $historique->setProspect($prospect);
  265.         $historique->setModeEnvoi($modeEnvoi);
  266.         $historique->setDateheure(new DateTime());
  267.         $historique->setContenu($contenu);
  268.         $historique->setEntreprise($prospect->getEntreprise());
  269.         if($expediteur instanceof Utilisateur) {
  270.             $historique->setExpediteur($expediteur);
  271.         }
  272.         foreach ($prospect->getPropositions() as $p) {
  273.             $historique->addLot($p->getLot());
  274.         }
  275.         $em->persist($historique);
  276.         if ($expediteur instanceof Utilisateur && $modeEnvoi === 'whatsapp') {
  277.             $historiqueUtilisateurRepository->log(
  278.                 $expediteur,
  279.                 HistoriqueUtilisateur::ACTION_CLICK_WHATSAPP,
  280.                 'Envoi proposition via WhatsApp à ' $prospect->getEmprunteurPrenom() . ' ' $prospect->getEmprunteurNom(),
  281.                 'prospect',
  282.                 $prospect->getId()
  283.             );
  284.         }
  285.         $em->flush();
  286.         return $this->json([
  287.             'message' => 'Nouvelle proposition dans l\'historique.'
  288.         ]);
  289.     }
  290.     /**
  291.      * @Route("/pj/rotate", name="ajax_piece_jointe_rotate")
  292.      */
  293.     public function pieceJointeRotate(Request $request): Response
  294.     {
  295.         $fileName $request->query->get('query');
  296.         $degrees 90;
  297.         $strArray explode('.'$fileName);
  298.         $ext trim(strtolower(end($strArray)));
  299.         if($ext == 'png')
  300.         {
  301.             ImagePdf::rotateImage90Deg($fileName);
  302.             return $this->json($fileName);
  303.         }
  304.         if ($ext == 'jpg' || $ext == 'jpeg')
  305.         {
  306.             $path explode('/'$fileName);
  307.             $fileName end($path);
  308.             ImagePdf::rotateImage90Deg(PieceJointe::SAVE_PATH $fileName);
  309.             return $this->json(PieceJointe::SAVE_PATH $fileName);
  310.         }
  311.         return $this->json(false);
  312.     }
  313.     /**
  314.      * @Route("/envoi-mail-fiche", name="ajax_envoi_mail_fiche")
  315.      */
  316.     public function envoiMailFiche(Request $requestUtilisateurRepository $utilisateurRepositoryProspectRepository $prospectRepositoryEntityManagerInterface $em): JsonResponse
  317.     {
  318.         $auteurId $request->get('auteurId');
  319.         $prospectId $request->get('prospectId');
  320.         $userConnected $this->getUser();
  321.     
  322.         if ($auteurId || $prospectId) {
  323.             $this->notificationService->alertAuteurProspect($prospectId$auteurId$userConnected);
  324.             
  325.             $infoSuivi = new InfosSuivi();
  326.             $infoSuivi->setUtilisateur($utilisateurRepository->find($auteurId));
  327.             $infoSuivi->addProspect($prospectRepository->find($prospectId));
  328.             $infoSuivi->setType('client');
  329.             $infoSuivi->setDescription('Un nouveau client vous a été attribué : [prospect]');
  330.             $infoSuivi->setDate(new DateTime());
  331.             $infoSuivi->setLu(false);
  332.             $em->persist($infoSuivi);
  333.             $em->flush();
  334.             return $this->json([
  335.                 'status' => 'success',
  336.                 'message' => 'Email envoyé avec succès'
  337.             ]);
  338.         }
  339.     
  340.         return $this->json([
  341.             'status' => 'error',
  342.             'message' => 'Auteur non trouvé ou erreur d\'envoi'
  343.         ]);
  344.     }
  345.     /**
  346.      * @Route("/envoi-mail-atraiter", name="ajax_envoi_mail_atraiter")
  347.      */
  348.     public function envoiMailATraiter(Request $requestProspectRepository $prospectRepository): JsonResponse
  349.     {
  350.         $prospectId $request->get('prospectId');
  351.         if (!$prospectId) {
  352.             return $this->json([
  353.                 'status' => 'error',
  354.                 'message' => 'Prospect non trouvé'
  355.             ]);
  356.         }
  357.         $prospect $prospectRepository->find($prospectId);
  358.         if (!$prospect) {
  359.             return $this->json([
  360.                 'status' => 'error',
  361.                 'message' => 'Prospect non trouvé'
  362.             ]);
  363.         }
  364.         if ($prospect->getStatut() !== 'atraiter') {
  365.             return $this->json([
  366.                 'status' => 'error',
  367.                 'message' => 'Le prospect doit être en statut "A TRAITER" pour envoyer cet email'
  368.             ]);
  369.         }
  370.         $negociateur $prospect->getAuteur();
  371.         if (!$negociateur) {
  372.             return $this->json([
  373.                 'status' => 'error',
  374.                 'message' => 'Aucun négociateur assigné à ce prospect'
  375.             ]);
  376.         }
  377.         // Envoi de l'email au CLIENT
  378.         $result $this->notificationService->alertProspectATraiter($prospect$negociateur);
  379.         if ($result) {
  380.             return $this->json([
  381.                 'status' => 'success',
  382.                 'message' => 'Email envoyé au client avec succès'
  383.             ]);
  384.         }
  385.         return $this->json([
  386.             'status' => 'error',
  387.             'message' => 'Erreur lors de l\'envoi de l\'email'
  388.         ]);
  389.     }
  390.     /**
  391.      * @Route("/info-suivi/{id}/lu", name="ajax_info_suivi_lu", methods={"POST"})
  392.      */
  393.     public function setInfoSuiviLu(Request $requestInfosSuivi $infoSuivi): Response
  394.     {
  395.         $infoSuivi->setLu(true);
  396.         $entityManager $this->getDoctrine()->getManager();
  397.         $entityManager->flush();
  398.         return new Response();
  399.     }
  400.     /**
  401.      * @Route("/prospect/check-duplicate", name="ajax_prospect_check_duplicate")
  402.      */
  403.     public function checkProspectDuplicate(Request $requestProspectRepository $prospectRepository): JsonResponse
  404.     {
  405.         $nom trim($request->query->get('nom'''));
  406.         $prenom trim($request->query->get('prenom'''));
  407.         // Nouveaux champs co-emprunteur
  408.         $coNom trim($request->query->get('coNom'''));
  409.         $coPrenom trim($request->query->get('coPrenom'''));
  410.         $currentProspectId $request->query->get('currentId'null);
  411.         // Si absolument rien n'est renseigné → pas de check
  412.         if (
  413.             (empty($nom) || empty($prenom)) &&
  414.             (empty($coNom) || empty($coPrenom))
  415.         ) {
  416.             return $this->json([
  417.                 'exists' => false,
  418.                 'message' => 'Champs insuffisants pour une vérification'
  419.             ]);
  420.         }
  421.         $user $this->getUser();
  422.         if (!$user instanceof Utilisateur || !$user->getEntreprise()) {
  423.             return $this->json([
  424.                 'exists' => false,
  425.                 'error' => 'Utilisateur non authentifié ou entreprise non définie'
  426.             ]);
  427.         }
  428.         try {
  429.             // Charger le prospect actuel (pour exclusion)
  430.             $currentNom null;
  431.             $currentPrenom null;
  432.             $currentCoNom null;
  433.             $currentCoPrenom null;
  434.             if ($currentProspectId) {
  435.                 $currentProspect $prospectRepository->find($currentProspectId);
  436.                 if ($currentProspect) {
  437.                     $currentNom trim($currentProspect->getEmprunteurNom());
  438.                     $currentPrenom trim($currentProspect->getEmprunteurPrenom());
  439.                     $currentCoNom trim($currentProspect->getCoEmprunteurNom());
  440.                     $currentCoPrenom trim($currentProspect->getCoEmprunteurPrenom());
  441.                 }
  442.             }
  443.             // Vérifications séparées
  444.             $errors = [];
  445.             // Doublon emprunteur
  446.             if (!empty($nom) && !empty($prenom)) {
  447.                 $existingEmprunteur $prospectRepository->findExistingProspectByNomPrenom(
  448.                     $nom,
  449.                     $prenom,
  450.                     $user->getEntreprise(),
  451.                     $currentProspectId,
  452.                     $currentNom,
  453.                     $currentPrenom
  454.                 );
  455.                 if ($existingEmprunteur) {
  456.                     $errors['emprunteur'] = [
  457.                         'id' => $existingEmprunteur->getId(),
  458.                         'nom' => $existingEmprunteur->getEmprunteurNom(),
  459.                         'prenom' => $existingEmprunteur->getEmprunteurPrenom(),
  460.                         'email' => $existingEmprunteur->getEmprunteurEmail(),
  461.                         'dateCreation' => $existingEmprunteur->getDateheure() ? $existingEmprunteur->getDateheure()->format('d/m/Y H:i') : null,
  462.                         'auteur' => $existingEmprunteur->getAuteur() ? $existingEmprunteur->getAuteur()->getNomEtPrenom() : null,
  463.                     ];
  464.                 }
  465.             }
  466.             // Doublon co-emprunteur
  467.             if (!empty($coNom) && !empty($coPrenom)) {
  468.                 $existingCo $prospectRepository->findExistingProspectByCoNomPrenom(
  469.                     $coNom,
  470.                     $coPrenom,
  471.                     $user->getEntreprise(),
  472.                     $currentProspectId,
  473.                     $currentCoNom,
  474.                     $currentCoPrenom
  475.                 );
  476.                 if ($existingCo) {
  477.                     $errors['co'] = [
  478.                         'id' => $existingCo->getId(),
  479.                         'nom' => $existingCo->getCoEmprunteurNom(),
  480.                         'prenom' => $existingCo->getCoEmprunteurPrenom(),
  481.                         'email' => $existingCo->getCoEmprunteurEmail(),
  482.                         'dateCreation' => $existingCo->getDateheure() ? $existingCo->getDateheure()->format('d/m/Y H:i') : null,
  483.                         'auteur' => $existingCo->getAuteur() ? $existingCo->getAuteur()->getNomEtPrenom() : null,
  484.                     ];
  485.                 }
  486.             }
  487.             // Si aucun problème détecté
  488.             if (empty($errors)) {
  489.                 return $this->json([
  490.                     'exists' => false,
  491.                     'message' => 'Aucun doublon détecté'
  492.                 ]);
  493.             }
  494.             // Sinon renvoyer les deux erreurs potentielles
  495.             return $this->json([
  496.                 'exists' => true,
  497.                 'blocked' => true,
  498.                 'errors' => $errors
  499.             ]);
  500.         } catch (\Exception $e) {
  501.             return $this->json([
  502.                 'exists' => false,
  503.                 'error' => 'Erreur lors de la vérification des doublons',
  504.                 'exception' => $e
  505.             ], 500);
  506.         }
  507.     }
  508.     /**
  509.      * @Route("/programme/check-duplicate", name="ajax_programme_check_duplicate")
  510.      */
  511.     public function checkProgrammeDuplicate(Request $requestProgrammeRepository $programmeRepository): JsonResponse
  512.     {
  513.         $nom        trim((string) $request->query->get('nom'''));
  514.         $promoteur  trim((string) $request->query->get('promoteur'''));
  515.         $currentId  $request->query->get('currentId');
  516.         if ($nom === '' || \strlen($nom) < 2) {
  517.             return $this->json([
  518.                 'exists'  => false,
  519.                 'message' => 'Nom requis (≥ 2 caractères)',
  520.             ]);
  521.         }
  522.         $user $this->getUser();
  523.         if (!$user instanceof Utilisateur || !$user->getEntreprise()) {
  524.             return $this->json([
  525.                 'exists' => false,
  526.                 'error'  => 'Utilisateur non authentifié ou entreprise non définie',
  527.             ]);
  528.         }
  529.         try {
  530.             $existing $programmeRepository->findExistingProgrammeByNomPromoteur(
  531.                 $nom,
  532.                 $promoteur !== '' $promoteur null,
  533.                 $user->getEntreprise(),
  534.                 $currentId ? (int) $currentId null
  535.             );
  536.             if ($existing) {
  537.                 return $this->json([
  538.                     'exists'   => true,
  539.                     'blocked'  => true,
  540.                     'message'  => 'Un programme avec ce nom (et promoteur si renseigné) existe déjà',
  541.                     'programme' => [
  542.                         'id'        => $existing->getId(),
  543.                         'nom'       => $existing->getNom(),
  544.                         'promoteur' => $existing->getPromoteur(),
  545.                         'ville'     => $existing->getVille(),
  546.                     ],
  547.                 ]);
  548.             }
  549.             return $this->json([
  550.                 'exists'  => false,
  551.                 'message' => 'Aucun doublon détecté',
  552.             ]);
  553.         } catch (\Throwable $e) {
  554.             return $this->json([
  555.                 'exists' => false,
  556.                 'error'  => 'Erreur lors de la vérification des doublons',
  557.             ], 500);
  558.         }
  559.     }
  560.     /**
  561.      * @Route("/utilisateur/{id}/historique/list", name="ajax_user_histo_list", methods={"GET"})
  562.      */
  563.     public function historiqueList(
  564.         Utilisateur $id,
  565.         Request $request,
  566.         HistoriqueUtilisateurRepository $repo,
  567.         \App\Repository\ProspectRepository $prospectRepository
  568.     ): JsonResponse {
  569.         $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
  570.         $action  $request->query->get('action') ?: null;
  571.         $fromStr $request->query->get('from') ?: null;
  572.         $toStr   $request->query->get('to') ?: null;
  573.         $page    max(1, (int) $request->query->get('page'1));
  574.         $perPage max(1min(200, (int) $request->query->get('perPage'25)));
  575.         $offset  = ($page 1) * $perPage;
  576.         $from $fromStr ? new \DateTimeImmutable($fromStr ' 00:00:00') : null;
  577.         $to   $toStr   ? new \DateTimeImmutable($toStr   ' 23:59:59') : null;
  578.         $total $repo->countByFilters($id$action$from$to);
  579.         $rows  $repo->findByFilters($id$action$from$to$perPage$offset);
  580.         $prospectIds = [];
  581.         foreach ($rows as $h) {
  582.             if ($h->getCibleType() === 'prospect' && $h->getCibleId()) {
  583.                 $prospectIds[] = (int) $h->getCibleId();
  584.             }
  585.         }
  586.         $prospectIds array_values(array_unique($prospectIds));
  587.         $prospectsById = [];
  588.         if ($prospectIds) {
  589.             $prospects $prospectRepository->findBy(['id' => $prospectIds]);
  590.             foreach ($prospects as $p) {
  591.                 $prospectsById[$p->getId()] = $p;
  592.             }
  593.         }
  594.         $items array_map(function (HistoriqueUtilisateur $h) use ($prospectsById) {
  595.             $contact null;
  596.             if ($h->getCibleType() === 'prospect' && $h->getCibleId()) {
  597.                 $pid = (int) $h->getCibleId();
  598.                 /** @var \App\Entity\Prospect|null $p */
  599.                 $p $prospectsById[$pid] ?? null;
  600.                 if ($p) {
  601.                     $prenom trim((string) ($p->getEmprunteurPrenom() ?? ''));
  602.                     $nom    trim((string) ($p->getEmprunteurNom() ?? ''));
  603.                     $full trim($prenom ' ' $nom);
  604.                     $contact $full !== '' $full : ($p->getEmprunteurEmail() ?: ('Prospect #' $pid));
  605.                 } else {
  606.                     $contact 'Prospect #' $pid;
  607.                 }
  608.             }
  609.             return [
  610.                 'id'           => $h->getId(),
  611.                 'createdAt'    => $h->getCreatedAt()->format('d/m/Y H:i'),
  612.                 'action'       => $h->getAction(),
  613.                 'action_label' => HistoriqueUtilisateur::labelFor($h->getAction()),
  614.                 'details'      => $h->getDetails(),
  615.                 'cibleType'    => $h->getCibleType(),
  616.                 'cibleId'      => $h->getCibleId(),
  617.                 'contact'      => $contact,
  618.             ];
  619.         }, $rows);
  620.         $pages   = (int) ceil($total max(1$perPage));
  621.         $hasMore $page $pages;
  622.         return $this->json([
  623.             'items'   => $items,
  624.             'total'   => $total,
  625.             'page'    => $page,
  626.             'perPage' => $perPage,
  627.             'pages'   => $pages,
  628.             'hasMore' => $hasMore,
  629.         ]);
  630.     }
  631.     /**
  632.      * @Route("/utilisateur/{id}/historique/purge", name="ajax_user_historique_purge", methods={"POST"})
  633.      */
  634.     public function purgeHistorique(
  635.         Utilisateur $user,
  636.         HistoriqueUtilisateurRepository $repo,
  637.         EntityManagerInterface $em
  638.     ): JsonResponse {
  639.         if (
  640.             !$this->isGranted('ROLE_DIRECTEUR')
  641.             && !$this->isGranted('ROLE_ADMIN')
  642.             && !$this->isGranted('ROLE_SUPERADMIN')
  643.         ) {
  644.             throw $this->createAccessDeniedException();
  645.         }
  646.         $count $repo->purgeByUser($user);
  647.         $em->flush();
  648.         return $this->json(['purged' => (int) $count]);
  649.     }
  650.     /**
  651.      * @Route("/historique/click-a-traiter", name="ajax_historique_click_a_traiter", methods={"POST"})
  652.      */
  653.     public function clickATraiter(
  654.         Request $request,
  655.         \App\Repository\HistoriqueUtilisateurRepository $repo,
  656.         \Doctrine\ORM\EntityManagerInterface $em,
  657.         \App\Repository\ProspectRepository $prospectRepository
  658.     ): \Symfony\Component\HttpFoundation\JsonResponse {
  659.         try {
  660.             $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
  661.             /** @var \App\Entity\Utilisateur $user */
  662.             $user $this->getUser();
  663.             $prospectId = (int) $request->request->get('prospectId'0);
  664.             if ($prospectId <= 0) {
  665.                 return $this->json(['ok' => false'error' => 'prospectId manquant'], 400);
  666.             }
  667.             $prospect $prospectRepository->find($prospectId);
  668.             $email $prospect $prospect->getEmprunteurEmail() : null;
  669.             // MAJ dateCliqueATraiter uniquement si le prospect existe et est en statut "atraiter"
  670.             if ($prospect && $prospect->getStatut() === 'atraiter') {
  671.                 $prospect->setDateCliqueATraiter(new \DateTimeImmutable());
  672.             }
  673.             $details 'Clic liste A TRAITER';
  674.             $repo->log(
  675.                 $user,
  676.                 \App\Entity\HistoriqueUtilisateur::ACTION_CLICK_A_TRAITER,
  677.                 $details,
  678.                 'prospect',
  679.                 $prospectId
  680.             );
  681.             $em->flush();
  682.             return $this->json(['ok' => true]);
  683.         } catch (\Throwable $e) {
  684.             return $this->json([
  685.                 'ok'    => false,
  686.                 'error' => $e->getMessage(),
  687.             ], 500);
  688.         }
  689.     }
  690. }