src/Controller/FactureServiceController.php line 148

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use Dompdf\Dompdf;
  4. use Dompdf\Options;
  5. use App\Entity\Client;
  6. use App\Entity\Recette;
  7. use Psr\Log\LoggerInterface;
  8. use App\Entity\FactureService;
  9. use App\Form\FactureServiceType;
  10. use App\Entity\ServiceFactureLigne;
  11. use App\Repository\ClientRepository;
  12. use Doctrine\ORM\EntityManagerInterface;
  13. use App\Repository\FactureServiceRepository;
  14. use Symfony\Component\HttpFoundation\Request;
  15. use Symfony\Component\Security\Core\Security;
  16. use Symfony\Component\HttpFoundation\Response;
  17. use Symfony\Component\Routing\Annotation\Route;
  18. use Symfony\Component\HttpFoundation\JsonResponse;
  19. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  20. #[Route('/facture/service')]
  21. class FactureServiceController extends AbstractController
  22. {
  23.     public function __construct(private readonly LoggerInterface $logger) {}
  24.     #[Route('/index'name'app_facture_service_index'methods: ['GET'])]
  25.     public function index(
  26.         FactureServiceRepository $factureServiceRepository,
  27.         Security $security
  28.     ): Response {
  29.         $user $security->getUser();
  30.         // Vérifier si l'utilisateur est connecté
  31.         if (!$user) {
  32.             throw $this->createAccessDeniedException('Vous devez être connecté pour accéder à cette page.');
  33.         }
  34.         // Récupérer les factures selon le rôle de l'utilisateur
  35.         if ($security->isGranted('ROLE_SUPER_ADMIN')) {
  36.             // Si Super Admin, récupérer toutes les factures
  37.             $factureServices $factureServiceRepository->findBy([], ['id' => 'DESC']);
  38.         } else {
  39.             // Pour les autres utilisateurs, récupérer uniquement les factures de leur boutique
  40.             $boutique $user->getBoutique();
  41.             if (!$boutique) {
  42.                 // Si l'utilisateur n'a pas de boutique associée
  43.                 $factureServices = [];
  44.             } else {
  45.                 // Récupérer les factures de la boutique de l'utilisateur
  46.                 $factureServices $factureServiceRepository->findBy(['boutique' => $boutique], ['id' => 'DESC']);
  47.             }
  48.         }
  49.         return $this->render('facture_service/index.html.twig', [
  50.             'facture_services' => $factureServices,
  51.         ]);
  52.     }
  53.     #[Route('/new'name'app_facture_service_new'methods: ['GET''POST'])]
  54.     public function new(
  55.         Request $request,
  56.         EntityManagerInterface $entityManager,
  57.         ClientRepository $clientRepository,
  58.         Security $security
  59.     ): Response {
  60.         $factureService = new FactureService();
  61.         $clients $clientRepository->findAll();
  62.         $form $this->createForm(FactureServiceType::class, $factureService);
  63.         $form->handleRequest($request);
  64.         // Récupérer l'utilisateur connecté
  65.         $user $security->getUser();
  66.         // Récupérer la boutique associée à l'utilisateur
  67.         $boutique null;
  68.         if ($user) {
  69.             // Supposons que l'utilisateur a une méthode getBoutique() 
  70.             // Ou adaptez selon votre structure d'entités
  71.             $boutique $user->getBoutique();
  72.             // Associer la boutique à la facture de service
  73.             $factureService->setBoutique($boutique);
  74.         }
  75.         if ($form->isSubmitted() && $form->isValid()) {
  76.             $clientId $request->request->get('client_id');
  77.             if ($clientId) {
  78.                 $client $entityManager->getRepository(Client::class)->find($clientId);
  79.                 if ($client) {
  80.                     $factureService->setClient($client);
  81.                 }
  82.             }
  83.             // Récupérez directement les services depuis le formulaire
  84.             $services $form->get('services')->get('services')->getData();
  85.             $montantTotal 0;
  86.             foreach ($services as $service) {
  87.                 $ligne = new ServiceFactureLigne();
  88.                 $ligne->setService($service);
  89.                 $ligne->setPrix($service->getPrix());
  90.                 $ligne->setServiceFacture($factureService);
  91.                 $factureService->addServiceFactureLigne($ligne);
  92.                 $montantTotal += $service->getPrix();
  93.             }
  94.             $factureService->setMontantTotal($montantTotal);
  95.             $factureService->setNumero($this->generateFactureNumber());
  96.             $factureService->setStatus('Payé');
  97.             $factureService->setDateCreation(new \DateTime());
  98.             // La boutique est déjà définie plus haut
  99.             $entityManager->persist($factureService);
  100.             // Créer la recette seulement si le statut est "Payé"
  101.             if ($factureService->getStatus() === 'Payé') {
  102.                 $this->createRecetteForFactureService($factureService$entityManager);
  103.             }
  104.             $entityManager->flush();
  105.             return $this->redirectToRoute('app_facture_service_show', ['id' => $factureService->getId()], Response::HTTP_SEE_OTHER);
  106.         }
  107.         return $this->renderForm('facture_service/new.html.twig', [
  108.             'facture_service' => $factureService,
  109.             'form' => $form,
  110.             'clients' => $clients,
  111.         ]);
  112.     }
  113.     #[Route('/{id}'name'app_facture_service_show'methods: ['GET'])]
  114.     public function show(FactureService $factureService): Response
  115.     {
  116.         return $this->render('facture_service/show.html.twig', [
  117.             'facture_service' => $factureService,
  118.         ]);
  119.     }
  120.     #[Route('/{id}/edit'name'app_facture_service_edit'methods: ['GET''POST'])]
  121.     public function edit(Request $requestFactureService $factureServiceEntityManagerInterface $entityManager): Response
  122.     {
  123.         $form $this->createForm(FactureServiceType::class, $factureService);
  124.         $form->handleRequest($request);
  125.         if ($form->isSubmitted() && $form->isValid()) {
  126.             $entityManager->flush();
  127.             return $this->redirectToRoute('app_facture_service_index', [], Response::HTTP_SEE_OTHER);
  128.         }
  129.         return $this->renderForm('facture_service/edit.html.twig', [
  130.             'facture_service' => $factureService,
  131.             'form' => $form,
  132.         ]);
  133.     }
  134.     #[Route('/{id}'name'app_facture_service_delete'methods: ['POST'])]
  135.     public function delete(Request $requestFactureService $factureServiceEntityManagerInterface $entityManager): Response
  136.     {
  137.         if ($this->isCsrfTokenValid('delete' $factureService->getId(), $request->request->get('_token'))) {
  138.             $entityManager->remove($factureService);
  139.             $entityManager->flush();
  140.         }
  141.         return $this->redirectToRoute('app_facture_service_index', [], Response::HTTP_SEE_OTHER);
  142.     }
  143.     private function generateFactureNumber(): string
  144.     {
  145.         return 'SERV-' date('YmdHis') . '-' rand(100999);
  146.     }
  147.     #[Route('/update/prix'name'app_facture_service_update_prix'methods: ['POST'])]
  148.     public function updatePrix(Request $requestEntityManagerInterface $entityManager): JsonResponse
  149.     {
  150.         try {
  151.             $data json_decode($request->getContent(), true);
  152.             if (!isset($data['factureId']) || !isset($data['lignes']) || !isset($data['montantTotal'])) {
  153.                 return new JsonResponse(['success' => false'message' => 'Données manquantes'], 400);
  154.             }
  155.             $factureService $entityManager->getRepository(FactureService::class)->find($data['factureId']);
  156.             if (!$factureService) {
  157.                 return new JsonResponse(['success' => false'message' => 'Facture non trouvée'], 404);
  158.             }
  159.             // Mettre à jour chaque ligne
  160.             foreach ($data['lignes'] as $ligneData) {
  161.                 $ligne $entityManager->getRepository(ServiceFactureLigne::class)->find($ligneData['id']);
  162.                 if ($ligne && $ligne->getServiceFacture()->getId() === $factureService->getId()) {
  163.                     $ligne->setPrix($ligneData['prix']);
  164.                 }
  165.             }
  166.             // Mettre à jour le montant total
  167.             $factureService->setMontantTotal($data['montantTotal']);
  168.             // Si la facture est déjà au statut "Payé", mettre à jour la recette associée
  169.             if ($factureService->getStatus() === 'Payé') {
  170.                 $recetteRepository $entityManager->getRepository(Recette::class);
  171.                 $recette $recetteRepository->findOneBy(['factureService' => $factureService]);
  172.                 if ($recette) {
  173.                     $recette->setMontant($data['montantTotal']);
  174.                 }
  175.             }
  176.             $entityManager->flush();
  177.             return new JsonResponse(['success' => true]);
  178.         } catch (\Exception $e) {
  179.             return new JsonResponse(['success' => false'message' => $e->getMessage()], 500);
  180.         }
  181.     }
  182.     #[Route('/{id}/print'name'app_facture_service_print'methods: ['GET'])]
  183.     public function printFacture(FactureService $factureService): Response
  184.     {
  185.         try {
  186.             // Chemin du logo
  187.             $logoPath $this->getParameter('kernel.project_dir') . '/public/assets/img/perl.jpg';
  188.             // Vérifier si le fichier existe avant de le lire
  189.             if (file_exists($logoPath)) {
  190.                 $logoData file_get_contents($logoPath);
  191.                 $base64Logo base64_encode($logoData);
  192.             } else {
  193.                 // Logo de secours si le fichier n'existe pas
  194.                 $this->logger->warning('Logo introuvable à {path}', ['path' => $logoPath]);
  195.                 $base64Logo ''// Vous pourriez définir un logo par défaut en base64
  196.             }
  197.             // Générer le HTML pour la facture
  198.             $html $this->renderView('facture_service/print.html.twig', [
  199.                 'facture_service' => $factureService,
  200.                 'base64Logo' => $base64Logo,
  201.             ]);
  202.             // Configurer DOMPDF avec des options optimisées
  203.             $options = new Options();
  204.             $options->set('isHtml5ParserEnabled'true);
  205.             $options->set('isRemoteEnabled'true);
  206.             $options->set('defaultFont''DejaVu Sans');
  207.             $options->set('isFontSubsettingEnabled'true);
  208.             // Définir un répertoire temporaire accessible en écriture
  209.             $tempDir $this->getParameter('kernel.project_dir') . '/var/tmp';
  210.             if (is_dir($tempDir) && is_writable($tempDir)) {
  211.                 $options->set('tempDir'$tempDir);
  212.             }
  213.             // Instancier Dompdf
  214.             $dompdf = new Dompdf($options);
  215.             $dompdf->loadHtml($html);
  216.             $dompdf->setPaper('A4');
  217.             // Capturer les erreurs potentielles lors du rendu
  218.             try {
  219.                 $dompdf->render();
  220.             } catch (\Exception $e) {
  221.                 $this->logger->error('Erreur lors du rendu PDF: {message}', [
  222.                     'message' => $e->getMessage(),
  223.                     'trace' => $e->getTraceAsString()
  224.                 ]);
  225.                 throw $e;
  226.             }
  227.             // Générer un nom de fichier avec date/heure pour éviter les doublons
  228.             $date = new \DateTime();
  229.             $fileName 'facture-service-' $factureService->getNumero() . '-' $date->format('YmdHis') . '.pdf';
  230.             // Enregistrer l'action utilisateur si la méthode existe
  231.             if (method_exists($this'logUserAction')) {
  232.                 $this->logger->info('Facture service imprimée', [
  233.                     'facture_service_id' => $factureService->getId(),
  234.                     'facture_service_numero' => $factureService->getNumero(),
  235.                     'filename' => $fileName
  236.                 ]);
  237.             }
  238.             // Récupérer le contenu PDF
  239.             $pdfContent $dompdf->output();
  240.             // Renvoyer le PDF comme réponse avec les bons en-têtes
  241.             $response = new Response($pdfContent);
  242.             $response->headers->set('Content-Type''application/pdf');
  243.             // Utiliser 'inline' pour afficher dans le navigateur
  244.             // Vous pouvez changer en 'attachment' si l'affichage ne fonctionne pas
  245.             $response->headers->set('Content-Disposition''inline; filename="' $fileName '"');
  246.             // Ajouter des en-têtes pour empêcher la mise en cache
  247.             $response->headers->set('Cache-Control''private, max-age=0, no-cache');
  248.             $response->headers->set('Pragma''no-cache');
  249.             return $response;
  250.         } catch (\Exception $e) {
  251.             // Gestion globale des erreurs
  252.             $this->logger->error('Erreur lors de l\'impression de la facture service {id}: {message}', [
  253.                 'id' => $factureService->getId(),
  254.                 'message' => $e->getMessage(),
  255.                 'trace' => $e->getTraceAsString()
  256.             ]);
  257.             $this->addFlash('error''Une erreur est survenue lors de l\'impression de la facture de service.');
  258.             return $this->redirectToRoute('app_facture_service_show', ['id' => $factureService->getId()]);
  259.         }
  260.     }
  261.     #[Route('/update/status'name'app_facture_service_update_status'methods: ['POST'])]
  262.     public function updateStatus(Request $requestEntityManagerInterface $entityManager): JsonResponse
  263.     {
  264.         try {
  265.             $data json_decode($request->getContent(), true);
  266.             if (!isset($data['id']) || !isset($data['status'])) {
  267.                 return new JsonResponse(['success' => false'message' => 'Données manquantes'], 400);
  268.             }
  269.             $factureService $entityManager->getRepository(FactureService::class)->find($data['id']);
  270.             if (!$factureService) {
  271.                 return new JsonResponse(['success' => false'message' => 'Facture non trouvée'], 404);
  272.             }
  273.             $oldStatus $factureService->getStatus();
  274.             $factureService->setStatus($data['status']);
  275.             // Si le statut passe à "Payé", créer ou mettre à jour la recette
  276.             if ($data['status'] === 'Payé' && $oldStatus !== 'Payé') {
  277.                 $recetteRepository $entityManager->getRepository(Recette::class);
  278.                 $recette $recetteRepository->findOneBy(['factureService' => $factureService]);
  279.                 if (!$recette) {
  280.                     // Création d'une nouvelle recette
  281.                     $this->createRecetteForFactureService($factureService$entityManager);
  282.                 } else {
  283.                     // Mise à jour de la recette existante
  284.                     $recette->setMontant($factureService->getMontantTotal());
  285.                     $recette->setDateRecette(new \DateTime());
  286.                 }
  287.             }
  288.             $entityManager->flush();
  289.             return new JsonResponse(['success' => true]);
  290.         } catch (\Exception $e) {
  291.             return new JsonResponse(['success' => false'message' => $e->getMessage()], 500);
  292.         }
  293.     }
  294.     private function createRecetteForFactureService(FactureService $factureServiceEntityManagerInterface $entityManager): void
  295.     {
  296.         // Créer une nouvelle recette
  297.         $recette = new Recette();
  298.         $recette->setDateRecette(new \DateTime());
  299.         $recette->setMontant($factureService->getMontantTotal());
  300.         $recette->setSource('Service');
  301.         $recette->setFactureService($factureService);
  302.         // Associer la boutique si l'utilisateur connecté a une boutique
  303.         $user $this->getUser();
  304.         if ($user && method_exists($user'getBoutique') && $user->getBoutique()) {
  305.             $recette->setBoutique($user->getBoutique());
  306.         }
  307.         $entityManager->persist($recette);
  308.     }
  309.     /**
  310.      * @Route("/facture/service/update/type-paiement", name="app_facture_service_update_type_paiement", methods={"POST"})
  311.      */
  312.     public function updateTypePaiement(Request $requestEntityManagerInterface $entityManager): Response
  313.     {
  314.         $data json_decode($request->getContent(), true);
  315.         $factureId $data['id'] ?? null;
  316.         $typePaiement $data['typePaiement'] ?? null;
  317.         if (!$factureId || !$typePaiement) {
  318.             return $this->json(['success' => false'message' => 'Données incomplètes'], 400);
  319.         }
  320.         $factureService $entityManager->getRepository(FactureService::class)->find($factureId);
  321.         if (!$factureService) {
  322.             return $this->json(['success' => false'message' => 'Facture non trouvée'], 404);
  323.         }
  324.         $factureService->setTypePaiement($typePaiement);
  325.         $entityManager->flush();
  326.         return $this->json(['success' => true]);
  327.     }
  328. }