Critical fixes: - Make MigrateConstructeurLinks migration no-op (legacy tables already dropped) - Add explicit ON CONFLICT (id) target in RestoreConstructeurLinks migration - Replace N+1 queries with 4 bulk GROUP BY in ConstructeurStatsController - Declare missing versionListRef template ref in machine detail page - Add missing await on removeMachineDocument, cast activeTab as string Important fixes: - Add lang="ts" to ToastContainer and constructeurs page - Type entityType as union in UsedInSection/useUsedIn - Remove dead duration param from showError - Update back-link props to new /catalogues/* URLs (3 pages) - Replace raw error blocks with EmptyState in component/piece detail pages - Type handleFillEntity params and machineInfoCardRef Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
97 lines
3.0 KiB
PHP
97 lines
3.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Controller;
|
|
|
|
use App\Entity\ComposantConstructeurLink;
|
|
use App\Entity\Constructeur;
|
|
use App\Entity\MachineConstructeurLink;
|
|
use App\Entity\PieceConstructeurLink;
|
|
use App\Entity\ProductConstructeurLink;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
use Symfony\Component\Routing\Attribute\Route;
|
|
|
|
final class ConstructeurStatsController extends AbstractController
|
|
{
|
|
public function __construct(
|
|
private readonly EntityManagerInterface $em,
|
|
) {}
|
|
|
|
#[Route('/api/constructeurs/{id}/stats', name: 'api_constructeur_stats', methods: ['GET'])]
|
|
public function stats(string $id): JsonResponse
|
|
{
|
|
$this->denyAccessUnlessGranted('ROLE_VIEWER');
|
|
|
|
$constructeur = $this->em->find(Constructeur::class, $id);
|
|
if (!$constructeur) {
|
|
return new JsonResponse(
|
|
['message' => 'Fournisseur introuvable.'],
|
|
Response::HTTP_NOT_FOUND,
|
|
);
|
|
}
|
|
|
|
$bulk = $this->fetchAllStats();
|
|
|
|
return new JsonResponse($bulk[$id] ?? [
|
|
'composantCount' => 0,
|
|
'pieceCount' => 0,
|
|
'machineCount' => 0,
|
|
'productCount' => 0,
|
|
]);
|
|
}
|
|
|
|
#[Route('/api/constructeurs/stats', name: 'api_constructeurs_stats_bulk', methods: ['GET'], priority: 1)]
|
|
public function bulkStats(): JsonResponse
|
|
{
|
|
$this->denyAccessUnlessGranted('ROLE_VIEWER');
|
|
|
|
return new JsonResponse($this->fetchAllStats());
|
|
}
|
|
|
|
/** @return array<string, array{composantCount: int, pieceCount: int, machineCount: int, productCount: int}> */
|
|
private function fetchAllStats(): array
|
|
{
|
|
$result = [];
|
|
|
|
// Initialize all constructeurs with zero counts
|
|
$allIds = $this->em->createQuery(
|
|
'SELECT c.id FROM App\Entity\Constructeur c',
|
|
)->getSingleColumnResult();
|
|
|
|
foreach ($allIds as $id) {
|
|
$result[$id] = [
|
|
'composantCount' => 0,
|
|
'pieceCount' => 0,
|
|
'machineCount' => 0,
|
|
'productCount' => 0,
|
|
];
|
|
}
|
|
|
|
// 4 bulk queries instead of 4N
|
|
$counts = [
|
|
'composantCount' => ComposantConstructeurLink::class,
|
|
'pieceCount' => PieceConstructeurLink::class,
|
|
'machineCount' => MachineConstructeurLink::class,
|
|
'productCount' => ProductConstructeurLink::class,
|
|
];
|
|
|
|
foreach ($counts as $key => $entityClass) {
|
|
$rows = $this->em->createQuery(
|
|
'SELECT IDENTITY(l.constructeur) AS cid, COUNT(l.id) AS cnt FROM '.$entityClass.' l GROUP BY l.constructeur',
|
|
)->getResult();
|
|
|
|
foreach ($rows as $row) {
|
|
if (isset($result[$row['cid']])) {
|
|
$result[$row['cid']][$key] = (int) $row['cnt'];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
}
|