src/Controller/DashboardController.php line 16

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  4. use Symfony\Component\HttpFoundation\Response;
  5. use Symfony\Component\Routing\Annotation\Route;
  6. use App\Repository\ProduitRepository;
  7. use App\Repository\VenteRepository;
  8. use App\Repository\DepenseRepository;
  9. class DashboardController extends AbstractController
  10. {
  11.     /**
  12.      * @Route("/", name="app_dashboard")
  13.      */
  14.     public function index(
  15.         VenteRepository $venteRepo,
  16.         ProduitRepository $produitRepo,
  17.         DepenseRepository $depenseRepo
  18.     ): Response {
  19.         $this->denyAccessUnlessGranted('VOIR_DASHBOARD');
  20.         // --- KPIs VENTES / STOCK ---
  21.         $ventesMois $venteRepo->sumThisMonth();
  22.         $margeMois  $venteRepo->margeThisMonth();
  23.         $kpis = [
  24.             'ventesJour'        => $venteRepo->sumToday(),
  25.             'ventesMois'        => $ventesMois,
  26.             'margeJour'         => $venteRepo->margeToday(),
  27.             'margeMois'         => $margeMois,
  28.             'tauxMargeMois'     => $ventesMois ? ($margeMois $ventesMois) * 100 0,
  29.             'produitsCritiques' => $produitRepo->countCritiques(),
  30.             'valeurStock'       => $produitRepo->valeurStock(),
  31.         ];
  32.         // Rotation sur 30 jours — méthode COGS (normes gestion de stock)
  33.         // Taux = COGS / Stock moyen au prix d'achat
  34.         // Stock moyen = (Stock début + Stock fin) / 2
  35.         // Stock début estimé = Stock fin + COGS  →  Stock moyen = Stock fin + COGS / 2
  36.         $fromRot        = new \DateTime('-30 days');
  37.         $toRot          = new \DateTime();
  38.         $cogs30         $venteRepo->coutVentesBetween($fromRot$toRot);
  39.         $stockFinValeur $produitRepo->valeurStockAuPrixAchat();
  40.         $stockMoyen     $stockFinValeur + ($cogs30 2.0);
  41.         $rotation       = ($stockMoyen 0) ? $cogs30 $stockMoyen 0;
  42.         $delaiRotation  = ($rotation 0) ? round(30 $rotation) : 0;
  43.         $kpis['rotationStock30j'] = $rotation;
  44.         $kpis['delaiRotation30j'] = $delaiRotation;
  45.         // --- KPIs DÉPENSES ---
  46.         $kpis['depensesJour'] = $depenseRepo->sumToday();
  47.         $kpis['depensesMois'] = $depenseRepo->sumThisMonth();
  48.         // Résultats (très simple : ventes - dépenses)
  49.         $kpis['resultatJour'] = $kpis['ventesJour'] - $kpis['depensesJour'];
  50.         $kpis['resultatMois'] = $kpis['ventesMois'] - $kpis['depensesMois'];
  51.         // --- Graphiques ventes ---
  52.         $graphDailySales    $venteRepo->dailySalesLast30Days();
  53.         $graphDailyMargins  $venteRepo->dailyMarginsLast30Days();
  54.         $monthlySales       $venteRepo->monthlySalesLast12Months();
  55.         $forecastMonthly    $this->forecastNextMonths($monthlySales3);
  56.         // --- Graphiques dépenses ---
  57.         $graphDailyExpenses $depenseRepo->dailyExpensesLast30Days();
  58.         $monthlyExpenses    $depenseRepo->monthlyExpensesLast12Months();
  59.         $expenseByType      $depenseRepo->sumByType(nullnull);
  60.         // --- Combinaison ventes / dépenses mensuelles ---
  61.         $combinedMonthly $this->mergeMonthlySalesAndExpenses($monthlySales$monthlyExpenses);
  62.         // --- Tops produits ---
  63.         $topQty    $venteRepo->topProductsByQuantity(10);
  64.         $topMargin $venteRepo->topProductsByMargin(10);
  65.         // --- Performance par catégorie (30 jours) ---
  66.         $perfCategories $venteRepo->performanceByCategory($fromRot$toRot);
  67.         return $this->render('dashboard/index.html.twig', [
  68.             'kpis'             => $kpis,
  69.             'graphDailySales'  => $graphDailySales,
  70.             'graphDailyMargins'=> $graphDailyMargins,
  71.             'graphDailyExpenses' => $graphDailyExpenses,
  72.             'monthlySales'     => $monthlySales,
  73.             'monthlyExpenses'  => $monthlyExpenses,
  74.             'combinedMonthly'  => $combinedMonthly,
  75.             'forecastMonthly'  => $forecastMonthly,
  76.             'topQty'           => $topQty,
  77.             'topMargin'        => $topMargin,
  78.             'perfCategories'   => $perfCategories,
  79.             'expenseByType'    => $expenseByType,
  80.         ]);
  81.     }
  82.     /**
  83.      * Prévision simple : moyenne des 3 derniers mois pour les N prochains.
  84.      *
  85.      * @param array $monthlySales  tableau issu de monthlySalesLast12Months()
  86.      * @param int   $nb            nombre de mois à prévoir
  87.      */
  88.     private function forecastNextMonths(array $monthlySalesint $nb): array
  89.     {
  90.         if (empty($monthlySales)) {
  91.             return [];
  92.         }
  93.         // Exclure le mois en cours (incomplet — fausserait la moyenne)
  94.         $currentMonth = (new \DateTime())->format('Y-m');
  95.         $completed array_values(array_filter($monthlySales, function ($row) use ($currentMonth) {
  96.             $key = ($row['mois'] instanceof \DateTimeInterface)
  97.                 ? $row['mois']->format('Y-m')
  98.                 : substr((string) $row['mois'], 07);
  99.             return $key $currentMonth;
  100.         }));
  101.         if (empty($completed)) {
  102.             return [];
  103.         }
  104.         // Moyenne glissante sur les 3 derniers mois complets
  105.         $last  array_slice($completed, -3);
  106.         $count count($last);
  107.         $totals array_map(fn($r) => (float) ($r['total'] ?? 0), $last);
  108.         $avg   array_sum($totals) / $count;
  109.         // Tendance linéaire simple (pente moyenne entre les mois utilisés)
  110.         $trend $count >= ? ($totals[$count 1] - $totals[0]) / ($count 1) : 0;
  111.         // Les prévisions démarrent toujours au mois suivant le mois actuel
  112.         $dt = new \DateTime('first day of next month');
  113.         $forecasts = [];
  114.         for ($i 0$i $nb$i++) {
  115.             $future = (clone $dt)->modify('+' $i ' month');
  116.             $forecasts[] = [
  117.                 'mois'  => new \DateTime($future->format('Y-m-01')),
  118.                 'total' => max(0.0$avg $trend * ($i 1)),
  119.                 'base'  => $count,
  120.                 'trend' => $trend,
  121.             ];
  122.         }
  123.         return $forecasts;
  124.     }
  125.     /**
  126.      * Fusionne les séries mensuelles ventes / dépenses sur une même base "YYYY-MM"
  127.      */
  128.     private function mergeMonthlySalesAndExpenses(array $sales, array $expenses): array
  129.     {
  130.         $map = [];
  131.         // Ventes
  132.         foreach ($sales as $row) {
  133.             $rawMonth = isset($row['mois']) ? $row['mois'] : null;
  134.             if ($rawMonth instanceof \DateTimeInterface) {
  135.                 $key $rawMonth->format('Y-m');
  136.             } else {
  137.                 $key = (string) $rawMonth;
  138.             }
  139.             if (!isset($map[$key])) {
  140.                 $map[$key] = ['mois' => $key'ventes' => 0.0'depenses' => 0.0];
  141.             }
  142.             $map[$key]['ventes'] = isset($row['total']) ? (float) $row['total'] : 0.0;
  143.         }
  144.         // Dépenses
  145.         foreach ($expenses as $row) {
  146.             $rawMonth = isset($row['mois']) ? $row['mois'] : null;
  147.             if ($rawMonth instanceof \DateTimeInterface) {
  148.                 $key $rawMonth->format('Y-m');
  149.             } else {
  150.                 $key = (string) $rawMonth;
  151.             }
  152.             if (!isset($map[$key])) {
  153.                 $map[$key] = ['mois' => $key'ventes' => 0.0'depenses' => 0.0];
  154.             }
  155.             $map[$key]['depenses'] = isset($row['total']) ? (float) $row['total'] : 0.0;
  156.         }
  157.         ksort($map);
  158.         return array_values($map);
  159.     }
  160. }