<?php
namespace App\Controller\Admin;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use App\Repository\AddressRepository;
use App\Repository\UserRepository;
use App\Repository\SupplierRepository;
use App\Form\FilterSupplierType;
use App\Form\SupplierType;
use App\Entity\Supplier;
use App\Entity\Document;
use App\Entity\User;
use App\Repository\DocumentDeclinationProduitRepository;
use App\Repository\DocumentRepository;
use App\Entity\Comment;
/**
* @Route("/supplier")
*/
class SupplierController extends AbstractController {
private $addressRepository;
private $userRepository;
public function __construct(
AddressRepository $addressRepository,
UserRepository $userRepository
) {
$this->addressRepository = $addressRepository;
$this->userRepository = $userRepository;
}
/**
* @Route("/supplier", name="supplier_index")
*/
public function index(): Response {
$form = $this->createForm(FilterSupplierType::class);
return $this->render('@admin/supplier/list_suppliers.html.twig', [
'form' => $form->createView()
]);
}
/**
* @Route("/search", name="search_supplier", methods="GET|POST", options = { "expose" = true})
*/
public function searchSupplier(Request $request, SupplierRepository $suppliers,EntityManagerInterface $em) {
$entities = $suppliers->search(0, 10, $request->get('query'), null);
foreach ($entities as $entity) {
$output[] = [
'id' => $entity->getId(),
'name' => $entity->getName(),
'namerasionsocial' => $entity->getRaisonSociale() ,
'phone' => $entity->getPhone(),
'email' => $entity->getEmail(),
'actions' => 'actions',
];
}
if( !$entities) {
$output = [];
}
return new JsonResponse($output);
}
/**
* @Route("/new", name="new_supplier", methods={"GET","POST"}, options={"expose"=true})
*/
public function new_supplier(
Request $request,
SupplierRepository $suppliers,
EntityManagerInterface $em
): Response {
$supplier = new Supplier();
// Formulaire
$form = $this->createForm(SupplierType::class, $supplier);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// Vérifs doublons (au submit uniquement)
$name = $request->request->get('name') ?? ($supplier->getName() ?: null);
$phone = $request->request->get('phone') ?? ($supplier->getPhone() ?: null);
if ($name && $suppliers->findOneBy(['name' => $name])) {
$this->addFlash('danger', "Référence que vous avez entrée existe déjà !");
return $this->render('@admin/supplier/_formAddEditSupplierModal.html.twig', [
'supplier' => $supplier,
'form' => $form->createView(),
'type' => 'new',
]);
}
if ($phone && $suppliers->findOneBy(['phone' => $phone])) {
$this->addFlash('danger', "Téléphone que vous avez entré existe déjà !");
return $this->render('@admin/supplier/_formAddEditSupplierModal.html.twig', [
'supplier' => $supplier,
'form' => $form->createView(),
'type' => 'new',
]);
}
// Upload du logo (champ non mappé "logoFile" dans SupplierType)
/** @var UploadedFile|null $file */
$file = $form->get('logoFile')->getData();
if ($file instanceof UploadedFile) {
$uploadDir = $this->getParameter('kernel.project_dir') . '/public/uploads/suppliers';
if (!is_dir($uploadDir)) { @mkdir($uploadDir, 0775, true); }
$safeName = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
$safeName = substr(preg_replace('/[^a-z0-9-_]/i', '-', $safeName), 0, 40);
$ext = $file->guessExtension() ?: 'png';
$newName = $safeName . '-' . uniqid() . '.' . $ext;
$file->move($uploadDir, $newName);
$supplier->setLogo($newName); // propriété string nullable dans l'entité Supplier
}
// Champs non mappés (si vos selects ne sont pas mappés dans le form)
$supplier->setCountry($request->request->get('supplier')['country'] ?? null);
$supplier->setRegion($request->request->get('supplier')['region'] ?? null);
// Métadonnées
$supplier->setCreatedAt(new \DateTime());
// Persist
$em->persist($supplier);
$em->flush();
$this->addFlash('success', "Fournisseur ajouté avec succès");
return $this->redirectToRoute('supplier_show', ['id' => $supplier->getId()]);
}
return $this->render('@admin/supplier/_formAddEditSupplierModal.html.twig', [
'supplier' => $supplier,
'form' => $form->createView(),
'type' => 'new',
]);
}
/**
* @Route("/edit/{id}/{document}", name="edit_supplier", methods={"GET","POST"}, options={"expose"=true}, defaults={"document"=null})
*/
public function edit_supplier(Request $request, SupplierRepository $suppliers, Supplier $supplier, $document = null,EntityManagerInterface $em): Response {
$existName = $suppliers->findOneBy(array('name' => $request->get('name')));
if( $existName) $request->getSession()->getFlashBag()->add('danger', "Référence que vous avez entré existe déjà!");
$existPhone = $suppliers->findOneBy(array('phone' => $request->get('phone')));
if( $existPhone) $request->getSession()->getFlashBag()->add('danger', "Téléphone que vous avez entré existe déjà!");
$form = $this->createForm(SupplierType::class, $supplier);
$form->handleRequest($request);
//dd($form->getData()->getRegion());
if( $form->isSubmitted()) {
$supplier->setCountry($request->request->get('supplier')['country'] ?? null);
$supplier->setRegion($request->request->get('supplier')['region'] ?? null);
$em->flush();
$request->getSession()->getFlashBag()->add('success', "Fournisseur modifié avec succès");
if( $document)
return $this->redirect($this->generateUrl('document_show', ['id' => $document]));
else
return $this->redirect($this->generateUrl('supplier_show', ['id' => $supplier->getId()]));
}
return $this->render('@admin/supplier/_formAddEditSupplierModal.html.twig', [
'supplier' => $supplier,
'form' => $form->createView(),
'document' => $document,
'type' => 'edit'
]);
}
/**
* @Route("/show/{id}", name="supplier_show", methods={"GET"}, options = { "expose" = true})
*/
public function show_supplier(Supplier $supplier, EntityManagerInterface $em,documentRepository $documentRepository, DocumentDeclinationProduitRepository $documentDeclinationProduitRepository): Response
{
$documents = $supplier->getDocuments();
// Compter les types de documents
$nbAchat = $documents->filter(fn($d) => $d->getType() === 'achat')->count();
$nbReception = $documents->filter(fn($d) => $d->getType() === 'reception')->count();
$nbFacture = $documents->filter(fn($d) => $d->getType() === 'facture')->count();
$nbRetour = $documents->filter(fn($d) => $d->getType() === 'retour')->count();
// Total achats TTC (sans frais de livraison)
$totalAchat = $documents->filter(fn($d) =>
$d->getType() === 'reception' &&
in_array($d->getStatus(), ['brouillon', 'valide', 'paye'])
)->map(fn($d) => $d->getTotalAmountTtc() - $d->getDeliveryTotal())
->reduce(fn($carry, $item) => $carry + $item, 0);
$totalVente = $documentDeclinationProduitRepository->getTotalVenteByFournisseur($supplier);
$lastReceptionDate = $documentRepository->getLastDocumentDateByType($supplier, 'reception');
$lastFactureDate = $documentRepository->getLastDocumentDateByType($supplier, 'facture');
$daysSinceLastReception = $lastReceptionDate ? (new \DateTime())->diff($lastReceptionDate)->days : null;
$daysSinceLastFacture = $lastFactureDate ? (new \DateTime())->diff($lastFactureDate)->days : null;
$receptionColorClass = $daysSinceLastReception !== null && $daysSinceLastReception > 30 ? 'text-danger' : 'text-success';
$factureColorClass = $daysSinceLastFacture !== null && $daysSinceLastFacture > 30 ? 'text-danger' : 'text-success';
$statistiques = compact('nbAchat', 'nbReception', 'nbFacture', 'nbRetour', 'totalAchat', 'totalVente');
return $this->render('@admin/supplier/fiche_supplier.html.twig', [
'user' => $supplier,
'statistiques' => $statistiques,
'lastReceptionDate' => $lastReceptionDate, // corrigé (espace supprimé)
'lastFactureDate' => $lastFactureDate,
'daysSinceLastReception' => $daysSinceLastReception,
'daysSinceLastFacture' => $daysSinceLastFacture,
'receptionColorClass' => $receptionColorClass,
'factureColorClass' => $factureColorClass,
]);
}
/**
* @Route("/list", name="list_supplier", methods="GET|POST", options = { "expose" = true })
*/
public function listData_supplier(Request $request, SupplierRepository $supplierRepository, DocumentRepository $documentRepository): JsonResponse
{
$pagination = $request->get('pagination');
$page = $pagination['page'] - 1;
$limit = $pagination['perpage'];
$filters = [
'name' => $request->get('name'),
'phone' => $request->get('phone'),
'email' => $request->get('email'),
'type' => $request->get('type'),
'dateBefore' => $request->get('dateBefore'),
'dateAfter' => $request->get('dateAfter'),
];
$entities = $supplierRepository->search($page, $limit, ...array_values($filters));
$count = $supplierRepository->countSupplier(...array_values($filters));
$output = [
'data' => [],
'meta' => [
'page' => $pagination['page'],
'perpage' => $limit,
'pages' => ceil($count / $limit),
'total' => $count,
]
];
foreach ($entities as $entity) {
$nbAchats = $documentRepository->countDocumentsBySupplier($entity, 'achat');
$nbReceptions = $documentRepository->countDocumentsBySupplier($entity, 'reception');
$nbFactures = $documentRepository->countDocumentsBySupplier($entity, 'facture');
$nbRetours = $documentRepository->countDocumentsBySupplier($entity, 'retour');
$totalAchat = $documentRepository->getTotalAchatByFournisseur($entity);
$lastReceptionDate = $documentRepository->getLastDocumentDateByType($entity, 'reception');
$output['data'][] = [
'id' => $entity->getId(),
'name' => $entity->getName(),
'raisonSociale' => $entity->getRaisonSociale(),
'civility' => $entity->getCivility(),
'name_responsable' => $entity->getNameResponsable(),
'phone_responsible1' => $entity->getPhoneResponsible1(),
'phone_responsible2' => $entity->getPhoneResponsible2(),
'adress' => $entity->getAdress(),
'type' => $entity->getType(),
'isActive' => $entity->isActive(),
'phone' => $entity->getPhone() ?: 'Pas mentionné',
'email' => $entity->getEmail() ?: 'Pas mentionné',
'createdAt' => $entity->getCreatedAt()->format('d/m/Y'),
'nbAchat' => $nbAchats,
'nbReception' => $nbReceptions,
'nbFacture' => $nbFactures,
'nbRetour' => $nbRetours,
'totalAchat' => $totalAchat,
'dateLastReception' => $lastReceptionDate ? $lastReceptionDate->format('d/m/Y') : null,
];
}
return new JsonResponse($output);
}
//suppression d'un fournisseur
/**
* @Route("/admin/fournisseurs/{id}/delete", name="supplier_delete", methods={"POST"}, options={"expose"=true})
*/
public function delete_supplier(Request $request, Supplier $supplier, EntityManagerInterface $em): JsonResponse
{
if ($supplier->getDocuments()->count() > 0) {
return new JsonResponse([
'success' => false,
'message' => 'Ce fournisseur a des documents associés et ne peut pas être supprimé !'
], 400);
}
$em->remove($supplier);
$em->flush();
return new JsonResponse(['success' => true]);
}
/**
* @Route("/admin/fournisseur/{id}/new_comment", name="supplier_comment_new", methods={"POST"})
*/
public function newComment(Request $request, Supplier $supplier, EntityManagerInterface $em): Response
{
if ($request->isMethod('POST') && $request->request->get('form_comment')) {
$comment = new Comment();
$comment->setDescription($request->request->get('form_comment')['comment']);
$comment->setUser($this->getUser());
$comment->setCreateAt(new \DateTime());
$comment->setSupplier($supplier); // lien vers le fournisseur
$em->persist($comment);
$em->flush();
return $this->redirectToRoute("supplier_show", ['id' => $supplier->getId(), "tab" => "comments"]);
}
throw $this->createNotFoundException("Commentaire non soumis.");
}
/**
* @Route("/admin/fournisseur/{id}/edit_comment/{comment}", name="supplier_comment_edit", methods={"POST"})
*/
public function editComment(Request $request, Supplier $supplier, Comment $comment, EntityManagerInterface $em): Response
{
if ($request->isMethod('POST') && $request->request->get('form_comment')) {
$comment->setDescription($request->request->get('form_comment')['comment']);
$comment->setUser($this->getUser());
$comment->setCreateAt(new \DateTime());
$em->flush();
return $this->redirectToRoute("supplier_show", ['id' => $supplier->getId(), "tab" => "comments"]);
}
throw $this->createNotFoundException("Commentaire non soumis.");
}
/**
* @Route("/fournisseur/{id}/statistiques/premiere-annee", name="supplier_premiere_annee", methods={"GET"})
*/
public function getPremiereAnneeReception(Supplier $supplier, DocumentRepository $docRepo): JsonResponse
{
$annee = $docRepo->getPremiereAnneeBonReception($supplier->getId());
if (!$annee) {
return $this->json(['annee' => date('Y')]); // fallback si aucun bon
}
return $this->json(['annee' => $annee]);
}
/**
* @Route("/fournisseur/{id}/statistiques/achat-par-annee", name="supplier_achat_par_annee", methods={"GET"})
*/
public function achatParAnnee(Supplier $supplier, Request $request, DocumentRepository $docRepo): JsonResponse
{
$startYear = $request->query->get('startYear');
$endYear = $request->query->get('endYear');
$type = $request->query->get('type', 'reception');
$mode = $request->query->get('mode', 'montant');
$dataset = $request->query->get('dataset', 'achat'); // ← Nouveau
if ($dataset === 'achat') {
$data = $docRepo->getTotalAchatsParAnnee($supplier->getId(), $startYear, $endYear, $type, $mode);
return $this->json($data);
}
if ($dataset === 'vente') {
$data = $docRepo->getTotalVentesParAnnee($supplier->getId(), $startYear, $endYear, $mode); // ← à créer
return $this->json($data);
}
if ($dataset === 'les_deux') {
$achats = $docRepo->getTotalAchatsParAnnee($supplier->getId(), $startYear, $endYear, $type, $mode);
$ventes = $docRepo->getTotalVentesParAnnee($supplier->getId(), $startYear, $endYear, $mode);
// Fusionner les deux par année
$merged = [];
foreach ($achats as $a) {
$merged[$a['year']] = ['year' => $a['year'], 'achat' => $a['total'], 'vente' => 0];
}
foreach ($ventes as $v) {
if (!isset($merged[$v['year']])) {
$merged[$v['year']] = ['year' => $v['year'], 'achat' => 0, 'vente' => $v['total']];
} else {
$merged[$v['year']]['vente'] = $v['total'];
}
}
// Réordonner par année
ksort($merged);
return $this->json(array_values($merged));
}
return $this->json(['error' => 'Invalid dataset'], 400);
}
/**
* @Route("/fournisseur/{id}/statistiques/achat-par-mois", name="supplier_achat_par_mois", methods={"GET"})
*/
public function achatParMois( Supplier $supplier,Request $request,DocumentRepository $docRepo): JsonResponse {
$year = $request->query->get('year');
$type = $request->query->get('type', 'reception'); // par défaut réception
$mode = $request->query->get('mode', 'montant'); // par défaut montant
$data = $docRepo->getTotalAchatParMois($supplier->getId(), $year, $type, $mode);
return $this->json($data);
}
/**
* @Route("/fournisseur/{id}/statistiques/evolution-mensuelle", name="supplier_evolution_mensuelle", methods={"GET"})
*/
public function evolutionAchatMensuelle(Supplier $supplier, Request $request, DocumentRepository $docRepo): JsonResponse
{
$type = $request->query->get('type', 'reception');
$data = $docRepo->getEvolutionAchatMensuelle($supplier->getId(),$type);
return $this->json($data);
}
/**
* @Route("/fournisseur/{id}/statistiques/montant-moyen", name="supplier_montant_moyen", methods={"GET"})
*/
public function montantMoyenParAnnee(Supplier $supplier, Request $request, DocumentRepository $docRepo): JsonResponse
{
$type = $request->query->get('type');
$data = $docRepo->getMontantMoyenParAnnee($supplier->getId(), $type);
return $this->json($data);
}
/**
* @Route("/fournisseur/{id}/statistiques/repartition-paiement", name="supplier_repartition_paiement", methods={"GET"})
*/
public function repartitionPaiement(Supplier $supplier, Request $request, DocumentRepository $docRepo): JsonResponse
{
$type = $request->query->get('type', 'reception');
$year = $request->query->get('year');
$data = $docRepo->getPaiementRepartition($supplier->getId(), $type, $year);
return $this->json($data);
}
/**
* @Route("/fournisseur/{id}/statistiques/achat-par-categorie", name="supplier_achat_par_categorie", methods={"GET"})
*/
public function achatParCategorie(Supplier $supplier,Request $request, DocumentRepository $documentRepository): JsonResponse {
$year = $request->query->get('year');
$type = $request->query->get('type', 'reception');
$mode = $request->query->get('mode', 'montant'); // 'montant' ou 'quantite'
$result = $documentRepository->getAchatParCategorie($supplier->getId(), $year, $type, $mode);
return $this->json($result);
}
}