<?php
namespace App\Controller;
use DateTime;
use Dompdf\Dompdf;
use Dompdf\Options;
use App\Entity\Vente;
use App\Entity\Client;
use App\Entity\Facture;
use App\Entity\Produit;
use App\Entity\Recette;
use App\Entity\Boutique;
use App\Entity\Echeance;
use App\Entity\ActionLog;
use App\Entity\SortieStock;
use App\Entity\DetailsVente;
use App\Entity\ProduitStock;
use Psr\Log\LoggerInterface;
use App\Service\RecetteService;
use App\Service\PaiementService;
use App\Repository\VenteRepository;
use App\Repository\ClientRepository;
use App\Repository\ProduitRepository;
use App\Repository\BoutiqueRepository;
use Doctrine\ORM\EntityManagerInterface;
use App\Repository\ProduitStockRepository;
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\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class PanierController extends AbstractController
{
private $recetteService;
private $paiementService;
private $logger;
public function __construct(
RecetteService $recetteService,
PaiementService $paiementService,
LoggerInterface $logger
) {
$this->recetteService = $recetteService;
$this->paiementService = $paiementService;
$this->logger = $logger;
}
#[Route('/panier', name: 'app_panier')]
public function index(
SessionInterface $session,
ProduitRepository $productsRepository,
ClientRepository $clientRepository,
EntityManagerInterface $entityManager
): Response {
$panier = $session->get("panier", []);
$clients = $clientRepository->findAll();
$venteEnModification = $session->get('vente_en_modification');
// On "fabrique" les données avec une structure cohérente
$dataPanier = [];
$total = 0;
foreach ($panier as $id => $item) {
$product = $productsRepository->find($id);
if (!$product) {
continue; // Ignorer les produits qui n'existent plus
}
// CORRECTION : Normaliser la structure du panier
if (is_array($item)) {
$quantite = $item['quantite'] ?? 1;
$prixVenteUnitaire = $item['prixVenteUnitaire'] ?? $product->getPrixUnitaire();
} else {
$quantite = $item;
$prixVenteUnitaire = $product->getPrixUnitaire();
// Mettre à jour la structure du panier pour la cohérence
$panier[$id] = [
'quantite' => $quantite,
'prixVenteUnitaire' => $prixVenteUnitaire
];
}
$dataPanier[] = [
"produit" => $product,
"quantite" => $quantite,
"prixVenteUnitaire" => $prixVenteUnitaire
];
$total += $prixVenteUnitaire * $quantite;
}
// Sauvegarder le panier normalisé
$session->set("panier", $panier);
// Si en mode modification, charger les données additionnelles
$venteData = null;
if ($venteEnModification) {
$vente = $entityManager->getRepository(Vente::class)->find($venteEnModification);
if ($vente) {
$venteData = $this->prepareVenteDataForTemplate($vente);
}
}
// Logs détaillés pour debug
$this->logger->info('PanierController::index - Données envoyées au template', [
'dataPanier' => $dataPanier,
'total' => $total,
'venteEnModification' => $venteEnModification,
'venteData' => $venteData
]);
// Log spécifique pour la vente 866
if ($venteEnModification === 866) {
$this->logger->info('=== VENTE 866 - ANALYSE DÉTAILLÉE ===');
foreach ($dataPanier as $index => $item) {
$this->logger->info("Item $index:", [
'produit_id' => $item['produit']->getId(),
'produit_nom' => $item['produit']->getNom(),
'produit_nom_json' => json_encode($item['produit']->getNom()),
'produit_nom_length' => strlen($item['produit']->getNom()),
'quantite' => $item['quantite'],
'prixVenteUnitaire' => $item['prixVenteUnitaire']
]);
}
}
return $this->render('panier/index.html.twig', compact(
"dataPanier",
"total",
"clients",
"venteEnModification",
"venteData"
));
}
// Nouvelle route pour recevoir les données de vente en AJAX
#[Route('/panier/vente-ajax', name: 'vente_panier_ajax', methods: ['POST'])]
public function ventePanierAjax(
SessionInterface $session,
EntityManagerInterface $entityManager,
ProduitRepository $produitRepository,
ProduitStockRepository $produitStockRepository,
Security $security,
Request $request
): JsonResponse {
try {
$panier = $session->get('panier', []);
if (empty($panier)) {
return new JsonResponse(['success' => false, 'message' => 'Votre panier est vide.'], 400);
}
$user = $security->getUser();
$boutique = $user->getBoutique();
if (!$boutique) {
return new JsonResponse(['success' => false, 'message' => 'Aucune boutique associée à votre compte.'], 400);
}
$data = json_decode($request->getContent(), true);
$clientId = $data['client_id'] ?? null;
$tva = (float)($data['tva'] ?? 0);
$fraisTransport = (float)($data['frais_transport'] ?? 0);
$statutPaiement = $data['statut_paiement'] ?? 'Payé';
$echeancesData = $data['echeances'] ?? [];
$acompteDirect = isset($data['acompte_direct']) ? (float)$data['acompte_direct'] : 0;
$dateVente = $data['date_vente'] ?? null;
$entityManager->beginTransaction();
// Déterminer la date de vente
$dateVenteFinale = null;
if ($dateVente && !empty($dateVente)) {
try {
$dateVenteFinale = new DateTime($dateVente);
} catch (\Exception $e) {
// Si la date est invalide, utiliser la date actuelle
$dateVenteFinale = new DateTime();
}
} else {
// Si aucune date n'est fournie, utiliser la date actuelle
$dateVenteFinale = new DateTime();
}
$vente = (new Vente())
->setDescription('Vente de produits')
->setDateVente($dateVenteFinale)
->setVendeur($user)
->setBoutique($boutique);
$montantTotal = 0;
foreach ($panier as $produitId => $item) {
$produit = $produitRepository->find($produitId);
if (!$produit) continue;
$quantite = is_array($item) ? ($item['quantite'] ?? 1) : (int)$item;
$prixVenteUnitaire = is_array($item) ? ($item['prixVenteUnitaire'] ?? $produit->getPrixUnitaire()) : $produit->getPrixUnitaire();
$stock = $produitStockRepository->findOneBy(['produit' => $produit, 'boutique' => $boutique]) ?? (new ProduitStock())
->setProduit($produit)->setBoutique($boutique)->setQuantite(0);
$stock->setQuantite($stock->getQuantite() - $quantite);
$entityManager->persist($stock);
$sortie = (new SortieStock())
->setProduit($produit)
->setQuantite($quantite)
->setDateSortie(new DateTime())
->setPrixVente($prixVenteUnitaire)
->setBoutique($boutique)
->setUtilisateur($user);
$entityManager->persist($sortie);
$prixTotal = $quantite * $prixVenteUnitaire;
$montantTotal += $prixTotal;
$detail = (new DetailsVente())
->setVente($vente)
->setProduit($produit)
->setQuantiteVendu($quantite)
->setPrixUnitaire($prixVenteUnitaire)
->setPrixTotal($prixTotal);
$entityManager->persist($detail);
}
$client = $clientId ? $entityManager->getRepository(Client::class)->find($clientId) : null;
if ($client) $vente->setClient($client);
$entityManager->persist($vente);
$entityManager->flush();
$montantTotalFinal = $montantTotal + $fraisTransport;
$facture = (new Facture())
->setVente($vente)
->setMontantTotal($montantTotal)
->setCreatedAt(new DateTime())
->setType('Facture')
->setTva($tva)
->setFraisTransport($fraisTransport)
->setStatutPaiement($statutPaiement);
if ($client) $facture->setClient($client);
if ($statutPaiement === 'Payé') {
$facture->setMontantPaye($montantTotalFinal);
$entityManager->persist($facture);
$entityManager->flush();
$echeanceAcompte = new Echeance();
$echeanceAcompte->setFacture($facture)
->setAmount($montantTotalFinal)
->setDate(new DateTime())
->setIsPaid(true)
->setClient($client)
->setVente($vente)
->setType('acompte');
$entityManager->persist($echeanceAcompte);
$entityManager->flush();
$this->recetteService->creerRecetteDepuisVente($vente, $montantTotalFinal, $boutique);
} elseif ($statutPaiement === 'Partiellement payé') {
$facture->setMontantPaye($acompteDirect);
$entityManager->persist($facture);
$entityManager->flush();
if ($acompteDirect > 0) {
$echeanceAcompte = new Echeance();
$echeanceAcompte->setFacture($facture)
->setAmount($acompteDirect)
->setDate(new DateTime())
->setIsPaid(true)
->setClient($client)
->setVente($vente)
->setType('acompte');
$entityManager->persist($echeanceAcompte);
$this->recetteService->creerRecetteDepuisVente($vente, $acompteDirect, $boutique);
}
$resteAPayer = $montantTotalFinal - $acompteDirect;
$totalEcheances = 0;
foreach ($echeancesData as $echeanceData) {
$echeanceAmount = (float)$echeanceData['amount'];
$totalEcheances += $echeanceAmount;
$echeance = new Echeance();
$echeance->setAmount($echeanceAmount)
->setDate(new DateTime($echeanceData['date']))
->setIsPaid(false)
->setFacture($facture)
->setClient($client)
->setVente($vente)
->setType('echeance');
$entityManager->persist($echeance);
}
if ($resteAPayer > 0 && $totalEcheances < $resteAPayer) {
$echeance = new Echeance();
$echeance->setAmount($resteAPayer - $totalEcheances)
->setDate(new DateTime())
->setIsPaid(false)
->setFacture($facture)
->setClient($client)
->setVente($vente)
->setType('echeance');
$entityManager->persist($echeance);
}
} else {
$facture->setMontantPaye(0);
$entityManager->persist($facture);
$entityManager->flush();
}
$entityManager->flush();
$entityManager->commit();
$session->set('panier', []);
return new JsonResponse([
'success' => true,
'message' => 'La vente a été effectuée avec succès.',
'factureId' => $facture->getId(),
'redirect' => $this->generateUrl('app_facture_show', ['id' => $facture->getId()])
]);
} catch (\Exception $e) {
return new JsonResponse([
'success' => false,
'message' => 'Erreur lors de la vente : ' . $e->getMessage()
], 500);
}
}
#[Route('panier/remove/{id}', name: 'remove')]
public function remove(Produit $product, SessionInterface $session)
{
// On récupère le panier actuel
$panier = $session->get("panier", []);
$id = $product->getId();
if (!empty($panier[$id])) {
if (is_array($panier[$id])) {
if ($panier[$id]['quantite'] > 1) {
$panier[$id]['quantite']--;
} else {
unset($panier[$id]);
}
} else {
if ($panier[$id] > 1) {
$panier[$id]--;
} else {
unset($panier[$id]);
}
}
}
// On sauvegarde dans la session
$session->set("panier", $panier);
return $this->redirectToRoute("app_panier");
}
#[Route('panier/delete/{id}', name: 'delete')]
public function supprimer(Produit $product, SessionInterface $session)
{
// On récupère le panier actuel
$panier = $session->get("panier", []);
$id = $product->getId();
if (!empty($panier[$id])) {
unset($panier[$id]);
}
// On sauvegarde dans la session
$session->set("panier", $panier);
return $this->redirectToRoute("app_panier");
}
#[Route('delete', name: 'delete_all')]
public function deleteAll(SessionInterface $session)
{
$session->remove("panier");
return $this->redirectToRoute("app_panier");
}
#[Route('/panier/clear-ajax', name: 'clear_cart_ajax', methods: ['POST'])]
public function clearCartAjax(SessionInterface $session): JsonResponse
{
try {
$session->remove("panier");
return new JsonResponse([
'success' => true,
'message' => 'Panier vidé avec succès'
]);
} catch (\Exception $e) {
return new JsonResponse([
'success' => false,
'message' => 'Erreur lors du vidage du panier : ' . $e->getMessage()
], 500);
}
}
#[Route('/panier/count', name: 'count_panier')]
public function countPanier(SessionInterface $session)
{
$panier = $session->get("panier", []);
$panierSize = count($panier);
return new JsonResponse(['panierSize' => $panierSize]);
}
#[Route('/panier/search', name: 'search_product', methods: ['GET'])]
public function searchProduct(Request $request, ProduitRepository $produitRepository, ProduitStockRepository $produitStockRepository): JsonResponse
{
$query = $request->query->get('q');
$this->logger->info('searchProduct appelée avec query:', ['query' => $query]);
if (!$query) {
$this->logger->info('Query vide, retour vide');
return new JsonResponse(['success' => false, 'products' => [], 'search' => $query]);
}
// Utilise la recherche flexible optimisée
$produits = $produitRepository->searchProduitsFlexible($query);
$this->logger->info('Produits trouvés:', ['count' => count($produits)]);
$resultats = [];
foreach ($produits as $produit) {
$quantiteDisponible = $produitStockRepository->findTotalQuantityByProduit($produit);
$resultat = [
'id' => $produit->getId(),
'nom' => $produit->getNom(),
'prixUnitaire' => $produit->getPrixUnitaire(),
'quantiteDisponible' => $quantiteDisponible ?? 0
];
$resultats[] = $resultat;
$this->logger->info('Produit ajouté au résultat:', $resultat);
}
$response = ['success' => true, 'products' => $resultats, 'search' => $query];
$this->logger->info('Réponse searchProduct:', $response);
return new JsonResponse($response);
}
// CORRECTION PRINCIPALE : Modification de la méthode addProductAjax
#[Route('/panier/add-ajax', name: 'add_product_ajax', methods: ['POST'])]
public function addProductAjax(
Request $request,
SessionInterface $session,
ProduitRepository $produitRepository,
BoutiqueRepository $boutiqueRepository,
ProduitStockRepository $produitStockRepository
): JsonResponse {
// Log d'entrée
error_log('=== DÉBUT addProductAjax ===');
error_log('Request content: ' . $request->getContent());
error_log('Content-Type: ' . $request->headers->get('Content-Type'));
try {
$data = json_decode($request->getContent(), true);
error_log('Data décodé: ' . json_encode($data));
$produitId = $data['id'] ?? null;
$quantite = (int)($data['quantite'] ?? 1);
$prixVenteUnitaire = (float)($data['prixVenteUnitaire'] ?? 0);
error_log("Produit ID: $produitId, Quantité: $quantite, Prix: $prixVenteUnitaire");
if (!$produitId) {
error_log('Erreur: ID produit manquant');
return new JsonResponse(['success' => false, 'message' => 'ID produit manquant.'], 400);
}
$produit = $produitRepository->find($produitId);
if (!$produit) {
error_log('Erreur: Produit introuvable');
return new JsonResponse(['success' => false, 'message' => 'Produit introuvable.'], 404);
}
if ($quantite < 1) {
error_log('Erreur: Quantité invalide');
return new JsonResponse(['success' => false, 'message' => 'Quantité invalide.'], 400);
}
if ($prixVenteUnitaire < 0) {
error_log('Erreur: Prix de vente invalide');
return new JsonResponse(['success' => false, 'message' => 'Prix de vente invalide.'], 400);
}
// Permettre les prix de vente à 0
// Si le prix n'est pas fourni (null ou undefined), utiliser le prix de base
// Mais permettre explicitement les prix de 0
if ($prixVenteUnitaire === null || $prixVenteUnitaire === 0 && !isset($data['prixVenteUnitaire'])) {
$prixVenteUnitaire = $produit->getPrixUnitaire();
error_log("Prix mis à jour: $prixVenteUnitaire");
}
$quantiteDisponible = $produitStockRepository->findTotalQuantityByProduit($produit);
error_log("Quantité disponible: $quantiteDisponible");
$panier = $session->get("panier", []);
error_log('Panier avant: ' . json_encode($panier));
// Vérifier si le produit existe déjà dans le panier
if (isset($panier[$produitId])) {
if (is_array($panier[$produitId])) {
// Ajouter à la quantité existante
$panier[$produitId]['quantite'] += $quantite;
$panier[$produitId]['prixVenteUnitaire'] = $prixVenteUnitaire;
} else {
// Convertir l'ancienne structure en nouvelle
$panier[$produitId] = [
'quantite' => $panier[$produitId] + $quantite,
'prixVenteUnitaire' => $prixVenteUnitaire
];
}
} else {
// Nouveau produit dans le panier
$panier[$produitId] = [
'quantite' => $quantite,
'prixVenteUnitaire' => $prixVenteUnitaire
];
}
$session->set("panier", $panier);
error_log('Panier après: ' . json_encode($panier));
$quantiteFinal = $panier[$produitId]['quantite'];
$prixVenteFinal = $panier[$produitId]['prixVenteUnitaire'];
$response = [
'success' => true,
'id' => $produit->getId(),
'nom' => $produit->getNom(),
'prixUnitaire' => $produit->getPrixUnitaire(),
'prixVenteUnitaire' => $prixVenteFinal,
'quantite' => $quantiteFinal,
'total' => $prixVenteFinal * $quantiteFinal,
'quantiteDisponible' => $quantiteDisponible
];
error_log('Réponse finale: ' . json_encode($response));
error_log('=== FIN addProductAjax SUCCESS ===');
return new JsonResponse($response);
} catch (\Exception $e) {
error_log('=== EXCEPTION dans addProductAjax ===');
error_log('Exception: ' . $e->getMessage());
error_log('Stack trace: ' . $e->getTraceAsString());
return new JsonResponse([
'success' => false,
'message' => 'Erreur lors de l\'ajout : ' . $e->getMessage()
], 500);
}
}
#[Route('/panier/update-quantity', name: 'update_quantity', methods: ['POST'])]
public function updateQuantity(Request $request, SessionInterface $session, ProduitRepository $produitRepository): JsonResponse
{
try {
$data = json_decode($request->getContent(), true);
$produitId = $data['id'] ?? null;
$quantite = (int) ($data['quantite'] ?? 0);
if (!$produitId || $quantite < 1) {
return new JsonResponse(['success' => false, 'message' => 'Données invalides.'], 400);
}
$produit = $produitRepository->find($produitId);
if (!$produit) {
return new JsonResponse(['success' => false, 'message' => 'Produit introuvable.'], 404);
}
$panier = $session->get("panier", []);
if (isset($panier[$produitId])) {
if (is_array($panier[$produitId])) {
$panier[$produitId]['quantite'] = $quantite;
} else {
$panier[$produitId] = [
'quantite' => $quantite,
'prixVenteUnitaire' => $produit->getPrixUnitaire()
];
}
} else {
$panier[$produitId] = [
'quantite' => $quantite,
'prixVenteUnitaire' => $produit->getPrixUnitaire()
];
}
$session->set("panier", $panier);
$prixUnitaire = is_array($panier[$produitId])
? $panier[$produitId]['prixVenteUnitaire']
: $produit->getPrixUnitaire();
return new JsonResponse([
'success' => true,
'id' => $produit->getId(),
'quantite' => $quantite,
'total' => $quantite * $prixUnitaire
]);
} catch (\Exception $e) {
return new JsonResponse([
'success' => false,
'message' => 'Erreur lors de la mise à jour : ' . $e->getMessage()
], 500);
}
}
#[Route('/panier/update-price', name: 'update_price', methods: ['POST'])]
public function updatePrice(Request $request, SessionInterface $session, ProduitRepository $produitRepository): JsonResponse
{
try {
$data = json_decode($request->getContent(), true);
$produitId = $data['id'];
$prixVenteUnitaire = (float)$data['prixVenteUnitaire'];
if ($prixVenteUnitaire < 0) {
return new JsonResponse(['success' => false, 'message' => 'Prix invalide.'], 400);
}
$panier = $session->get('panier', []);
if (!isset($panier[$produitId])) {
return new JsonResponse(['success' => false, 'message' => 'Produit non trouvé dans le panier'], 400);
}
// Convertir en tableau si nécessaire
if (!is_array($panier[$produitId])) {
$panier[$produitId] = [
'quantite' => $panier[$produitId],
'prixVenteUnitaire' => $prixVenteUnitaire
];
} else {
$panier[$produitId]['prixVenteUnitaire'] = $prixVenteUnitaire;
}
$session->set('panier', $panier);
return new JsonResponse([
'success' => true,
'prixVenteUnitaire' => $prixVenteUnitaire
]);
} catch (\Exception $e) {
return new JsonResponse([
'success' => false,
'message' => 'Erreur lors de la mise à jour du prix : ' . $e->getMessage()
], 500);
}
}
#[Route('/panier/add-client-ajax', name: 'add_client_ajax', methods: ['POST'])]
public function addClientAjax(
Request $request,
EntityManagerInterface $entityManager
): JsonResponse {
try {
error_log('=== [addClientAjax] Début appel ===');
$data = json_decode($request->getContent(), true);
error_log('Payload reçu : ' . json_encode($data));
$nom = $data['nom'] ?? '';
$phone = $data['phone'] ?? '';
$adresse = $data['adresse'] ?? null;
if (empty($nom) || empty($phone)) {
error_log('Erreur : nom ou téléphone manquant');
return new JsonResponse(['success' => false, 'message' => 'Nom et téléphone requis.'], 400);
}
$client = new Client();
$client->setNom($nom);
$client->setPhone($phone);
$client->setAdresse($adresse);
$entityManager->persist($client);
$entityManager->flush();
error_log('Client ajouté avec succès, id=' . $client->getId());
return new JsonResponse([
'success' => true,
'client' => [
'id' => $client->getId(),
'nom' => $client->getNom()
]
]);
} catch (\Exception $e) {
error_log('Exception dans addClientAjax : ' . $e->getMessage());
return new JsonResponse([
'success' => false,
'message' => 'Erreur lors de l\'ajout du client : ' . $e->getMessage()
], 500);
}
}
#[Route('/panier/delete-ajax/{id}', name: 'delete_ajax', methods: ['DELETE'])]
public function deleteAjax(
Produit $produit,
SessionInterface $session,
ProduitRepository $produitRepository
): JsonResponse {
try {
// Récupérer le panier
$panier = $session->get("panier", []);
$id = $produit->getId();
// Supprimer le produit du panier
if (!empty($panier[$id])) {
unset($panier[$id]);
}
// Recalculer le total
$total = 0;
foreach ($panier as $produitId => $item) {
$produitPanier = $produitRepository->find($produitId);
if ($produitPanier) {
$quantite = is_array($item) ? $item['quantite'] : $item;
$prix = is_array($item) && isset($item['prixVenteUnitaire'])
? $item['prixVenteUnitaire']
: $produitPanier->getPrixUnitaire();
$total += $prix * $quantite;
}
}
// Sauvegarder le panier mis à jour
$session->set("panier", $panier);
return new JsonResponse([
'success' => true,
'newTotal' => number_format($total, 2),
'isEmpty' => empty($panier)
]);
} catch (\Exception $e) {
return new JsonResponse([
'success' => false,
'message' => 'Erreur lors de la suppression : ' . $e->getMessage()
], 500);
}
}
/**
* Route AJAX pour récupérer les données d'une vente
*/
#[Route('/panier/get-vente-data/{id}', name: 'get_vente_data_ajax', methods: ['GET'])]
public function getVenteDataAjax(
int $id,
EntityManagerInterface $entityManager
): JsonResponse {
try {
$vente = $entityManager->getRepository(Vente::class)->find($id);
if (!$vente) {
return new JsonResponse(['success' => false, 'message' => 'Vente introuvable.'], 404);
}
$factures = $vente->getFactures();
$facture = $factures->count() > 0 ? $factures->first() : null;
$venteData = [
'id' => $vente->getId(),
'client' => $vente->getClient() ? [
'id' => $vente->getClient()->getId(),
'nom' => $vente->getClient()->getNom()
] : null,
'tva' => $facture ? $facture->getTva() : 0,
'fraisTransport' => $facture ? $facture->getFraisTransport() : 0,
'statutPaiement' => $facture ? $facture->getStatutPaiement() : 'Payé',
'montantPaye' => $facture ? $facture->getMontantPaye() : 0,
'montantTotal' => $facture ? $facture->getMontantTotal() : 0,
];
// Récupérer les échéances
$echeances = [];
if ($facture) {
foreach ($facture->getEcheance() as $echeance) {
if ($echeance->getType() !== 'acompte') {
$echeances[] = [
'id' => $echeance->getId(),
'amount' => $echeance->getAmount(),
'date' => $echeance->getDate()->format('Y-m-d'),
'isPaid' => $echeance->isIsPaid()
];
}
}
}
$venteData['echeances'] = $echeances;
return new JsonResponse(['success' => true, 'data' => $venteData]);
} catch (\Exception $e) {
return new JsonResponse([
'success' => false,
'message' => 'Erreur lors de la récupération des données : ' . $e->getMessage()
], 500);
}
}
#[Route('/vente/{id}/modifier', name: 'edit_vente')]
public function editVente(
int $id,
EntityManagerInterface $entityManager,
SessionInterface $session
): Response {
$vente = $entityManager->getRepository(Vente::class)->find($id);
if (!$vente) {
throw $this->createNotFoundException("Vente introuvable");
}
$panier = [];
foreach ($vente->getDetailsVentes() as $detail) {
$produit = $detail->getProduit();
$panier[$produit->getId()] = [
'quantite' => $detail->getQuantiteVendu(),
'prixVenteUnitaire' => $detail->getPrixUnitaire()
];
}
$session->set('panier', $panier);
$session->set('vente_en_modification', $vente->getId());
return $this->redirectToRoute('modifier_vente', ['id' => $vente->getId()]); // On affiche le panier existant
}
/**
* Prépare les données d'une vente pour le template
*/
private function prepareVenteDataForTemplate(Vente $vente): array
{
$factures = $vente->getFactures();
$facture = $factures->count() > 0 ? $factures->first() : null;
$echeances = [];
if ($facture) {
foreach ($facture->getEcheance() as $echeance) {
if ($echeance->getType() !== 'acompte') {
$echeances[] = [
'id' => $echeance->getId(),
'amount' => $echeance->getAmount(),
'date' => $echeance->getDate()->format('Y-m-d'),
'isPaid' => $echeance->isIsPaid()
];
}
}
}
return [
'id' => $vente->getId(),
'client' => $vente->getClient() ? [
'id' => $vente->getClient()->getId(),
'nom' => $vente->getClient()->getNom()
] : null,
'tva' => $facture ? $facture->getTva() : 0,
'fraisTransport' => $facture ? $facture->getFraisTransport() : 0,
'statutPaiement' => $facture ? $facture->getStatutPaiement() : 'Payé',
'montantPaye' => $facture ? $facture->getMontantPaye() : 0,
'echeances' => $echeances
];
}
/**
* Route unifiée pour rediriger vers le panier en mode modification
*/
#[Route('/panier/modifier-vente/{id}', name: 'modifier_vente', methods: ['GET'])]
public function modifierVente(
int $id,
SessionInterface $session,
EntityManagerInterface $entityManager
): Response {
$vente = $entityManager->getRepository(Vente::class)->find($id);
if (!$vente) {
$this->addFlash('error', 'Vente introuvable.');
return $this->redirectToRoute('app_vente_index');
}
$user = $this->getUser();
if ($vente->getBoutique() !== $user->getBoutique()) {
$this->addFlash('error', 'Vous n\'avez pas l\'autorisation de modifier cette vente.');
return $this->redirectToRoute('app_vente_index');
}
// Vérifier si la vente peut être modifiée
if (!$this->peutModifierVente($vente)) {
$this->addFlash('error', 'Cette vente ne peut plus être modifiée.');
return $this->redirectToRoute('app_vente_index');
}
// Charger les données de la vente dans la session
$this->chargerDonneesVenteEnSession($vente, $session);
$this->addFlash('info', 'Vente chargée pour modification. Vous pouvez maintenant modifier les produits et les informations.');
return $this->redirectToRoute('app_panier');
}
/**
* Charge les données d'une vente dans la session pour modification
*/
private function chargerDonneesVenteEnSession(Vente $vente, SessionInterface $session): void
{
// Log spécifique pour la vente 866
if ($vente->getId() === 866) {
$this->logger->info('=== DÉBUT chargerDonneesVenteEnSession pour vente 866 ===');
}
// Charger les produits dans le panier
$panier = [];
foreach ($vente->getDetailsVentes() as $detail) {
$produit = $detail->getProduit();
$panierItem = [
'quantite' => $detail->getQuantiteVendu(),
'prixVenteUnitaire' => $detail->getPrixUnitaire()
];
$panier[$produit->getId()] = $panierItem;
// Log spécifique pour la vente 866
if ($vente->getId() === 866) {
$this->logger->info('Produit ajouté au panier:', [
'produit_id' => $produit->getId(),
'produit_nom' => $produit->getNom(),
'produit_nom_length' => strlen($produit->getNom()),
'produit_nom_json' => json_encode($produit->getNom()),
'detail' => $panierItem
]);
}
}
if ($vente->getId() === 866) {
$this->logger->info('Panier final pour vente 866:', $panier);
}
$session->set('panier', $panier);
$session->set('vente_en_modification', $vente->getId());
// Préremplir les informations annexes
$factures = $vente->getFactures();
$facture = $factures->count() > 0 ? $factures->first() : null;
$session->set('panier_client_id', $vente->getClient() ? $vente->getClient()->getId() : null);
$session->set('panier_tva', $facture ? $facture->getTva() : 0);
$session->set('panier_frais_transport', $facture ? $facture->getFraisTransport() : 0);
$session->set('panier_statut_paiement', $facture ? $facture->getStatutPaiement() : 'Payé');
$session->set('panier_montant_paye', $facture ? $facture->getMontantPaye() : 0);
// Charger les échéances (hors acomptes)
$echeances = [];
if ($facture) {
foreach ($facture->getEcheance() as $echeance) {
if ($echeance->getType() !== 'acompte') {
$echeances[] = [
'id' => $echeance->getId(),
'amount' => $echeance->getAmount(),
'date' => $echeance->getDate()->format('Y-m-d'),
'isPaid' => $echeance->isIsPaid()
];
}
}
}
$session->set('panier_echeances', $echeances);
}
/**
* Méthode utilitaire pour vérifier si une vente peut être modifiée
*/
private function peutModifierVente(Vente $vente): bool
{
// Vérifier si la vente n'est pas trop ancienne (ex: plus de 7 jours)
$dateLimit = new DateTime('-7 days');
if ($vente->getDateVente() < $dateLimit) {
return false;
}
// Vérifier si la vente n'a pas d'échéances payées (sauf acomptes)
$factures = $vente->getFactures();
if ($factures->count() > 0) {
$facture = $factures->first();
foreach ($facture->getEcheance() as $echeance) {
if ($echeance->isIsPaid() && $echeance->getType() !== 'acompte') {
return false; // Ne peut pas modifier si des échéances sont payées
}
}
}
return true;
}
/**
* Route AJAX pour traiter la modification d'une vente
*/
#[Route('/panier/modifier-vente-ajax', name: 'modifier_vente_ajax', methods: ['POST'])]
public function modifierVentZeAjax(
SessionInterface $session,
EntityManagerInterface $entityManager,
ProduitRepository $produitRepository,
ProduitStockRepository $produitStockRepository,
Security $security,
Request $request
): JsonResponse {
try {
$panier = $session->get('panier', []);
if (empty($panier)) {
return new JsonResponse(['success' => false, 'message' => 'Votre panier est vide.'], 400);
}
$venteId = $session->get('vente_en_modification');
if (!$venteId) {
return new JsonResponse(['success' => false, 'message' => 'Aucune vente en cours de modification.'], 400);
}
$user = $security->getUser();
$boutique = $user->getBoutique();
if (!$boutique) {
return new JsonResponse(['success' => false, 'message' => 'Aucune boutique associée à votre compte.'], 400);
}
$data = json_decode($request->getContent(), true);
$clientId = $data['client_id'] ?? null;
$tva = (float)($data['tva'] ?? 0);
$fraisTransport = (float)($data['frais_transport'] ?? 0);
$statutPaiement = $data['statut_paiement'] ?? 'Payé';
$echeancesData = $data['echeances'] ?? [];
$acompteDirect = isset($data['acompte_direct']) ? (float)$data['acompte_direct'] : 0;
$dateVente = $data['date_vente'] ?? null;
$entityManager->beginTransaction();
// Récupérer la vente originale
$venteOriginale = $entityManager->getRepository(Vente::class)->find($venteId);
if (!$venteOriginale) {
$entityManager->rollback();
return new JsonResponse(['success' => false, 'message' => 'Vente originale introuvable.'], 404);
}
// Vérifier les permissions
if ($venteOriginale->getBoutique() !== $boutique) {
$entityManager->rollback();
return new JsonResponse(['success' => false, 'message' => 'Vous n\'avez pas l\'autorisation de modifier cette vente.'], 403);
}
// Vérifier si la vente peut être modifiée
if (!$this->peutModifierVente($venteOriginale)) {
$entityManager->rollback();
return new JsonResponse(['success' => false, 'message' => 'Cette vente ne peut plus être modifiée.'], 400);
}
// Étape 1: Annuler la vente originale (restaurer le stock)
$this->annulerVenteOriginale($venteOriginale, $entityManager, $produitStockRepository);
// Étape 2: Gérer la date de vente (permettre la modification si une nouvelle date est fournie)
if ($dateVente && !empty($dateVente)) {
try {
$nouvelleDate = new DateTime($dateVente);
$venteOriginale->setDateVente($nouvelleDate);
} catch (\Exception $e) {
// Si la date est invalide, conserver la date originale
// Pas de modification de la date
}
}
// Si aucune date n'est fournie, la date originale est conservée
// Étape 3: Supprimer les anciens détails de vente
foreach ($venteOriginale->getDetailsVentes() as $ancienDetail) {
$entityManager->remove($ancienDetail);
}
// Étape 4: Créer les nouveaux détails de vente et mettre à jour le stock
$montantTotal = 0;
foreach ($panier as $produitId => $item) {
$produit = $produitRepository->find($produitId);
if (!$produit) continue;
$quantite = is_array($item) ? ($item['quantite'] ?? 1) : (int)$item;
$prixVenteUnitaire = is_array($item) ? ($item['prixVenteUnitaire'] ?? $produit->getPrixUnitaire()) : $produit->getPrixUnitaire();
// Mettre à jour le stock
$stock = $produitStockRepository->findOneBy(['produit' => $produit, 'boutique' => $boutique]) ?? (new ProduitStock())
->setProduit($produit)->setBoutique($boutique)->setQuantite(0);
$stock->setQuantite($stock->getQuantite() - $quantite);
$entityManager->persist($stock);
// Créer une nouvelle sortie de stock
$sortie = (new SortieStock())
->setProduit($produit)
->setQuantite($quantite)
->setDateSortie(new DateTime())
->setPrixVente($prixVenteUnitaire)
->setBoutique($boutique)
->setUtilisateur($user);
$entityManager->persist($sortie);
$prixTotal = $quantite * $prixVenteUnitaire;
$montantTotal += $prixTotal;
// Créer le nouveau détail de vente
$detail = (new DetailsVente())
->setVente($venteOriginale)
->setProduit($produit)
->setQuantiteVendu($quantite)
->setPrixUnitaire($prixVenteUnitaire)
->setPrixTotal($prixTotal);
$entityManager->persist($detail);
}
// Étape 5: Mettre à jour le client
$client = $clientId ? $entityManager->getRepository(Client::class)->find($clientId) : null;
$venteOriginale->setClient($client);
// Étape 6: Mettre à jour ou créer la facture
$factures = $venteOriginale->getFactures();
$facture = $factures->count() > 0 ? $factures->first() : null;
if (!$facture) {
$facture = new Facture();
$facture->setVente($venteOriginale);
$facture->setCreatedAt(new DateTime());
}
$montantTotalFinal = $montantTotal + $fraisTransport;
$facture->setMontantTotal($montantTotal)
->setType('Facture')
->setTva($tva)
->setFraisTransport($fraisTransport)
->setStatutPaiement($statutPaiement);
if ($client) {
$facture->setClient($client);
}
// Étape 7: Supprimer les anciennes échéances et recettes
foreach ($facture->getEcheance() as $ancienneEcheance) {
$entityManager->remove($ancienneEcheance);
}
foreach ($venteOriginale->getRecettes() as $ancienneRecette) {
$entityManager->remove($ancienneRecette);
}
// Étape 8: Traiter les nouveaux paiements
if ($statutPaiement === 'Payé') {
$facture->setMontantPaye($montantTotalFinal);
$entityManager->persist($facture);
$entityManager->flush();
$echeanceAcompte = new Echeance();
$echeanceAcompte->setFacture($facture)
->setAmount($montantTotalFinal)
->setDate(new DateTime())
->setIsPaid(true)
->setClient($client)
->setVente($venteOriginale)
->setType('acompte');
$entityManager->persist($echeanceAcompte);
$entityManager->flush();
$this->recetteService->creerRecetteDepuisVente($venteOriginale, $montantTotalFinal, $boutique);
} elseif ($statutPaiement === 'Partiellement payé') {
$facture->setMontantPaye($acompteDirect);
$entityManager->persist($facture);
$entityManager->flush();
if ($acompteDirect > 0) {
$echeanceAcompte = new Echeance();
$echeanceAcompte->setFacture($facture)
->setAmount($acompteDirect)
->setDate(new DateTime())
->setIsPaid(true)
->setClient($client)
->setVente($venteOriginale)
->setType('acompte');
$entityManager->persist($echeanceAcompte);
$this->recetteService->creerRecetteDepuisVente($venteOriginale, $acompteDirect, $boutique);
}
$resteAPayer = $montantTotalFinal - $acompteDirect;
$totalEcheances = 0;
foreach ($echeancesData as $echeanceData) {
$echeanceAmount = (float)$echeanceData['amount'];
$totalEcheances += $echeanceAmount;
$echeance = new Echeance();
$echeance->setAmount($echeanceAmount)
->setDate(new DateTime($echeanceData['date']))
->setIsPaid(false)
->setFacture($facture)
->setClient($client)
->setVente($venteOriginale)
->setType('echeance');
$entityManager->persist($echeance);
}
if ($resteAPayer > 0 && $totalEcheances < $resteAPayer) {
$echeance = new Echeance();
$echeance->setAmount($resteAPayer - $totalEcheances)
->setDate(new DateTime())
->setIsPaid(false)
->setFacture($facture)
->setClient($client)
->setVente($venteOriginale)
->setType('echeance');
$entityManager->persist($echeance);
}
} else {
$facture->setMontantPaye(0);
$entityManager->persist($facture);
$entityManager->flush();
}
$entityManager->flush();
$entityManager->commit();
// Nettoyer la session
$session->set('panier', []);
$session->remove('vente_en_modification');
$this->nettoyerSessionPanier($session);
return new JsonResponse([
'success' => true,
'message' => 'La vente a été modifiée avec succès.',
'factureId' => $facture->getId(),
'redirect' => $this->generateUrl('app_dashboard')
]);
} catch (\Exception $e) {
$entityManager->rollback();
$this->logger->error('Erreur lors de la modification de vente', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return new JsonResponse([
'success' => false,
'message' => 'Erreur lors de la modification : ' . $e->getMessage()
], 500);
}
}
/**
* Annule une vente originale en restaurant le stock
*/
private function annulerVenteOriginale(
Vente $vente,
EntityManagerInterface $entityManager,
ProduitStockRepository $produitStockRepository
): void {
$boutique = $vente->getBoutique();
// Restaurer le stock pour chaque produit de la vente originale
foreach ($vente->getDetailsVentes() as $detail) {
$produit = $detail->getProduit();
$quantiteARestaurer = $detail->getQuantiteVendu();
// Trouver le stock du produit pour cette boutique
$stock = $produitStockRepository->findOneBy(['produit' => $produit, 'boutique' => $boutique]);
if ($stock) {
$stock->setQuantite($stock->getQuantite() + $quantiteARestaurer);
$entityManager->persist($stock);
} else {
// Si le stock n'existe pas, le créer avec la quantité restaurée
$nouveauStock = new ProduitStock();
$nouveauStock->setProduit($produit)
->setBoutique($boutique)
->setQuantite($quantiteARestaurer);
$entityManager->persist($nouveauStock);
}
}
}
/**
* Nettoie toutes les données de session liées au panier
*/
private function nettoyerSessionPanier(SessionInterface $session): void
{
$session->remove('panier_client_id');
$session->remove('panier_tva');
$session->remove('panier_frais_transport');
$session->remove('panier_statut_paiement');
$session->remove('panier_montant_paye');
$session->remove('panier_echeances');
}
/**
* Route pour annuler complètement une modification en cours
*/
#[Route('/panier/annuler-modification', name: 'annuler_modification_ajax', methods: ['POST'])]
public function annulerModification(SessionInterface $session): JsonResponse
{
try {
$session->remove('panier');
$session->remove('vente_en_modification');
$this->nettoyerSessionPanier($session);
return new JsonResponse([
'success' => true,
'message' => 'Modification annulée avec succès.',
'redirect' => $this->generateUrl('app_vente_index')
]);
} catch (\Exception $e) {
return new JsonResponse([
'success' => false,
'message' => 'Erreur lors de l\'annulation : ' . $e->getMessage()
], 500);
}
}
/**
* Route pour vérifier si une vente peut être modifiée
*/
#[Route('/panier/peut-modifier-vente/{id}', name: 'peut_modifier_vente_ajax', methods: ['GET'])]
public function peutModifierVenteAjax(
int $id,
EntityManagerInterface $entityManager
): JsonResponse {
try {
$vente = $entityManager->getRepository(Vente::class)->find($id);
if (!$vente) {
return new JsonResponse(['success' => false, 'message' => 'Vente introuvable.'], 404);
}
$user = $this->getUser();
if ($vente->getBoutique() !== $user->getBoutique()) {
return new JsonResponse(['success' => false, 'message' => 'Vous n\'avez pas l\'autorisation.'], 403);
}
$peutModifier = $this->peutModifierVente($vente);
return new JsonResponse([
'success' => true,
'peutModifier' => $peutModifier,
'message' => $peutModifier ? 'Vente modifiable' : 'Cette vente ne peut plus être modifiée'
]);
} catch (\Exception $e) {
return new JsonResponse([
'success' => false,
'message' => 'Erreur lors de la vérification : ' . $e->getMessage()
], 500);
}
}
}