<?php
namespace App\Controller;
use Dompdf\Dompdf;
use Dompdf\Options;
use App\Entity\Client;
use App\Entity\Recette;
use Psr\Log\LoggerInterface;
use App\Entity\FactureService;
use App\Form\FactureServiceType;
use App\Entity\ServiceFactureLigne;
use App\Repository\ClientRepository;
use Doctrine\ORM\EntityManagerInterface;
use App\Repository\FactureServiceRepository;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
#[Route('/facture/service')]
class FactureServiceController extends AbstractController
{
public function __construct(private readonly LoggerInterface $logger) {}
#[Route('/index', name: 'app_facture_service_index', methods: ['GET'])]
public function index(
FactureServiceRepository $factureServiceRepository,
Security $security
): Response {
$user = $security->getUser();
// Vérifier si l'utilisateur est connecté
if (!$user) {
throw $this->createAccessDeniedException('Vous devez être connecté pour accéder à cette page.');
}
// Récupérer les factures selon le rôle de l'utilisateur
if ($security->isGranted('ROLE_SUPER_ADMIN')) {
// Si Super Admin, récupérer toutes les factures
$factureServices = $factureServiceRepository->findBy([], ['id' => 'DESC']);
} else {
// Pour les autres utilisateurs, récupérer uniquement les factures de leur boutique
$boutique = $user->getBoutique();
if (!$boutique) {
// Si l'utilisateur n'a pas de boutique associée
$factureServices = [];
} else {
// Récupérer les factures de la boutique de l'utilisateur
$factureServices = $factureServiceRepository->findBy(['boutique' => $boutique], ['id' => 'DESC']);
}
}
return $this->render('facture_service/index.html.twig', [
'facture_services' => $factureServices,
]);
}
#[Route('/new', name: 'app_facture_service_new', methods: ['GET', 'POST'])]
public function new(
Request $request,
EntityManagerInterface $entityManager,
ClientRepository $clientRepository,
Security $security
): Response {
$factureService = new FactureService();
$clients = $clientRepository->findAll();
$form = $this->createForm(FactureServiceType::class, $factureService);
$form->handleRequest($request);
// Récupérer l'utilisateur connecté
$user = $security->getUser();
// Récupérer la boutique associée à l'utilisateur
$boutique = null;
if ($user) {
// Supposons que l'utilisateur a une méthode getBoutique()
// Ou adaptez selon votre structure d'entités
$boutique = $user->getBoutique();
// Associer la boutique à la facture de service
$factureService->setBoutique($boutique);
}
if ($form->isSubmitted() && $form->isValid()) {
$clientId = $request->request->get('client_id');
if ($clientId) {
$client = $entityManager->getRepository(Client::class)->find($clientId);
if ($client) {
$factureService->setClient($client);
}
}
// Récupérez directement les services depuis le formulaire
$services = $form->get('services')->get('services')->getData();
$montantTotal = 0;
foreach ($services as $service) {
$ligne = new ServiceFactureLigne();
$ligne->setService($service);
$ligne->setPrix($service->getPrix());
$ligne->setServiceFacture($factureService);
$factureService->addServiceFactureLigne($ligne);
$montantTotal += $service->getPrix();
}
$factureService->setMontantTotal($montantTotal);
$factureService->setNumero($this->generateFactureNumber());
$factureService->setStatus('Payé');
$factureService->setDateCreation(new \DateTime());
// La boutique est déjà définie plus haut
$entityManager->persist($factureService);
// Créer la recette seulement si le statut est "Payé"
if ($factureService->getStatus() === 'Payé') {
$this->createRecetteForFactureService($factureService, $entityManager);
}
$entityManager->flush();
return $this->redirectToRoute('app_facture_service_show', ['id' => $factureService->getId()], Response::HTTP_SEE_OTHER);
}
return $this->renderForm('facture_service/new.html.twig', [
'facture_service' => $factureService,
'form' => $form,
'clients' => $clients,
]);
}
#[Route('/{id}', name: 'app_facture_service_show', methods: ['GET'])]
public function show(FactureService $factureService): Response
{
return $this->render('facture_service/show.html.twig', [
'facture_service' => $factureService,
]);
}
#[Route('/{id}/edit', name: 'app_facture_service_edit', methods: ['GET', 'POST'])]
public function edit(Request $request, FactureService $factureService, EntityManagerInterface $entityManager): Response
{
$form = $this->createForm(FactureServiceType::class, $factureService);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager->flush();
return $this->redirectToRoute('app_facture_service_index', [], Response::HTTP_SEE_OTHER);
}
return $this->renderForm('facture_service/edit.html.twig', [
'facture_service' => $factureService,
'form' => $form,
]);
}
#[Route('/{id}', name: 'app_facture_service_delete', methods: ['POST'])]
public function delete(Request $request, FactureService $factureService, EntityManagerInterface $entityManager): Response
{
if ($this->isCsrfTokenValid('delete' . $factureService->getId(), $request->request->get('_token'))) {
$entityManager->remove($factureService);
$entityManager->flush();
}
return $this->redirectToRoute('app_facture_service_index', [], Response::HTTP_SEE_OTHER);
}
private function generateFactureNumber(): string
{
return 'SERV-' . date('YmdHis') . '-' . rand(100, 999);
}
#[Route('/update/prix', name: 'app_facture_service_update_prix', methods: ['POST'])]
public function updatePrix(Request $request, EntityManagerInterface $entityManager): JsonResponse
{
try {
$data = json_decode($request->getContent(), true);
if (!isset($data['factureId']) || !isset($data['lignes']) || !isset($data['montantTotal'])) {
return new JsonResponse(['success' => false, 'message' => 'Données manquantes'], 400);
}
$factureService = $entityManager->getRepository(FactureService::class)->find($data['factureId']);
if (!$factureService) {
return new JsonResponse(['success' => false, 'message' => 'Facture non trouvée'], 404);
}
// Mettre à jour chaque ligne
foreach ($data['lignes'] as $ligneData) {
$ligne = $entityManager->getRepository(ServiceFactureLigne::class)->find($ligneData['id']);
if ($ligne && $ligne->getServiceFacture()->getId() === $factureService->getId()) {
$ligne->setPrix($ligneData['prix']);
}
}
// Mettre à jour le montant total
$factureService->setMontantTotal($data['montantTotal']);
// Si la facture est déjà au statut "Payé", mettre à jour la recette associée
if ($factureService->getStatus() === 'Payé') {
$recetteRepository = $entityManager->getRepository(Recette::class);
$recette = $recetteRepository->findOneBy(['factureService' => $factureService]);
if ($recette) {
$recette->setMontant($data['montantTotal']);
}
}
$entityManager->flush();
return new JsonResponse(['success' => true]);
} catch (\Exception $e) {
return new JsonResponse(['success' => false, 'message' => $e->getMessage()], 500);
}
}
#[Route('/{id}/print', name: 'app_facture_service_print', methods: ['GET'])]
public function printFacture(FactureService $factureService): Response
{
try {
// Chemin du logo
$logoPath = $this->getParameter('kernel.project_dir') . '/public/assets/img/perl.jpg';
// Vérifier si le fichier existe avant de le lire
if (file_exists($logoPath)) {
$logoData = file_get_contents($logoPath);
$base64Logo = base64_encode($logoData);
} else {
// Logo de secours si le fichier n'existe pas
$this->logger->warning('Logo introuvable à {path}', ['path' => $logoPath]);
$base64Logo = ''; // Vous pourriez définir un logo par défaut en base64
}
// Générer le HTML pour la facture
$html = $this->renderView('facture_service/print.html.twig', [
'facture_service' => $factureService,
'base64Logo' => $base64Logo,
]);
// Configurer DOMPDF avec des options optimisées
$options = new Options();
$options->set('isHtml5ParserEnabled', true);
$options->set('isRemoteEnabled', true);
$options->set('defaultFont', 'DejaVu Sans');
$options->set('isFontSubsettingEnabled', true);
// Définir un répertoire temporaire accessible en écriture
$tempDir = $this->getParameter('kernel.project_dir') . '/var/tmp';
if (is_dir($tempDir) && is_writable($tempDir)) {
$options->set('tempDir', $tempDir);
}
// Instancier Dompdf
$dompdf = new Dompdf($options);
$dompdf->loadHtml($html);
$dompdf->setPaper('A4');
// Capturer les erreurs potentielles lors du rendu
try {
$dompdf->render();
} catch (\Exception $e) {
$this->logger->error('Erreur lors du rendu PDF: {message}', [
'message' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
throw $e;
}
// Générer un nom de fichier avec date/heure pour éviter les doublons
$date = new \DateTime();
$fileName = 'facture-service-' . $factureService->getNumero() . '-' . $date->format('YmdHis') . '.pdf';
// Enregistrer l'action utilisateur si la méthode existe
if (method_exists($this, 'logUserAction')) {
$this->logger->info('Facture service imprimée', [
'facture_service_id' => $factureService->getId(),
'facture_service_numero' => $factureService->getNumero(),
'filename' => $fileName
]);
}
// Récupérer le contenu PDF
$pdfContent = $dompdf->output();
// Renvoyer le PDF comme réponse avec les bons en-têtes
$response = new Response($pdfContent);
$response->headers->set('Content-Type', 'application/pdf');
// Utiliser 'inline' pour afficher dans le navigateur
// Vous pouvez changer en 'attachment' si l'affichage ne fonctionne pas
$response->headers->set('Content-Disposition', 'inline; filename="' . $fileName . '"');
// Ajouter des en-têtes pour empêcher la mise en cache
$response->headers->set('Cache-Control', 'private, max-age=0, no-cache');
$response->headers->set('Pragma', 'no-cache');
return $response;
} catch (\Exception $e) {
// Gestion globale des erreurs
$this->logger->error('Erreur lors de l\'impression de la facture service {id}: {message}', [
'id' => $factureService->getId(),
'message' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
$this->addFlash('error', 'Une erreur est survenue lors de l\'impression de la facture de service.');
return $this->redirectToRoute('app_facture_service_show', ['id' => $factureService->getId()]);
}
}
#[Route('/update/status', name: 'app_facture_service_update_status', methods: ['POST'])]
public function updateStatus(Request $request, EntityManagerInterface $entityManager): JsonResponse
{
try {
$data = json_decode($request->getContent(), true);
if (!isset($data['id']) || !isset($data['status'])) {
return new JsonResponse(['success' => false, 'message' => 'Données manquantes'], 400);
}
$factureService = $entityManager->getRepository(FactureService::class)->find($data['id']);
if (!$factureService) {
return new JsonResponse(['success' => false, 'message' => 'Facture non trouvée'], 404);
}
$oldStatus = $factureService->getStatus();
$factureService->setStatus($data['status']);
// Si le statut passe à "Payé", créer ou mettre à jour la recette
if ($data['status'] === 'Payé' && $oldStatus !== 'Payé') {
$recetteRepository = $entityManager->getRepository(Recette::class);
$recette = $recetteRepository->findOneBy(['factureService' => $factureService]);
if (!$recette) {
// Création d'une nouvelle recette
$this->createRecetteForFactureService($factureService, $entityManager);
} else {
// Mise à jour de la recette existante
$recette->setMontant($factureService->getMontantTotal());
$recette->setDateRecette(new \DateTime());
}
}
$entityManager->flush();
return new JsonResponse(['success' => true]);
} catch (\Exception $e) {
return new JsonResponse(['success' => false, 'message' => $e->getMessage()], 500);
}
}
private function createRecetteForFactureService(FactureService $factureService, EntityManagerInterface $entityManager): void
{
// Créer une nouvelle recette
$recette = new Recette();
$recette->setDateRecette(new \DateTime());
$recette->setMontant($factureService->getMontantTotal());
$recette->setSource('Service');
$recette->setFactureService($factureService);
// Associer la boutique si l'utilisateur connecté a une boutique
$user = $this->getUser();
if ($user && method_exists($user, 'getBoutique') && $user->getBoutique()) {
$recette->setBoutique($user->getBoutique());
}
$entityManager->persist($recette);
}
/**
* @Route("/facture/service/update/type-paiement", name="app_facture_service_update_type_paiement", methods={"POST"})
*/
public function updateTypePaiement(Request $request, EntityManagerInterface $entityManager): Response
{
$data = json_decode($request->getContent(), true);
$factureId = $data['id'] ?? null;
$typePaiement = $data['typePaiement'] ?? null;
if (!$factureId || !$typePaiement) {
return $this->json(['success' => false, 'message' => 'Données incomplètes'], 400);
}
$factureService = $entityManager->getRepository(FactureService::class)->find($factureId);
if (!$factureService) {
return $this->json(['success' => false, 'message' => 'Facture non trouvée'], 404);
}
$factureService->setTypePaiement($typePaiement);
$entityManager->flush();
return $this->json(['success' => true]);
}
}