fix(rbac) : usine peut lire les listes client/fournisseur pour le select de contrepartie pesée (ERP-209)
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m36s
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 5m34s

Problème : connecté en usine (usine17/82/86), les déroulants Client/Fournisseur
du ticket de pesée sortent vides. Cause = 403 : le rôle usine n'a pas
`commercial.clients.view`/`suppliers.view`, et le composable contrepartie (résilient
au 403) laisse les selects vides. Ce n'est PAS le filtre site (ERP-208).

Fix (permission dédiée « référentiel », ne fuit pas le répertoire) :
- CommercialModule : nouvelles permissions `commercial.clients.read_ref` et
  `commercial.suppliers.read_ref` (lecture de la LISTE pour alimenter un select).
- Client/Supplier GetCollection : security `view OR read_ref`. Seule la collection
  s'ouvre ; item Get, POST et PATCH restent gardés par `view`/`manage`. La sidebar
  répertoire reste gardée par `view` → usine ne voit pas le répertoire.
- RbacSeeder (rôle Usine) : ajout des 2 read_ref.
- Miroirs RBAC (règle ABSOLUE n°8) : personas.ts (user-full) + SeedE2ECommand alignés.
- Tests : testUsineIsForbiddenEverywhere → testUsineCanReadListButNothingElse
  (200 sur la liste, 403 sur détail/création/édition) pour Client et Supplier ;
  l'assertion categories/sites confirme que read_ref ne couvre QUE clients/suppliers.

Déploiement : jouer `app:sync-permissions` puis `app:seed-rbac`. Si usine17/82/86
utilisent des rôles custom (pas le rôle « usine » standard), leur ajouter les 2
permissions read_ref à la main.

RETOUR ARRIÈRE MÉTIER (si l'usine ne doit PAS voir les tiers) : retirer les 2
read_ref de RbacSeeder ROLE_USINE + de CommercialModule + le `or ...read_ref` des
GetCollection Client/Supplier, puis sync-permissions + seed-rbac. Restaurer aussi
les tests testUsineIsForbiddenEverywhere (403 partout).

Note : une fois le 403 levé, la liste reste filtrée sur le site courant (ERP-208) ;
si aucun tiers n'a d'adresse sur le site usine, le select peut rester vide — point
de données/sémantique distinct, à arbitrer séparément.
This commit is contained in:
2026-06-29 12:00:57 +02:00
parent dd11cb37ec
commit d21025067a
8 changed files with 52 additions and 9 deletions
@@ -24,7 +24,8 @@ use Symfony\Component\Console\Output\NullOutput;
* - bureau : suppliers.view + manage (ni accounting, ni archive)
* - compta : suppliers.view + accounting.view + accounting.manage (PAS manage)
* - commerciale : suppliers.view + manage (PAS accounting)
* - usine : aucune permission (403 partout)
* - usine : read_ref seul -> 200 sur la LISTE (select contrepartie pesee,
* ERP-209), 403 sur detail/creation/edition
* - archive : admin seul (aucun role metier)
*
* @internal
@@ -59,14 +60,18 @@ final class SupplierRBACMatrixTest extends AbstractSupplierApiTestCase
self::ensureKernelShutdown();
}
public function testUsineIsForbiddenEverywhere(): void
public function testUsineCanReadSupplierListButNothingElse(): void
{
$seed = $this->seedSupplier('Usine Target');
$client = $this->authAs('usine');
// ERP-209 : `commercial.suppliers.read_ref` ouvre la LISTE seule (select de
// contrepartie du ticket de pesee) -> 200 sur la collection.
$client->request('GET', '/api/suppliers', ['headers' => ['Accept' => self::LD]]);
self::assertResponseStatusCodeSame(403);
self::assertResponseStatusCodeSame(200);
// Mais RIEN d'autre : detail, creation et edition restent gardes par
// view/manage -> 403. (Retour arriere metier : cf. RbacSeeder ROLE_USINE.)
$client->request('GET', '/api/suppliers/'.$seed->getId(), ['headers' => ['Accept' => self::LD]]);
self::assertResponseStatusCodeSame(403);