src/Controller/AjaxController.php line 123

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 $em):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.         $em->flush();
  277.         return $this->json([
  278.             'message' => 'Nouvelle proposition dans l\'historique.'
  279.         ]);
  280.     }
  281.     /**
  282.      * @Route("/pj/rotate", name="ajax_piece_jointe_rotate")
  283.      */
  284.     public function pieceJointeRotate(Request $request): Response
  285.     {
  286.         $fileName $request->query->get('query');
  287.         $degrees 90;
  288.         $strArray explode('.'$fileName);
  289.         $ext trim(strtolower(end($strArray)));
  290.         if($ext == 'png')
  291.         {
  292.             ImagePdf::rotateImage90Deg($fileName);
  293.             return $this->json($fileName);
  294.         }
  295.         if ($ext == 'jpg' || $ext == 'jpeg')
  296.         {
  297.             $path explode('/'$fileName);
  298.             $fileName end($path);
  299.             ImagePdf::rotateImage90Deg(PieceJointe::SAVE_PATH $fileName);
  300.             return $this->json(PieceJointe::SAVE_PATH $fileName);
  301.         }
  302.         return $this->json(false);
  303.     }
  304.     /**
  305.      * @Route("/envoi-mail-fiche", name="ajax_envoi_mail_fiche")
  306.      */
  307.     public function envoiMailFiche(Request $requestUtilisateurRepository $utilisateurRepositoryProspectRepository $prospectRepositoryEntityManagerInterface $em): JsonResponse
  308.     {
  309.         $auteurId $request->get('auteurId');
  310.         $prospectId $request->get('prospectId');
  311.         $userConnected $this->getUser();
  312.     
  313.         if ($auteurId || $prospectId) {
  314.             $this->notificationService->alertAuteurProspect($prospectId$auteurId$userConnected);
  315.             
  316.             $infoSuivi = new InfosSuivi();
  317.             $infoSuivi->setUtilisateur($utilisateurRepository->find($auteurId));
  318.             $infoSuivi->addProspect($prospectRepository->find($prospectId));
  319.             $infoSuivi->setType('client');
  320.             $infoSuivi->setDescription('Un nouveau client vous a été attribué : [prospect]');
  321.             $infoSuivi->setDate(new DateTime());
  322.             $infoSuivi->setLu(false);
  323.             $em->persist($infoSuivi);
  324.             $em->flush();
  325.             return $this->json([
  326.                 'status' => 'success',
  327.                 'message' => 'Email envoyé avec succès'
  328.             ]);
  329.         }
  330.     
  331.         return $this->json([
  332.             'status' => 'error',
  333.             'message' => 'Auteur non trouvé ou erreur d\'envoi'
  334.         ]);
  335.     }
  336.     /**
  337.      * @Route("/envoi-mail-atraiter", name="ajax_envoi_mail_atraiter")
  338.      */
  339.     public function envoiMailATraiter(Request $requestProspectRepository $prospectRepository): JsonResponse
  340.     {
  341.         $prospectId $request->get('prospectId');
  342.         if (!$prospectId) {
  343.             return $this->json([
  344.                 'status' => 'error',
  345.                 'message' => 'Prospect non trouvé'
  346.             ]);
  347.         }
  348.         $prospect $prospectRepository->find($prospectId);
  349.         if (!$prospect) {
  350.             return $this->json([
  351.                 'status' => 'error',
  352.                 'message' => 'Prospect non trouvé'
  353.             ]);
  354.         }
  355.         if ($prospect->getStatut() !== 'atraiter') {
  356.             return $this->json([
  357.                 'status' => 'error',
  358.                 'message' => 'Le prospect doit être en statut "A TRAITER" pour envoyer cet email'
  359.             ]);
  360.         }
  361.         $negociateur $prospect->getAuteur();
  362.         if (!$negociateur) {
  363.             return $this->json([
  364.                 'status' => 'error',
  365.                 'message' => 'Aucun négociateur assigné à ce prospect'
  366.             ]);
  367.         }
  368.         // Envoi de l'email au CLIENT
  369.         $result $this->notificationService->alertProspectATraiter($prospect$negociateur);
  370.         if ($result) {
  371.             return $this->json([
  372.                 'status' => 'success',
  373.                 'message' => 'Email envoyé au client avec succès'
  374.             ]);
  375.         }
  376.         return $this->json([
  377.             'status' => 'error',
  378.             'message' => 'Erreur lors de l\'envoi de l\'email'
  379.         ]);
  380.     }
  381.     /**
  382.      * @Route("/info-suivi/{id}/lu", name="ajax_info_suivi_lu", methods={"POST"})
  383.      */
  384.     public function setInfoSuiviLu(Request $requestInfosSuivi $infoSuivi): Response
  385.     {
  386.         $infoSuivi->setLu(true);
  387.         $entityManager $this->getDoctrine()->getManager();
  388.         $entityManager->flush();
  389.         return new Response();
  390.     }
  391.     /**
  392.      * @Route("/prospect/check-duplicate", name="ajax_prospect_check_duplicate")
  393.      */
  394.     public function checkProspectDuplicate(Request $requestProspectRepository $prospectRepository): JsonResponse
  395.     {
  396.         $nom trim($request->query->get('nom'''));
  397.         $prenom trim($request->query->get('prenom'''));
  398.         // Nouveaux champs co-emprunteur
  399.         $coNom trim($request->query->get('coNom'''));
  400.         $coPrenom trim($request->query->get('coPrenom'''));
  401.         $currentProspectId $request->query->get('currentId'null);
  402.         // Si absolument rien n'est renseigné → pas de check
  403.         if (
  404.             (empty($nom) || empty($prenom)) &&
  405.             (empty($coNom) || empty($coPrenom))
  406.         ) {
  407.             return $this->json([
  408.                 'exists' => false,
  409.                 'message' => 'Champs insuffisants pour une vérification'
  410.             ]);
  411.         }
  412.         $user $this->getUser();
  413.         if (!$user instanceof Utilisateur || !$user->getEntreprise()) {
  414.             return $this->json([
  415.                 'exists' => false,
  416.                 'error' => 'Utilisateur non authentifié ou entreprise non définie'
  417.             ]);
  418.         }
  419.         try {
  420.             // Charger le prospect actuel (pour exclusion)
  421.             $currentNom null;
  422.             $currentPrenom null;
  423.             $currentCoNom null;
  424.             $currentCoPrenom null;
  425.             if ($currentProspectId) {
  426.                 $currentProspect $prospectRepository->find($currentProspectId);
  427.                 if ($currentProspect) {
  428.                     $currentNom trim($currentProspect->getEmprunteurNom());
  429.                     $currentPrenom trim($currentProspect->getEmprunteurPrenom());
  430.                     $currentCoNom trim($currentProspect->getCoEmprunteurNom());
  431.                     $currentCoPrenom trim($currentProspect->getCoEmprunteurPrenom());
  432.                 }
  433.             }
  434.             // Vérifications séparées
  435.             $errors = [];
  436.             // Doublon emprunteur
  437.             if (!empty($nom) && !empty($prenom)) {
  438.                 $existingEmprunteur $prospectRepository->findExistingProspectByNomPrenom(
  439.                     $nom,
  440.                     $prenom,
  441.                     $user->getEntreprise(),
  442.                     $currentProspectId,
  443.                     $currentNom,
  444.                     $currentPrenom
  445.                 );
  446.                 if ($existingEmprunteur) {
  447.                     $errors['emprunteur'] = [
  448.                         'id' => $existingEmprunteur->getId(),
  449.                         'nom' => $existingEmprunteur->getEmprunteurNom(),
  450.                         'prenom' => $existingEmprunteur->getEmprunteurPrenom(),
  451.                         'email' => $existingEmprunteur->getEmprunteurEmail(),
  452.                         'dateCreation' => $existingEmprunteur->getDateheure() ? $existingEmprunteur->getDateheure()->format('d/m/Y H:i') : null,
  453.                         'auteur' => $existingEmprunteur->getAuteur() ? $existingEmprunteur->getAuteur()->getNomEtPrenom() : null,
  454.                     ];
  455.                 }
  456.             }
  457.             // Doublon co-emprunteur
  458.             if (!empty($coNom) && !empty($coPrenom)) {
  459.                 $existingCo $prospectRepository->findExistingProspectByCoNomPrenom(
  460.                     $coNom,
  461.                     $coPrenom,
  462.                     $user->getEntreprise(),
  463.                     $currentProspectId,
  464.                     $currentCoNom,
  465.                     $currentCoPrenom
  466.                 );
  467.                 if ($existingCo) {
  468.                     $errors['co'] = [
  469.                         'id' => $existingCo->getId(),
  470.                         'nom' => $existingCo->getCoEmprunteurNom(),
  471.                         'prenom' => $existingCo->getCoEmprunteurPrenom(),
  472.                         'email' => $existingCo->getCoEmprunteurEmail(),
  473.                         'dateCreation' => $existingCo->getDateheure() ? $existingCo->getDateheure()->format('d/m/Y H:i') : null,
  474.                         'auteur' => $existingCo->getAuteur() ? $existingCo->getAuteur()->getNomEtPrenom() : null,
  475.                     ];
  476.                 }
  477.             }
  478.             // Si aucun problème détecté
  479.             if (empty($errors)) {
  480.                 return $this->json([
  481.                     'exists' => false,
  482.                     'message' => 'Aucun doublon détecté'
  483.                 ]);
  484.             }
  485.             // Sinon renvoyer les deux erreurs potentielles
  486.             return $this->json([
  487.                 'exists' => true,
  488.                 'blocked' => true,
  489.                 'errors' => $errors
  490.             ]);
  491.         } catch (\Exception $e) {
  492.             return $this->json([
  493.                 'exists' => false,
  494.                 'error' => 'Erreur lors de la vérification des doublons',
  495.                 'exception' => $e
  496.             ], 500);
  497.         }
  498.     }
  499.     /**
  500.      * @Route("/programme/check-duplicate", name="ajax_programme_check_duplicate")
  501.      */
  502.     public function checkProgrammeDuplicate(Request $requestProgrammeRepository $programmeRepository): JsonResponse
  503.     {
  504.         $nom        trim((string) $request->query->get('nom'''));
  505.         $promoteur  trim((string) $request->query->get('promoteur'''));
  506.         $currentId  $request->query->get('currentId');
  507.         if ($nom === '' || \strlen($nom) < 2) {
  508.             return $this->json([
  509.                 'exists'  => false,
  510.                 'message' => 'Nom requis (≥ 2 caractères)',
  511.             ]);
  512.         }
  513.         $user $this->getUser();
  514.         if (!$user instanceof Utilisateur || !$user->getEntreprise()) {
  515.             return $this->json([
  516.                 'exists' => false,
  517.                 'error'  => 'Utilisateur non authentifié ou entreprise non définie',
  518.             ]);
  519.         }
  520.         try {
  521.             $existing $programmeRepository->findExistingProgrammeByNomPromoteur(
  522.                 $nom,
  523.                 $promoteur !== '' $promoteur null,
  524.                 $user->getEntreprise(),
  525.                 $currentId ? (int) $currentId null
  526.             );
  527.             if ($existing) {
  528.                 return $this->json([
  529.                     'exists'   => true,
  530.                     'blocked'  => true,
  531.                     'message'  => 'Un programme avec ce nom (et promoteur si renseigné) existe déjà',
  532.                     'programme' => [
  533.                         'id'        => $existing->getId(),
  534.                         'nom'       => $existing->getNom(),
  535.                         'promoteur' => $existing->getPromoteur(),
  536.                         'ville'     => $existing->getVille(),
  537.                     ],
  538.                 ]);
  539.             }
  540.             return $this->json([
  541.                 'exists'  => false,
  542.                 'message' => 'Aucun doublon détecté',
  543.             ]);
  544.         } catch (\Throwable $e) {
  545.             return $this->json([
  546.                 'exists' => false,
  547.                 'error'  => 'Erreur lors de la vérification des doublons',
  548.             ], 500);
  549.         }
  550.     }
  551.     /**
  552.      * @Route("/utilisateur/{id}/historique/list", name="ajax_user_histo_list", methods={"GET"})
  553.      */
  554.     public function historiqueList(
  555.         Utilisateur $id,
  556.         Request $request,
  557.         HistoriqueUtilisateurRepository $repo,
  558.         \App\Repository\ProspectRepository $prospectRepository
  559.     ): JsonResponse {
  560.         $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
  561.         $action  $request->query->get('action') ?: null;
  562.         $fromStr $request->query->get('from') ?: null;
  563.         $toStr   $request->query->get('to') ?: null;
  564.         $page    max(1, (int) $request->query->get('page'1));
  565.         $perPage max(1min(200, (int) $request->query->get('perPage'25)));
  566.         $offset  = ($page 1) * $perPage;
  567.         $from $fromStr ? new \DateTimeImmutable($fromStr ' 00:00:00') : null;
  568.         $to   $toStr   ? new \DateTimeImmutable($toStr   ' 23:59:59') : null;
  569.         $total $repo->countByFilters($id$action$from$to);
  570.         $rows  $repo->findByFilters($id$action$from$to$perPage$offset);
  571.         $prospectIds = [];
  572.         foreach ($rows as $h) {
  573.             if ($h->getCibleType() === 'prospect' && $h->getCibleId()) {
  574.                 $prospectIds[] = (int) $h->getCibleId();
  575.             }
  576.         }
  577.         $prospectIds array_values(array_unique($prospectIds));
  578.         $prospectsById = [];
  579.         if ($prospectIds) {
  580.             $prospects $prospectRepository->findBy(['id' => $prospectIds]);
  581.             foreach ($prospects as $p) {
  582.                 $prospectsById[$p->getId()] = $p;
  583.             }
  584.         }
  585.         $items array_map(function (HistoriqueUtilisateur $h) use ($prospectsById) {
  586.             $contact null;
  587.             if ($h->getCibleType() === 'prospect' && $h->getCibleId()) {
  588.                 $pid = (int) $h->getCibleId();
  589.                 /** @var \App\Entity\Prospect|null $p */
  590.                 $p $prospectsById[$pid] ?? null;
  591.                 if ($p) {
  592.                     $prenom trim((string) ($p->getEmprunteurPrenom() ?? ''));
  593.                     $nom    trim((string) ($p->getEmprunteurNom() ?? ''));
  594.                     $full trim($prenom ' ' $nom);
  595.                     $contact $full !== '' $full : ($p->getEmprunteurEmail() ?: ('Prospect #' $pid));
  596.                 } else {
  597.                     $contact 'Prospect #' $pid;
  598.                 }
  599.             }
  600.             return [
  601.                 'id'           => $h->getId(),
  602.                 'createdAt'    => $h->getCreatedAt()->format('d/m/Y H:i'),
  603.                 'action'       => $h->getAction(),
  604.                 'action_label' => HistoriqueUtilisateur::labelFor($h->getAction()),
  605.                 'details'      => $h->getDetails(),
  606.                 'cibleType'    => $h->getCibleType(),
  607.                 'cibleId'      => $h->getCibleId(),
  608.                 'contact'      => $contact,
  609.             ];
  610.         }, $rows);
  611.         $pages   = (int) ceil($total max(1$perPage));
  612.         $hasMore $page $pages;
  613.         return $this->json([
  614.             'items'   => $items,
  615.             'total'   => $total,
  616.             'page'    => $page,
  617.             'perPage' => $perPage,
  618.             'pages'   => $pages,
  619.             'hasMore' => $hasMore,
  620.         ]);
  621.     }
  622.     /**
  623.      * @Route("/utilisateur/{id}/historique/purge", name="ajax_user_historique_purge", methods={"POST"})
  624.      */
  625.     public function purgeHistorique(
  626.         Utilisateur $user,
  627.         HistoriqueUtilisateurRepository $repo,
  628.         EntityManagerInterface $em
  629.     ): JsonResponse {
  630.         if (
  631.             !$this->isGranted('ROLE_DIRECTEUR')
  632.             && !$this->isGranted('ROLE_ADMIN')
  633.             && !$this->isGranted('ROLE_SUPERADMIN')
  634.         ) {
  635.             throw $this->createAccessDeniedException();
  636.         }
  637.         $count $repo->purgeByUser($user);
  638.         $em->flush();
  639.         return $this->json(['purged' => (int) $count]);
  640.     }
  641.     /**
  642.      * @Route("/historique/click-a-traiter", name="ajax_historique_click_a_traiter", methods={"POST"})
  643.      */
  644.     public function clickATraiter(
  645.         Request $request,
  646.         \App\Repository\HistoriqueUtilisateurRepository $repo,
  647.         \Doctrine\ORM\EntityManagerInterface $em,
  648.         \App\Repository\ProspectRepository $prospectRepository
  649.     ): \Symfony\Component\HttpFoundation\JsonResponse {
  650.         try {
  651.             $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
  652.             /** @var \App\Entity\Utilisateur $user */
  653.             $user $this->getUser();
  654.             $prospectId = (int) $request->request->get('prospectId'0);
  655.             if ($prospectId <= 0) {
  656.                 return $this->json(['ok' => false'error' => 'prospectId manquant'], 400);
  657.             }
  658.             $prospect $prospectRepository->find($prospectId);
  659.             $email $prospect $prospect->getEmprunteurEmail() : null;
  660.             // MAJ dateCliqueATraiter uniquement si le prospect existe et est en statut "atraiter"
  661.             if ($prospect && $prospect->getStatut() === 'atraiter') {
  662.                 $prospect->setDateCliqueATraiter(new \DateTimeImmutable());
  663.             }
  664.             $details 'Clic liste A TRAITER';
  665.             $repo->log(
  666.                 $user,
  667.                 \App\Entity\HistoriqueUtilisateur::ACTION_CLICK_A_TRAITER,
  668.                 $details,
  669.                 'prospect',
  670.                 $prospectId
  671.             );
  672.             $em->flush();
  673.             return $this->json(['ok' => true]);
  674.         } catch (\Throwable $e) {
  675.             return $this->json([
  676.                 'ok'    => false,
  677.                 'error' => $e->getMessage(),
  678.             ], 500);
  679.         }
  680.     }
  681. }