<?php
namespace App\Controller\Api\History;
use App\Entity\User;
use App\Repository\EtatProductionRepository;
use App\Repository\HistoryRepository;
use App\Service\HierarchyService;
use App\Repository\UserRepository;
use App\Repository\PointOfSaleRepository;
use App\Repository\SatisfactionClientRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Doctrine\ORM\EntityManagerInterface;
class HistoryAnalyticsController extends AbstractController
{
public function __invoke(
Request $request,
EtatProductionRepository $etatProductionRepository,
HistoryRepository $historyRepository,
PointOfSaleRepository $pointOfSaleRepository,
SatisfactionClientRepository $satisfactionClientRepository,
UserRepository $userRepository,
EntityManagerInterface $entityManager,
HierarchyService $hierarchyService
) {
$pointOfSaleId = $request->query->get('pointOfSaleId', null);
$pointsOfSale = [];
if ($pointOfSaleId !== null) {
$organisationId = null;
$pointOfSale = $pointOfSaleRepository->findOneBy(["id" => $pointOfSaleId]);
if ($pointOfSale === null) {
throw new BadRequestHttpException('Le point de vente avec cet ID n\'existe pas.');
}
$pointsOfSale = [$pointOfSale];
} else {
$organisationId = $request->query->get('organisationId', null);
$pointsOfSale = $pointOfSaleRepository->findAll();
}
$codeCluster = $request->query->get('codeCluster', null);
$codeInsee = $request->query->get('codeInsee', null);
$optionSelect = $request->query->get('optionSelect', 'V');
$category = $request->query->get('categoryId');
$nbrMonth = (int) $request->query->get('nbrMonth', 1);
$currentYear = (int) date('Y');
$currentMonth = (int) date('m');
$queryMonth = $request->query->get('month');
$queryYear = $request->query->get('year');
$perid = $request->query->get('perid');
$sellerId = $request->query->get('sellerId');
$departement = $request->query->get('departement');
$childs = [];
$user = null;
if ($request->query->get('idUser') != null) {
$user = $entityManager->getRepository(User::class)->find((int)$request->query->get('idUser'));
if (!$user) {
throw new BadRequestHttpException('L\'utilisateur avec cet ID n\'existe pas.');
}
// Récupérer l'hierarchie descendante
if (in_array('ROLE_MANAGER', $user->getRoles()) || in_array('ROLE_DIRECTOR', $user->getRoles())) {
$childs = array_map(fn($p) => (int) $p['id'], $hierarchyService->getHierarchyDescendante($user->getId()));
}
}
$baseMonth = $queryMonth !== null ? (int) $queryMonth : $currentMonth;
$baseYear = $queryYear !== null ? (int) $queryYear : $currentYear;
$etatKO = $etatProductionRepository->findOneBy(["nom" => 'Racco KO']);
$etatRaccorde = $etatProductionRepository->findOneBy(["nom" => 'Raccorde']);
$response = [];
for ($i = 0; $i < $nbrMonth; $i++) {
$date = (new \DateTimeImmutable("$baseYear-$baseMonth-01"))->modify("-$i months");
$mois = (int) $date->format('m');
$annee = (int) $date->format('Y');
// Initialisation des agrégats
$totalVentes = 0;
$totalSatisfaction = 0.0;
$nombreVendeurs = 0;
$etp = 0;
$totalVentesKO = 0;
$totalObjectif = 0;
$projection = 0;
$jours_restants = 0;
$moyenneJournaliere = 0;
$clients4P = 0;
$clients4PC = 0;
$j30 = 0;
$j30_mobile = 0;
$titulaire_mobile = 0;
$titulaire_mobile_chainage = 0;
$clients_hors_mig = 0;
$total_clients_unique = 0;
$total_ventes_raccordes_fixe_conquete = 0;
$total_ventes_raccordes_fixe = 0;
$total_ventes_raccordes_mobile = 0;
$j30_raccorde_fixe_conquete = 0;
$j30_raccorde_fixe = 0;
$j30_raccorde_mobile = 0;
$rio = 0;
$pto_non_saisie = 0;
$pto_saisie = 0;
$pto_non_existante = 0;
$pto_existante = 0;
$total_vente_vla_conquette = 0;
$total_ventes_box_5G = 0;
$mrz = [];
$clients_4PC_par_semaine = [];
$clients_4P_par_semaine = [];
foreach ($pointsOfSale as $pointOfSale) {
$satisfactionData = $satisfactionClientRepository->getSatisfactionClientAverageForSpecificMonthAndYearForHistory(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
$category,
$childs,
$organisationId,
$perid,
$sellerId,
$departement
);
$note = !empty($satisfactionData) && isset($satisfactionData[0]['moyenneSatisfaction'])
? (float) $satisfactionData[0]['moyenneSatisfaction'] : 0.0;
$totalSatisfaction += $note;
$totalVentes += $historyRepository->getProductionsAnalyticsVentes(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
$optionSelect,
$etatKO,
$category,
$childs,
$organisationId,
$perid,
$sellerId,
$departement
)[0]['total_ventes'] ?? 0;
$total_ventes_box_5G += $historyRepository->getProductionsAnalyticsVentesBox5G(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
"V",
$etatKO,
$category,
$childs,
$organisationId,
$perid,
$sellerId,
$departement
)[0]['total_ventes'] ?? 0;
$total_ventes_raccordes_fixe_conquete += $historyRepository->getProductionsAnalyticsVentesForChurn(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
'B',
"Raccorde",
null,
$childs,
$organisationId,
$perid,
$sellerId,
$departement
)[0]['total_ventes'] ?? 0;
$total_ventes_raccordes_fixe += $historyRepository->getProductionsAnalyticsVentes(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
'B',
"Raccorde",
null,
$childs,
$organisationId,
$perid,
$sellerId,
$departement
)[0]['total_ventes'] ?? 0;
$total_ventes_raccordes_mobile += $historyRepository->getProductionsAnalyticsVentes(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
'B',
"Raccorde_mobile",
"31,32,33",
$childs,
$organisationId,
$perid,
$sellerId,
$departement
)[0]['total_ventes'] ?? 0;
$nombreVendeurs += $historyRepository->getProductionsAnalyticsVendeurs(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
$optionSelect,
$etatKO,
$category,
$childs,
$organisationId,
$perid,
$sellerId,
$departement
)[0]['nombre_vendeurs'] ?? 0;
$etp += (int)($historyRepository->getProductionsAnalyticsETP(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
$optionSelect,
$etatKO,
$category,
$childs,
$organisationId,
$perid,
$sellerId,
$departement
)[0]['etp'] ?? 0);
$totalVentesKO += $historyRepository->getProductionsAnalyticsKO(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
$optionSelect,
$etatKO,
$category,
$childs,
$organisationId,
$perid,
$sellerId,
$departement
)[0]['total_ventes_ko'] ?? 0;
$totalObjectif += (int)($historyRepository->getProductionsAnalyticsObjectifs(
$pointOfSale,
$codeCluster,
$mois,
$annee,
$optionSelect,
$etatKO,
$category,
$childs,
$organisationId,
$departement
)[0]['total_objectif'] ?? 0);
$projection += (int)$historyRepository->getProductionsAnalyticsProjection(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
$optionSelect,
$etatKO,
$category,
$childs,
$organisationId,
$perid,
$sellerId,
$departement
)['projection_ventes'] ?? 0;
$moyenneJournaliere += (float)$historyRepository->getProductionsAnalyticsProjection(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
$optionSelect,
$etatKO,
$category,
$childs,
$organisationId,
$perid,
$sellerId,
$departement
)['ventes_par_jour'] ?? 0;
$jours_restants += (float)$historyRepository->getProductionsAnalyticsProjection(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
$optionSelect,
$etatKO,
$category,
$childs,
$organisationId,
$perid,
$sellerId,
$departement
)['jours_restants'] ?? 0;
$clients4P += (int)$historyRepository->getProductionsAnalyticsTotal4P(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
$optionSelect,
$etatKO,
"",
$childs,
$organisationId,
$perid,
$sellerId,
$departement
)['total_vente'] ?? 0;
$clients4PC += (int)$historyRepository->getProductionsAnalyticsTitulaireVentes(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
$optionSelect,
$etatKO,
null,
"chainage",
false,
$childs,
$organisationId,
$perid,
$sellerId,
$departement
)[0]['nombre_titulaire_email'] ?? 0;
$j30 += (int)$historyRepository->getProductionsAnalyticsTotalVentesByDateResiliationInf30(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
$optionSelect,
$etatKO,
$category,
$childs,
$organisationId,
$perid,
$sellerId,
$departement
)['total_vente'] ?? 0;
$j30_raccorde_fixe += (int)$historyRepository->getProductionsAnalyticsTotalVentesByDateResiliationInf30(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
"B",
$etatKO,
"1,3",
$childs,
$organisationId,
$perid,
$sellerId,
$departement
)['total_vente'] ?? 0;
$j30_raccorde_fixe_conquete += (int)$historyRepository->getProductionsAnalyticsTotalVentesByDateResiliationInf30ForChurn(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
"B",
$etatKO,
"1,3",
$childs,
$organisationId,
$perid,
$sellerId,
$departement
)['total_vente'] ?? 0;
$j30_mobile += (int)$historyRepository->getProductionsAnalyticsTotalVentesByDateResiliationInf30Mobile(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
"B",
$etatKO,
"31,32,33",
$childs,
$organisationId,
$perid,
$sellerId,
$departement
)['total_vente'] ?? 0;
$j30_raccorde_mobile += (int)$historyRepository->getProductionsAnalyticsTotalVentesByDateResiliationInf30Mobile(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
"B",
"Raccorde_mobile",
"31,32,33,48",
$childs,
$organisationId,
$perid,
$sellerId,
$departement
)['total_vente'] ?? 0;
$titulaire_mobile += (int)$historyRepository->getProductionsAnalyticsTotalMobileV2(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
$optionSelect,
$etatKO,
"",
$childs,
$organisationId,
$perid,
$sellerId,
$departement
)['total_vente'] ?? 0;
$titulaire_mobile_chainage += (int)$historyRepository->getProductionsAnalyticsTotalMobileChainage(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
$optionSelect,
$etatKO,
"chainage",
$childs,
$organisationId,
$perid,
$sellerId,
$departement
)['total_vente'] ?? 0;
$clients_hors_mig += (int)$historyRepository->getProductionsAnalyticsTotalClientsHosting(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
$optionSelect,
"",
$childs,
$organisationId,
$perid,
$sellerId,
$departement
)['total_vente'] ?? 0;
$mrz[] = $historyRepository->getProductionsAnalyticsTotalByIdentityCtrl(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
$optionSelect,
"",
$childs,
$organisationId,
$perid,
$sellerId,
$departement
);
$total_clients_unique += (int)$historyRepository->getProductionsAnalyticsTotalClientsUnique(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
$optionSelect,
$etatKO,
$category,
"",
$childs,
$organisationId,
$perid,
$sellerId,
$departement
)[0]['total_client_unique'] ?? 0;
$clients_4PC_par_semaine[] = $historyRepository->get4PCByWeek(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
$optionSelect,
$etatKO,
"31,32,33",
"chainage",
true,
$childs,
$organisationId,
$perid,
$sellerId,
$departement
);
$clients_4P_par_semaine[] = $historyRepository->get4PByWeek(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
$optionSelect,
$etatKO,
"",
$childs,
$organisationId,
$perid,
$sellerId,
$departement
);
$rio += (int)$historyRepository->getProductionsAnalyticsTotalVenteByOption(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
$childs,
$organisationId,
"RIO",
$perid,
$sellerId,
$departement
)['total_vente'] ?? 0;
$pto_non_saisie += (int)$historyRepository->getProductionsAnalyticsTotalVenteByOption(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
$childs,
$organisationId,
"PTO_NON_SAISIE",
$perid,
$sellerId,
$departement
)['total_vente'] ?? 0;
$pto_saisie += (int)$historyRepository->getProductionsAnalyticsTotalVenteByOption(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
$childs,
$organisationId,
"PTO_SAISIE",
$perid,
$sellerId,
$departement
)['total_vente'] ?? 0;
$pto_non_existante += (int)$historyRepository->getProductionsAnalyticsTotalVenteByOption(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
$childs,
$organisationId,
"PTO_NON_EXISTANTE",
$perid,
$sellerId,
$departement
)['total_vente'] ?? 0;
$pto_existante += (int)$historyRepository->getProductionsAnalyticsTotalVenteByOption(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
$childs,
$organisationId,
"PTO_EXISTANTE",
$perid,
$sellerId,
$departement
)['total_vente'] ?? 0;
$total_vente_vla_conquette += $historyRepository->getProductionsAnalyticsVentesVlaConquete(
$pointOfSale,
$codeCluster,
$codeInsee,
$mois,
$annee,
"B",
"1,3",
$childs,
$organisationId,
$perid,
$sellerId,
$departement
)[0]['total_ventes'] ?? 0;
$total_churn_fixe = 0;
$total_churn_mobile = 0;
$total_rio = 0;
$total_pto_saisie = 0;
$total_pto_non_saisie = 0;
$total_pto_non_existante = 0;
$total_potentiel = 0;
$total_pto_existante = 0;
if ($total_ventes_raccordes_fixe_conquete > 0 && $j30_raccorde_fixe_conquete > 0) {
$ratio_fixe = ($j30_raccorde_fixe_conquete / $total_ventes_raccordes_fixe_conquete) * 100;
$total_churn_fixe = number_format($ratio_fixe, 1);
}
if ($total_ventes_raccordes_mobile > 0 && $j30_raccorde_mobile > 0) {
$ratio_mobile = ($j30_raccorde_mobile / $total_ventes_raccordes_mobile) * 100;
$total_churn_mobile = number_format($ratio_mobile, 1);
}
if ($total_vente_vla_conquette > 0 && $rio > 0) {
$ratio_rio = ($rio / $total_vente_vla_conquette) * 100;
$total_rio = round($ratio_rio, 2);
}
if ($total_vente_vla_conquette > 0 && $pto_saisie > 0) {
$ratio_pto_saisie = ($pto_saisie / $total_vente_vla_conquette) * 100;
$total_pto_saisie = round($ratio_pto_saisie, 2);
}
if ($total_vente_vla_conquette > 0 && $pto_non_saisie > 0) {
$ratio_pto_non_saisie = ($pto_non_saisie / $total_vente_vla_conquette) * 100;
$total_pto_non_saisie = round($ratio_pto_non_saisie, 2);
}
if ($total_vente_vla_conquette > 0 && $pto_non_existante > 0) {
$ratio_pto_non_existante = ($pto_non_existante / $total_vente_vla_conquette) * 100;
$total_pto_non_existante = round($ratio_pto_non_existante, 2);
}
if ($pto_saisie > 0 && $pto_existante > 0) {
$ratio_potentiel = ($pto_saisie / $pto_existante) * 100;
$total_potentiel = round($ratio_potentiel, 2);
}
if ($total_vente_vla_conquette > 0 && $pto_existante > 0) {
$ratio_pto_existante = ($pto_existante / $total_vente_vla_conquette) * 100;
$total_pto_existante = round($ratio_pto_existante, 2);
}
}
$nbPos = count($pointsOfSale);
$response[$mois] = [
'total_ventes' => $totalVentes,
'note_satisfaction' => round($nbPos ? $totalSatisfaction / $nbPos : 0.0, 2),
'nombre_vendeurs' => $nombreVendeurs,
'etp' => $etp,
'total_ventes_ko' => $totalVentesKO,
'total_objectif' => $totalObjectif,
'projection' => $projection,
'moyenne_journalliere' => round($moyenneJournaliere, 2),
'jours_restants' => $jours_restants,
'clients4P' => $clients4P,
'clients4PC' => $clients4PC,
'j+30' => $j30,
'j+30_mobile' => $j30_mobile,
'nombre_titulaire_email_mobile' => $titulaire_mobile,
'nombre_titulaire_email_mobile_chainage' => $titulaire_mobile_chainage,
'clients_hors_mig' => $clients_hors_mig,
'mrz' => $this->fixMrzTotals($mrz),
'total_client_unique' => $total_clients_unique,
'j30_raccorde_fixe' => $j30_raccorde_fixe,
'j30_raccorde_mobile' => $j30_raccorde_mobile,
'total_ventes_raccordes_fixe' => $total_ventes_raccordes_fixe,
'total_ventes_raccordes_mobile' => $total_ventes_raccordes_mobile,
'clients_churn_fixe' => $total_churn_fixe,
'clients_churn_mobile' => $total_churn_mobile,
'clients_4PC_par_semaine' => $this->fixTaux4PCParSemaine($clients_4PC_par_semaine) ?? [],
'clients_4P_par_semaine' => $this->fixTaux4PCParSemaine($clients_4P_par_semaine) ?? [],
'RIO' => $total_rio . "%",
'Potentiel' => $total_potentiel . "%",
'PTO_saisie' => $total_pto_saisie . "%",
'PTO_non_saisie' => $total_pto_non_saisie . "%",
'PTO_sans' => $total_pto_non_existante . "%",
'PTO_existante' => $total_pto_existante . "%",
'total_ventes_box_5G' => $total_ventes_box_5G
];
}
return new JsonResponse($nbrMonth > 1 ? $response : $response[$baseMonth], 200);
}
public function fixTaux4PcParSemaine($clients_4PC_par_semaine)
{
$toclientsParSemaine = [];
foreach ($clients_4PC_par_semaine as $source) {
foreach ($source as $item) {
if (!isset($item['semaine']) || !isset($item['total'])) {
// On ignore les éléments incomplets
continue;
}
$semaine = $item['semaine'];
$total = (int) $item['total'];
if (!isset($toclientsParSemaine[$semaine])) {
$toclientsParSemaine[$semaine] = 0;
}
$toclientsParSemaine[$semaine] += $total;
}
}
$resultat = [];
foreach ($toclientsParSemaine as $semaine => $total) {
$resultat[] = [
'semaine' => $semaine,
'total' => $total
];
}
// Tri par ordre croissant de semaine (au cas où)
usort($resultat, fn($a, $b) => $a['semaine'] <=> $b['semaine']);
return $resultat;
}
public function fixMrzTotals(array $mrz): array
{
$toclientsParType = [];
foreach ($mrz as $groupe) {
foreach ($groupe as $item) {
if (!isset($item['identity_ctrl']) || !isset($item['total_vente'])) {
continue;
}
$type = $item['identity_ctrl'];
$vente = (int) $item['total_vente'];
if (!isset($toclientsParType[$type])) {
$toclientsParType[$type] = 0;
}
$toclientsParType[$type] += $vente;
}
}
$resultat = [];
foreach ($toclientsParType as $type => $total) {
$resultat[] = [
'identity_ctrl' => $type,
'total_vente' => $total
];
}
return $resultat;
}
}