3fe0f676f6
Auto Tag Develop / tag (push) Successful in 11s
Ticket Lesstime #139 (M3 — Répertoire prestataires, position 1.9). DoD back avant le front : suite PHPUnit consolidée sur la matrice § 8.1 + captures JSON réelles dans la spec § 4.0.bis. ## Contenu - **Fix réfs comptables** : `provider:read:accounting` ajouté sur `TvaMode`/`PaymentDelay`/`PaymentType`/`Bank` — sans ça elles sortaient en IRI nu dans le détail prestataire (réplique du fix ERP-92 du M2, piège #1 § 4.0.bis). - **`ProviderSerializationContractTest`** (13 tests) : gating RIB/scalaires par omission, réfs compta en objet `{id,code,label}`, `isArchived`, embed categories/sites liste+détail, sous-collections, enveloppe AP4 ; `testDodReferenceJsonShape` dumpe le JSON réel (`PROVIDER_DOD_DUMP=1`). - **`ProviderAuditTest`** (5 tests) : create/update/archive (`technique.Provider`), iban/bic dans le diff (`technique.ProviderRib`, pas dAuditIgnore), trace M2M `sites`. - **`ProviderListTest`** étendu : `?pagination=false`, anti-N+1, filtre `?typeCode=PRESTATAIRE`. - **`ProviderRbacGatingTest`** étendu : restauration en conflit de nom → 409 (RG-3.14). - **`ProviderFixtures`** (§ 8.4) : démo idempotente (complet VIREMENT+banque+RIB, LCR+RIB, CHEQUE multi-cat, minimal, archivé) répartie sur sites 86/17/82 ; skip en env `test`. - Helper `seedCompleteProvider` ; spec § 4.0.bis : gabarits remplacés par les captures réelles (liste + détail avec/sans accounting.view). ## Vérifications - `make php-cs-fixer-allow-risky` → 0 fichier - `make test` → OK, 677 tests, 3328 assertions (garde-fous globaux verts) ## Notes - MR stackée sur ERP-138 (base = sa branche). - Fixtures démo exercées en dev via `make fixtures` (autowiring vérifié). --------- Co-authored-by: Matthieu <contact@malio.fr> Reviewed-on: #100
99 lines
4.5 KiB
PHP
99 lines
4.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Module\Technique\Domain\Repository;
|
|
|
|
use App\Module\Technique\Domain\Entity\Provider;
|
|
use Doctrine\ORM\QueryBuilder;
|
|
|
|
interface ProviderRepositoryInterface
|
|
{
|
|
public function findById(int $id): ?Provider;
|
|
|
|
public function save(Provider $provider): void;
|
|
|
|
/**
|
|
* Restreint un QueryBuilder de liste aux prestataires rattaches au site donne
|
|
* (relation DIRECTE provider.sites). Sert le cloisonnement par site pilote par
|
|
* l'utilisateur (RG-3.17, § 2.13) : le ProviderProvider resout le site courant
|
|
* (CurrentSiteProvider) puis appelle cette methode quand l'user n'a pas
|
|
* `sites.bypass_scope`. Decouple ainsi la DECISION (Provider, qui connait
|
|
* l'user) du DQL (repository, qui ne connait que l'id de site).
|
|
*
|
|
* Sous-requete IN (et non JOIN sur la M2M) pour ne pas perturber le
|
|
* DISTINCT / ORDER BY / pagination du QueryBuilder de selection — meme parti
|
|
* pris que les filtres ?categoryCode / ?siteId. Applique AVANT la pagination
|
|
* (le COUNT du Paginator reflete alors le perimetre de l'user).
|
|
*/
|
|
public function applySiteScope(QueryBuilder $qb, int $siteId): void;
|
|
|
|
/**
|
|
* Construit un QueryBuilder de liste pour le repertoire prestataires.
|
|
* - Exclut toujours les prestataires soft-deletes (deleted_at IS NOT NULL, RG-3.16).
|
|
* - Archivage (RG-3.16) :
|
|
* - $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-3.16).
|
|
* - $search : recherche fuzzy insensible a la casse sur companyName + les
|
|
* contacts lies (firstName / lastName / email) via sous-requete.
|
|
* Metacaracteres LIKE echappes. Ignore si null/vide.
|
|
* - $categoryCodes : restreint aux prestataires possedant au moins une
|
|
* categorie dont le code est dans la liste (OR). Liste vide = pas de filtre.
|
|
* - $siteIds : restreint aux prestataires rattaches a l'un des sites donnes
|
|
* (OR — RG-3.03, relation DIRECTE provider.sites). Liste vide = pas de filtre.
|
|
*
|
|
* Filtrage centralise ICI (et non dans le provider/controller) pour que la
|
|
* liste paginee et l'export partagent strictement la meme logique de selection
|
|
* (miroir M2).
|
|
*
|
|
* 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 (§ 2.12, cf. M1/ERP-100, M2).
|
|
*
|
|
* NB : le cloisonnement par site pilote par l'utilisateur (RG-3.17, § 2.13) est
|
|
* applique en AMONT par le ProviderProvider (ERP-134), pas par ce QueryBuilder
|
|
* (qui ne connait pas l'user courant).
|
|
*
|
|
* @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 puis
|
|
* sites — relation DIRECTE provider.sites, RG-3.03) sur un jeu de prestataires
|
|
* 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 sites en DEUX requetes distinctes (et non un
|
|
* double fetch-join) pour ne pas multiplier categories x sites en un seul
|
|
* produit cartesien.
|
|
*
|
|
* @param list<Provider> $providers
|
|
*/
|
|
public function hydrateListCollections(array $providers): void;
|
|
|
|
/**
|
|
* Hydrate en lot la collection `contacts` sur un jeu de prestataires DEJA
|
|
* charges (memes instances via l'identity map). Reservee aux chemins qui ont
|
|
* besoin du contact principal (export) : la LISTE paginee n'embarque pas les
|
|
* contacts (§ 2.12), d'ou une methode dediee plutot qu'une passe supplementaire
|
|
* dans {@see self::hydrateListCollections()}.
|
|
*
|
|
* @param list<Provider> $providers
|
|
*/
|
|
public function hydrateContacts(array $providers): void;
|
|
}
|