src/Controller/VenteController.php line 64

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use DateTime;
  4. use App\Entity\Vente;
  5. use App\Entity\Client;
  6. use App\Entity\Facture;
  7. use App\Form\VenteType;
  8. use App\Entity\SortieStock;
  9. use App\Entity\DetailsVente;
  10. use App\Repository\VenteRepository;
  11. use App\Repository\ClientRepository;
  12. use App\Repository\ProduitRepository;
  13. use App\Repository\ProduitStockRepository;
  14. use Doctrine\ORM\EntityManagerInterface;
  15. use Symfony\Component\HttpFoundation\Request;
  16. use Symfony\Component\HttpFoundation\Response;
  17. use Symfony\Component\Routing\Annotation\Route;
  18. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  19. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  20. use Symfony\Component\Security\Core\Security;
  21. #[Route('/vente')]
  22. class VenteController extends AbstractController
  23. {
  24.     #[Route('/'name'app_vente_index'methods: ['GET'])]
  25. public function index(
  26.     VenteRepository $venteRepository,
  27.     Security $security
  28. ): Response {
  29.     /** @var \App\Entity\User $user */
  30.     $user $security->getUser();
  31.     
  32.     // Vérifier si l'utilisateur est connecté
  33.     if (!$user) {
  34.         throw $this->createAccessDeniedException('Vous devez être connecté pour accéder à cette page.');
  35.     }
  36.     
  37.     // Récupérer les ventes selon le rôle de l'utilisateur
  38.     if ($security->isGranted('ROLE_SUPER_ADMIN')) {
  39.         // Si Super Admin, récupérer toutes les ventes
  40.         $ventes $venteRepository->findBy([], ['id' => 'DESC']);
  41.     } else {
  42.         // Pour les autres utilisateurs, récupérer uniquement les ventes de leur boutique
  43.         $boutique $user->getBoutique();
  44.         
  45.         if (!$boutique) {
  46.             // Si l'utilisateur n'a pas de boutique associée
  47.             $ventes = [];
  48.         } else {
  49.             // Récupérer les ventes de la boutique de l'utilisateur
  50.             $ventes $venteRepository->findBy(['boutique' => $boutique], ['id' => 'DESC']);
  51.         }
  52.     }
  53.     
  54.     return $this->render('vente/index.html.twig', [
  55.         'ventes' => $ventes,
  56.     ]);
  57. }
  58.     #[Route('/{id}'name'app_vente_show'methods: ['GET'])]
  59.     public function show(Vente $vente): Response
  60.     {
  61.         $totalAPayer 0;
  62.         foreach ($vente->getDetailsVentes() as $detailVente) {
  63.             $totalAPayer += $detailVente->getPrixTotal();
  64.         }
  65.         return $this->render('vente/show.html.twig', [
  66.             'vente' => $vente,
  67.             'totalAPayer' => $totalAPayer,
  68.         ]);
  69.     }
  70.     #[Route('/edit/{id}'name'edit_vente')]
  71.     public function editVente(
  72.         Vente $vente,
  73.         SessionInterface $session,
  74.         ProduitRepository $productsRepository,
  75.         ClientRepository $clientRepository
  76.     ): Response {
  77.         // Initialiser le panier avec les produits de la vente
  78.         $panier = [];
  79.         foreach ($vente->getDetailsVentes() as $detail) {
  80.             $panier[$detail->getProduit()->getId()] = [
  81.                 'quantite' => $detail->getQuantiteVendu(),
  82.                 'prixVenteUnitaire' => $detail->getPrixUnitaire(),
  83.                 'remisePourcentage' => $detail->getRemisePourcentage() ?? 0
  84.             ];
  85.         }
  86.         // Stocker le panier en session
  87.         $session->set("panier"$panier);
  88.         // Récupérer les clients pour le formulaire
  89.         $clients $clientRepository->findAll();
  90.         // Préparer les données du panier pour l'affichage
  91.         $dataPanier = [];
  92.         $total 0;
  93.         foreach ($panier as $id => $item) {
  94.             $product $productsRepository->find($id);
  95.             $quantite $item['quantite'];
  96.             $prixVenteUnitaire $item['prixVenteUnitaire'];
  97.             $remisePourcentage = isset($item['remisePourcentage']) ? (float)$item['remisePourcentage'] : 0.0;
  98.             $dataPanier[] = [
  99.                 "produit" => $product,
  100.                 "quantite" => $quantite,
  101.                 "prixVenteUnitaire" => $prixVenteUnitaire,
  102.                 "remisePourcentage" => $remisePourcentage
  103.             ];
  104.             $total += $prixVenteUnitaire $quantite * (- ($remisePourcentage 100));
  105.         }
  106.         // Ajouter l'ID de la vente en session
  107.         $session->set("edit_vente_id"$vente->getId());
  108.         return $this->render('panier/edit_vente.html.twig', [
  109.             'dataPanier' => $dataPanier,
  110.             'total' => $total,
  111.             'clients' => $clients,
  112.             'vente' => $vente,
  113.             'selectedClient' => $vente->getClient()
  114.         ]);
  115.     }
  116.     #[Route('/update/{id}'name'update_vente'methods: ['POST'])]
  117.     public function updateVente(
  118.         Request $request,
  119.         Vente $vente,
  120.         SessionInterface $session,
  121.         EntityManagerInterface $entityManager,
  122.         ProduitRepository $produitRepository,
  123.         ProduitStockRepository $produitStockRepository,
  124.         Security $security
  125.     ): Response {
  126.         $panier $session->get('panier', []);
  127.         if (empty($panier)) {
  128.             $this->addFlash('error''Le panier est vide.');
  129.             return $this->redirectToRoute('app_vente_show', ['id' => $vente->getId()]);
  130.         }
  131.         $user $security->getUser();
  132.         $boutique $vente->getBoutique();
  133.         if (!$boutique) {
  134.             $this->addFlash('error''Aucune boutique associée à cette vente.');
  135.             return $this->redirectToRoute('app_vente_show', ['id' => $vente->getId()]);
  136.         }
  137.         // ÉTAPE 1: Sauvegarder les anciennes quantités avant suppression des détails
  138.         $anciensDetailsMap = [];
  139.         foreach ($vente->getDetailsVentes() as $detail) {
  140.             $anciensDetailsMap[$detail->getProduit()->getId()] = $detail->getQuantiteVendu();
  141.         }
  142.         // ÉTAPE 2: Démarrer une transaction atomique
  143.         $entityManager->beginTransaction();
  144.         try {
  145.             // ÉTAPE 3: Calculer les différences et vérifier le stock
  146.             $ajustementsStock = [];
  147.             
  148.             // Vérifier les produits du nouveau panier
  149.             foreach ($panier as $produitId => $item) {
  150.                 $produit $produitRepository->find($produitId);
  151.                 if (!$produit) {
  152.                     throw new \Exception("Produit introuvable (ID: {$produitId})");
  153.                 }
  154.                 $nouvelleQuantite is_array($item) ? $item['quantite'] : $item;
  155.                 $ancienneQuantite = isset($anciensDetailsMap[$produitId]) ? $anciensDetailsMap[$produitId] : 0;
  156.                 $difference $nouvelleQuantite $ancienneQuantite;
  157.                 // Si la quantité a augmenté, vérifier le stock disponible
  158.                 if ($difference 0) {
  159.                     $stockBoutique $produitStockRepository->findOneBy([
  160.                         'produit' => $produit,
  161.                         'boutique' => $boutique
  162.                     ]);
  163.                     if (!$stockBoutique) {
  164.                         throw new \Exception("Le produit '{$produit->getNom()}' n'existe pas dans la boutique");
  165.                     }
  166.                     if ($stockBoutique->getQuantite() < $difference) {
  167.                         throw new \Exception("Stock insuffisant pour '{$produit->getNom()}' (Disponible: {$stockBoutique->getQuantite()}, Nécessaire: {$difference})");
  168.                     }
  169.                 }
  170.                 // Stocker l'ajustement à effectuer
  171.                 if ($difference != 0) {
  172.                     $ajustementsStock[$produitId] = [
  173.                         'produit' => $produit,
  174.                         'difference' => $difference,
  175.                         'nouvelleQuantite' => $nouvelleQuantite
  176.                     ];
  177.                 }
  178.             }
  179.             // ÉTAPE 4: Gérer les produits supprimés (restockage)
  180.             foreach ($anciensDetailsMap as $produitId => $ancienneQuantite) {
  181.                 if (!isset($panier[$produitId])) {
  182.                     $produit $produitRepository->find($produitId);
  183.                     if ($produit) {
  184.                         // Produit supprimé = restockage complet
  185.                         $ajustementsStock[$produitId] = [
  186.                             'produit' => $produit,
  187.                             'difference' => -$ancienneQuantite,
  188.                             'nouvelleQuantite' => 0
  189.                         ];
  190.                     }
  191.                 }
  192.             }
  193.             // ÉTAPE 5: Appliquer les ajustements de stock
  194.             foreach ($ajustementsStock as $produitId => $ajustement) {
  195.                 $produit $ajustement['produit'];
  196.                 $difference $ajustement['difference'];
  197.                 $stockBoutique $produitStockRepository->findOneBy([
  198.                     'produit' => $produit,
  199.                     'boutique' => $boutique
  200.                 ]);
  201.                 if (!$stockBoutique) {
  202.                     throw new \Exception("Stock introuvable pour '{$produit->getNom()}' dans la boutique");
  203.                 }
  204.                 // Mettre à jour le stock
  205.                 $ancienStock $stockBoutique->getQuantite();
  206.                 $nouveauStock $ancienStock $difference// - car difference est du point de vue de la vente
  207.                 
  208.                 if ($nouveauStock 0) {
  209.                     throw new \Exception("Erreur de calcul du stock pour '{$produit->getNom()}'");
  210.                 }
  211.                 $stockBoutique->setQuantite($nouveauStock);
  212.                 $entityManager->persist($stockBoutique);
  213.                 // ÉTAPE 6: Créer l'enregistrement de SortieStock pour tracer le mouvement
  214.                 if ($difference != 0) {
  215.                     $sortieStock = new SortieStock();
  216.                     $sortieStock->setProduit($produit);
  217.                     $sortieStock->setQuantite(abs($difference));
  218.                     $sortieStock->setDateSortie(new DateTime());
  219.                     $sortieStock->setBoutique($boutique);
  220.                     $sortieStock->setUtilisateur($user);
  221.                     
  222.                     // Récupérer le prix de vente depuis le panier
  223.                     if (isset($panier[$produitId])) {
  224.                         $prixVenteUnitaire is_array($panier[$produitId]) && isset($panier[$produitId]['prixVenteUnitaire'])
  225.                             ? $panier[$produitId]['prixVenteUnitaire']
  226.                             : $produit->getPrixUnitaire();
  227.                         $sortieStock->setPrixVente($prixVenteUnitaire);
  228.                     } else {
  229.                         // Pour les produits supprimés, utiliser le prix par défaut
  230.                         $sortieStock->setPrixVente($produit->getPrixUnitaire());
  231.                     }
  232.                     $entityManager->persist($sortieStock);
  233.                 }
  234.             }
  235.             // Supprimer les anciens détails
  236.             foreach ($vente->getDetailsVentes() as $detail) {
  237.                 $entityManager->remove($detail);
  238.             }
  239.             // Mettre à jour le client si nécessaire
  240.             $clientId $request->request->get('client_id');
  241.             if ($clientId) {
  242.                 $client $entityManager->getRepository(Client::class)->find($clientId);
  243.                 if ($client) {
  244.                     $vente->setClient($client);
  245.                     // Mettre à jour aussi le client de la facture associée
  246.                     $facture $entityManager->getRepository(Facture::class)->findOneBy(['vente' => $vente]);
  247.                     if ($facture) {
  248.                         $facture->setClient($client);
  249.                     }
  250.                 }
  251.             }
  252.             // Créer les nouveaux détails
  253.             $montantTotal 0;
  254.             foreach ($panier as $produitId => $item) {
  255.                 $produit $produitRepository->find($produitId);
  256.                 if (!$produit) continue;
  257.                 $quantite is_array($item) ? $item['quantite'] : $item;
  258.                 $prixVenteUnitaire is_array($item) && isset($item['prixVenteUnitaire'])
  259.                     ? $item['prixVenteUnitaire']
  260.                     : $produit->getPrixUnitaire();
  261.                 $remisePourcentage is_array($item) && isset($item['remisePourcentage'])
  262.                     ? (float)$item['remisePourcentage']
  263.                     : 0.0;
  264.                 $prixTotal = (int) round($prixVenteUnitaire $quantite * (- ($remisePourcentage 100)));
  265.                 $montantTotal += $prixTotal;
  266.                 $detailVente = new DetailsVente();
  267.                 $detailVente->setVente($vente);
  268.                 $detailVente->setProduit($produit);
  269.                 $detailVente->setQuantiteVendu($quantite);
  270.                 $detailVente->setPrixUnitaire($prixVenteUnitaire);
  271.                 $detailVente->setPrixTotal($prixTotal);
  272.                 $detailVente->setRemisePourcentage($remisePourcentage);
  273.                 $entityManager->persist($detailVente);
  274.             }
  275.             // Mettre à jour le montant total de la facture associée
  276.             $facture $entityManager->getRepository(Facture::class)->findOneBy(['vente' => $vente]);
  277.             if ($facture) {
  278.                 $facture->setMontantTotal($montantTotal);
  279.             }
  280.             // Valider la transaction complète
  281.             $entityManager->flush();
  282.             $entityManager->commit();
  283.             // Nettoyer la session
  284.             $session->remove('panier');
  285.             $session->remove('edit_vente_id');
  286.             $this->addFlash('success''La vente a été mise à jour avec succès !');
  287.             return $this->redirectToRoute('app_facture_show', ['id' => $facture->getId()]);
  288.         } catch (\Exception $e) {
  289.             // ROLLBACK en cas d'erreur
  290.             if ($entityManager->getConnection()->isTransactionActive()) {
  291.                 $entityManager->rollback();
  292.             }
  293.             $this->addFlash('error''Erreur lors de la modification de la vente : ' $e->getMessage());
  294.             return $this->redirectToRoute('app_vente_show', ['id' => $vente->getId()]);
  295.         }
  296.     }
  297. }