feat(technique) : entités + repositories Provider* (ERP-133) (#91)
Auto Tag Develop / tag (push) Successful in 9s
Auto Tag Develop / tag (push) Successful in 9s
PR **empilée sur ERP-132** (#90) — base = \`feature/ERP-132-migrer-schema-bdd-m3\` (ERP-132 pas encore mergé dans develop). À rebaser sur develop une fois #90 mergée. ## Périmètre (ticket Lesstime #133, M3 § 3.3/3.4/2.12/4.0) Entités Doctrine + mapping ApiResource (squelette) + repository avec hydratation anti-N+1. Miroir des entités `Supplier*` (M2), **amputé de l'onglet Information** et **augmenté de `provider.sites`** (M2M direct, RG-3.03). ### Créé - `Provider`, `ProviderContact`, `ProviderAddress` (simplifiée : pas de `addressType`/`bennes`/`triageProvider`), `ProviderRib` — `#[Auditable]` + Timestampable/Blamable. - `ProviderRepositoryInterface` + `DoctrineProviderRepository` : `createListQueryBuilder` (filtres + tri seuls) + `hydrateListCollections` anti-N+1 (catégories puis **sites en relation directe**, requêtes `IN` bornées séparées — § 2.12). ### Contrat de sérialisation (RETEX M1 — 3 maillons) Groupes posés sur l'entité (source unique) : liste = `provider:read`+`category:read`+`site:read` ; détail = +`provider:item:read`. Piège booléen `isArchived` traité (`#[Groups]`+`#[SerializedName]` sur le getter). Embed `categories[].code/name` + `sites[].name/postalCode` (objet, pas IRI). ### Consommation cross-module (§ 2.1) - Site/Category via contrats Shared (`SiteInterface`/`CategoryInterface` + `resolve_target_entities`) — comme Supplier, conforme règle ABSOLUE n°1. - Référentiels comptables (`TvaMode`/`PaymentDelay`/`PaymentType`/`Bank`) en relation ORM partagée directe (décision § 2.1, remontée Shared tracée HP-M4-2). ### Garde-fous / infra (requis pour le vert) - Mapping ORM du module `Technique` dans `doctrine.yaml` (sinon les 9 tables `provider*` vues orphelines → DROP). - Tables `provider*` ajoutées à `ColumnCommentsCatalog` + ligne `dbal:run-sql uq_provider_company_name_active` au makefile `test-db-setup`. - 4 libellés `audit.entity.technique_*` (fr.json) ; `ProviderAddress::postalCode` whitelisté dans `EXCLUDED_LENGTH_MIRROR` (Regex CP {4,5}). ## Hors périmètre (→ ERP-134) ApiResource **sans** `ProviderProvider`/`ProviderProcessor` ; sous-entités **sans** `#[ApiResource]`. Hydratation effective, gating accounting, cloisonnement par site, normalisation, 409 doublon, RG-3.07/3.08 → ERP-134. Sous-ressources POST/PATCH/DELETE → ticket ultérieur. ## Tests - \`make test\` → **589/589 ✓** · \`php-cs-fixer\` → 0 correction. - \`schema:validate\` : mapping OK ; « not in sync » résiduel strictement homologue à supplier (COMMENT via catalogue + index FK auto-Doctrine), non régressif. --------- Co-authored-by: Matthieu <contact@malio.fr> Reviewed-on: #91
This commit was merged in pull request #91.
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user