feat(catalog) : M7 — StorageProvider + StorageProcessor (ERP-213) #165
Reference in New Issue
Block a user
Delete Branch "feat/erp-213-storage-provider-processor"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
M7 · ERP-213 (1.4) —
StorageProvider+StorageProcessorExpose les opérations API stockage (liste paginée + filtres, création/édition) avec normalisation serveur et règles métier. Pattern miroir
ProductProvider/ProductProcessor.Livré
StorageProvider(ORM) — collection paginée Hydra (ApiPlatform\Doctrine\Orm\Paginator, jamais d'array brut), échappatoire?pagination=false. Exclut les soft-deleted (RG-7.07). Trisite.code ASC, storageType.label ASC, numero ASC. Filtres?search=(numero),?siteId[]=,?storageTypeId=,?state=. Item : lookup par id, 404 si soft-deleted (§ 2.8).StorageProcessor(Post/Patch) —StorageFieldNormalizer(numero → trim, pas d'UPPER, RG-7.06 / HP-M7-05) ; RG-7.01 unicité(site, storageType, numero)parmi les actifs → 409 (exclut le courant en PATCH, filet anti-race sur l'index partiel) ; mode strict PATCH.existsActiveBySiteTypeNumero()+createListQueryBuilder()(jointures to-one site/storageType eager-load, filtres, JSONB@>pour?state=).AbstractStorageApiTestCase+StorageApiTest(10 cas).Décision — RG-7.03 omise (validée avec Tristan)
Le prompt demandait de vérifier que
storageType.sitescontient le site. Mais ce concept a été retiré du modèle en M6 : la jointurestorage_type_sitea été droppée (migrationVersion20260626100000) etStorageTyperendu plat — l'entité le documente explicitement (« un type n'est PAS rattaché à des sites ; la dispo relève de la future entité Stockage »). C'est désormaisStorage(1 site + 1 type) qui matérialise cette disponibilité : il n'existe plus de référentiel à interroger. RG-7.03 est donc inimplémentable telle quelle et omise. À reclarifier côté spec. (De même, le filtre?siteId[]=duStorageTypeProvidercité par le prompt n'existe pas — supprimé en M6 ; le filtre site est porté ici directement parStorage.site.)Vérifications
StorageApiTest(10 tests, 64 assertions) : collection paginée Hydra + contrat de sérialisation (site/storageTypeobjets embarqués,displayNameprésent) ; 201 création ; trim numéro ; 409 doublon triplet ; même numéro sur autre type → 201 ; réutilisation après soft-delete → 201 ; soft-deleted → 404 ; view lit / ne gère pas ; 403 des 4 personas métier (admin-only).make test:CollectionsArePaginatedTestvert. Les échecs de la suite complète (Core/Commercial/Sites/Transport) sont du flaky JWT + pollution BDD (suite sans DAMA) — chacun passe sur BDD propre / en isolation ; les 4 rejoués ensemble avecStorageApiTestsur BDD propre = 38/38.make php-cs-fixer-allow-risky: clean.Revue de code — M7 Stockages
🟠
storageTypeId=0/siteId=0: liste vide vs export complet —StorageProvider.phpctype_digit('0')est vrai → le provider appliquest.id = 0(collection vide). L'export, lui, exige> 0et traite 0 commenull(toutes les lignes). Même querystring, deux résultats. Racine commune avec les divergences de l'export (ERP-214) : les filtres sont parsés deux fois avec des règles subtilement différentes.→ Factoriser un
StorageListFilters(lecture unique, acceptantRequestou$context['filters']) consommé par le provider et le contrôleur d'export.🟡 Duplication —
StorageProcessor/StorageFieldNormalizerStorageFieldNormalizer::normalizeNumeroest byte-identique àProductFieldNormalizer::normalizeName: un service entier (+ DI + dépendance processor + cast(string)) pour untrim()queAssert\NotBlank(normalizer:'trim')effectue déjà avant le processor. Les readers de filtres du provider sont aussi un copier-coller deProductProvider. → Mutualiser danssrc/Shared.Faible (perf, hérité du pattern Product) : le filtre
?state=charge tous les ids matchés en PHP puis les réinjecte enIN (...)(deux allers-retours) au lieu d'unEXISTS/ fonction DQL JSONB inline.