97459e798f
Auto Tag Develop / tag (push) Successful in 7s
Export XLSX du répertoire fournisseurs (spec-back M2 § 4.6), jumeau de l'export client M1. **Stack : cible `feature/ERP-90-rbac-fournisseurs`** (ERP-84→91 non encore mergés dans develop).
## Périmètre
- **`SupplierExportController`** avec `#[Route(priority: 1)]` (anti-conflit API Platform `{id}`) + `is_granted('commercial.suppliers.view')`.
- Mêmes filtres que la liste (`includeArchived`/`archivedOnly`/`search`/`categoryCode`/`siteId`) via `createListQueryBuilder()` partagé avec le `SupplierProvider` ; non archivés par défaut.
- Colonnes : Nom fournisseur, **Contact principal** (Nom + Prénom du `SupplierContact` de plus petit `position`, ERP-106), Tél principal, Tél secondaire, Email, Catégories (CSV), Sites (CSV), **SIREN omis sans `accounting.view`**, Date de création.
- Fichier `repertoire-fournisseurs-{YYYYMMDD}.xlsx`.
- **`hydrateContacts()`** ajouté au repository : chargement batché des contacts en une requête `IN` (anti-N+1). Méthode dédiée à l'export — la liste paginée n'embarque pas les contacts, on ne lui impose pas ce coût.
## Correctif hors-périmètre (signalé)
Tables `supplier*` ajoutées à `ColumnCommentsCatalog` : leurs `COMMENT ON COLUMN` (posés par la migration ERP-85) étaient dropés par le `schema:update --force` du `test-db-setup` et non restaurés (catalogue = source rejouée par `app:apply-column-comments`), cassant `ColumnsHaveSqlCommentTest` dès un re-setup de la base de test. Trou laissé par ERP-85/86, vert tant que personne ne re-setup la base.
## Tests
- `SupplierExportControllerTest` (9 cas) : réponse/filename, exclusion archives, filtre search, contact principal, colonnes catégories/sites, gating SIREN avec/sans `accounting.view`, 403, 401.
- `make test` : 508 tests / 2035 assertions, 0 échec. `php-cs-fixer` clean.
---------
Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #70
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
81 lines
3.5 KiB
PHP
81 lines
3.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Module\Commercial\Domain\Repository;
|
|
|
|
use App\Module\Commercial\Domain\Entity\Supplier;
|
|
use Doctrine\ORM\QueryBuilder;
|
|
|
|
interface SupplierRepositoryInterface
|
|
{
|
|
public function findById(int $id): ?Supplier;
|
|
|
|
public function save(Supplier $supplier): void;
|
|
|
|
/**
|
|
* Construit un QueryBuilder de liste pour le repertoire fournisseurs.
|
|
* - Exclut toujours les fournisseurs soft-deletes (deleted_at IS NOT NULL, RG-2.17).
|
|
* - Archivage (RG-2.17) :
|
|
* - $archivedOnly = true -> uniquement les archives (is_archived = true) ;
|
|
* - sinon $includeArchived = true -> actifs + archives (echappatoire) ;
|
|
* - sinon (defaut) -> uniquement les actifs (is_archived = false).
|
|
* $archivedOnly a la priorite sur $includeArchived.
|
|
* - Tri par defaut : companyName ASC (RG-2.17).
|
|
* - $search : recherche fuzzy insensible a la casse sur companyName + les
|
|
* contacts lies (firstName / lastName / email) via sous-requete (D1,
|
|
* refonte-contact §4.1). Metacaracteres LIKE echappes. Ignore si null/vide.
|
|
* - $categoryCodes : restreint aux fournisseurs possedant au moins une
|
|
* categorie dont le code est dans la liste (OR). Liste vide = pas de filtre.
|
|
* - $siteIds : restreint aux fournisseurs ayant au moins une adresse rattachee
|
|
* a l'un des sites donnes (OR — RG-2.06). Liste vide = pas de filtre.
|
|
*
|
|
* Filtrage centralise ICI (et non dans le provider/controller) pour que la
|
|
* liste paginee (SupplierProvider) et l'export (SupplierExportController)
|
|
* partagent strictement la meme logique de selection.
|
|
*
|
|
* Contrat = SELECTION uniquement (filtres + tri). Aucun fetch-join to-many :
|
|
* l'hydratation des collections affichees est deleguee a
|
|
* {@see self::hydrateListCollections()} pour ne pas imposer le cout d'un
|
|
* produit cartesien aux chemins non pagines (cf. M1/ERP-100).
|
|
*
|
|
* @param list<string> $categoryCodes
|
|
* @param list<int> $siteIds
|
|
*/
|
|
public function createListQueryBuilder(
|
|
bool $includeArchived = false,
|
|
?string $search = null,
|
|
array $categoryCodes = [],
|
|
array $siteIds = [],
|
|
bool $archivedOnly = false,
|
|
): QueryBuilder;
|
|
|
|
/**
|
|
* Hydrate en lot les collections affichees par le repertoire (categories,
|
|
* adresses et leurs sites) sur un jeu de fournisseurs DEJA charges, via
|
|
* l'identity map Doctrine (memes instances). A appeler apres une selection
|
|
* bornee (page courante ou jeu d'export) pour eviter le N+1 a la
|
|
* serialisation, sans imposer de fetch-join au QueryBuilder de selection
|
|
* (anti N+1, § 2.12).
|
|
*
|
|
* Charge les categories et les adresses/sites en DEUX requetes distinctes
|
|
* (et non un triple fetch-join) pour ne pas multiplier categories x adresses
|
|
* x sites en un seul produit cartesien.
|
|
*
|
|
* @param list<Supplier> $suppliers
|
|
*/
|
|
public function hydrateListCollections(array $suppliers): void;
|
|
|
|
/**
|
|
* Hydrate en lot la collection `contacts` sur un jeu de fournisseurs DEJA
|
|
* charges (memes instances via l'identity map). Reservee a l'export XLSX
|
|
* (§ 4.6) qui a besoin du contact principal : la LISTE paginee n'embarque
|
|
* pas les contacts (§ 2.12), d'ou une methode dediee plutot qu'une passe
|
|
* supplementaire dans {@see self::hydrateListCollections()} — on n'impose pas
|
|
* le cout du chargement des contacts au chemin liste.
|
|
*
|
|
* @param list<Supplier> $suppliers
|
|
*/
|
|
public function hydrateContacts(array $suppliers): void;
|
|
}
|