refactor(commercial) : découpler l'hydratation des collections de la sélection clients (ERP-100)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 1m55s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m6s

createListQueryBuilder() redevient filtres + tri seuls (contrat de l'interface) :
plus de fetch-join to-many imposé à tous les appelants. L'hydratation des
collections affichées (Catégories / Site(s)) passe par la nouvelle méthode
hydrateListCollections(), appelée par la liste paginée, ?pagination=false et
l'export XLSX sur leur jeu déjà borné.

Deux requêtes IN séparées (catégories d'un côté, adresses+sites de l'autre)
remplissent les collections via l'identity map et cassent le produit cartésien
catégories × adresses × sites qui explosait sur les chemins non paginés.

Ajoute un garde-fou fonctionnel sur les colonnes Catégories/Sites de l'export.

Découvert en review ERP-62 (#44).
This commit is contained in:
Matthieu
2026-06-03 11:36:33 +02:00
parent 583d634a83
commit a97adb1dd9
5 changed files with 120 additions and 14 deletions
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace App\Tests\Module\Commercial\Api;
use App\Module\Commercial\Domain\Entity\ClientAddress;
use App\Module\Sites\Domain\Entity\Site;
use PhpOffice\PhpSpreadsheet\IOFactory;
/**
@@ -88,6 +90,39 @@ final class ClientExportControllerTest extends AbstractCommercialApiTestCase
self::assertNotContains('SECTEUR CO', $names);
}
/**
* ERP-100 : depuis le decouplage hydratation/selection, le QueryBuilder de
* liste ne fetch-join plus les collections — l'export les recharge en lot via
* hydrateListCollections(). Ce test garde que les colonnes « Catégories » et
* « Site(s) » restent peuplees (un oubli d'hydratation les rendrait vides
* sans erreur).
*/
public function testExportPopulatesCategoryAndSiteColumns(): void
{
$client = $this->createAdminClient();
$seed = $this->seedClient('Hydrate Co', false, 'DISTRIBUTEUR');
$em = $this->getEm();
$site = $em->getRepository(Site::class)->findOneBy([]);
self::assertNotNull($site, 'Aucun site seede : impossible de tester la colonne Site(s).');
$address = new ClientAddress();
$address->setClient($seed);
$address->setPostalCode('86100');
$address->setCity('Châtellerault');
$address->setStreet('1 rue du Test');
$address->addSite($site);
$em->persist($address);
$em->flush();
$flat = $this->flatten($this->gridFromResponse($client->request('GET', self::EXPORT_URL)->getContent()));
// Colonne « Catégories » : libelle de la categorie du client (getName()).
self::assertStringContainsString('test_cli_cat_distributeur', $flat);
// Colonne « Site(s) » : site agrege depuis l'adresse (RG-1.10).
self::assertStringContainsString((string) $site->getName(), $flat);
}
public function testSirenColumnPresentWithAccountingView(): void
{
// L'admin bypass le RBAC : il a donc accounting.view -> colonne SIREN.