From e90f0fb46e8e93a9e9748fb94a0baf740f83cffc Mon Sep 17 00:00:00 2001 From: Matthieu Date: Fri, 5 Jun 2026 14:21:19 +0200 Subject: [PATCH] =?UTF-8?q?feat(commercial)=20:=20RBAC=20fournisseurs=20(p?= =?UTF-8?q?ermissions=20+=203=20sources=20+=20seed=20par=20r=C3=B4le=20+?= =?UTF-8?q?=20s=C3=A9curit=C3=A9=20r=C3=A9f=C3=A9rentiels)=20(ERP-90)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 5 permissions commercial.suppliers.* (view/manage/accounting.view/accounting.manage/archive) dans CommercialModule::permissions() - 3 sources RBAC synchronisées (règle n°8) : sidebar.php (/suppliers + suppliers.view), personas.ts (user-full), SeedE2ECommand.php (miroir back) - Assignation par rôle dans RbacSeeder::MATRIX (§ 2.9, idempotent) : Bureau view+manage, Compta view+accounting.view+accounting.manage, Commerciale view+manage, Usine aucune, archive Admin seul - Sécurité des référentiels (tva_modes/payment_delays/payment_types/banks) élargie : view client OR view fournisseur --- config/sidebar.php | 9 +++++---- frontend/tests/e2e/_fixtures/personas.ts | 9 +++++++++ src/Module/Commercial/CommercialModule.php | 5 +++++ src/Module/Commercial/Domain/Entity/Bank.php | 6 +++--- .../Commercial/Domain/Entity/PaymentDelay.php | 6 +++--- .../Commercial/Domain/Entity/PaymentType.php | 6 +++--- src/Module/Commercial/Domain/Entity/TvaMode.php | 9 +++++---- src/Module/Core/Application/Rbac/RbacSeeder.php | 17 +++++++++++++++-- .../Infrastructure/Console/SeedE2ECommand.php | 8 ++++++++ 9 files changed, 56 insertions(+), 19 deletions(-) diff --git a/config/sidebar.php b/config/sidebar.php index 3c551b8..f2baf26 100644 --- a/config/sidebar.php +++ b/config/sidebar.php @@ -53,10 +53,11 @@ return [ 'permission' => 'commercial.clients.view', ], [ - 'label' => 'sidebar.commercial.suppliers', - 'to' => '/suppliers', - 'icon' => 'mdi:account-arrow-left-outline', - 'module' => 'commercial', + 'label' => 'sidebar.commercial.suppliers', + 'to' => '/suppliers', + 'icon' => 'mdi:account-arrow-left-outline', + 'module' => 'commercial', + 'permission' => 'commercial.suppliers.view', ], ], ], diff --git a/frontend/tests/e2e/_fixtures/personas.ts b/frontend/tests/e2e/_fixtures/personas.ts index c610608..73afbd4 100644 --- a/frontend/tests/e2e/_fixtures/personas.ts +++ b/frontend/tests/e2e/_fixtures/personas.ts @@ -75,6 +75,15 @@ export const personas: Record = { 'commercial.clients.accounting.view', 'commercial.clients.accounting.manage', 'commercial.clients.archive', + // Commercial — Repertoire fournisseurs (M2, ERP-90). Meme logique que + // les clients : mappe sur le persona "tout", pas de nouveau persona + // (regle ABSOLUE n°7). commercial.suppliers.view n'ajoute pas de lien + // dans la section Administration, donc expectedAdminLinks reste inchange. + 'commercial.suppliers.view', + 'commercial.suppliers.manage', + 'commercial.suppliers.accounting.view', + 'commercial.suppliers.accounting.manage', + 'commercial.suppliers.archive', ], expectedAdminLinks: ['users', 'roles', 'sites', 'categories', 'audit-log'], }, diff --git a/src/Module/Commercial/CommercialModule.php b/src/Module/Commercial/CommercialModule.php index f4fac9c..71157c9 100644 --- a/src/Module/Commercial/CommercialModule.php +++ b/src/Module/Commercial/CommercialModule.php @@ -39,6 +39,11 @@ final class CommercialModule ['code' => 'commercial.clients.accounting.view', 'label' => 'Voir l\'onglet Comptabilité d\'un client'], ['code' => 'commercial.clients.accounting.manage', 'label' => 'Modifier l\'onglet Comptabilité d\'un client'], ['code' => 'commercial.clients.archive', 'label' => 'Archiver / restaurer un client'], + ['code' => 'commercial.suppliers.view', 'label' => 'Voir les fournisseurs'], + ['code' => 'commercial.suppliers.manage', 'label' => 'Créer / modifier les fournisseurs (hors onglet Comptabilité)'], + ['code' => 'commercial.suppliers.accounting.view', 'label' => 'Voir l\'onglet Comptabilité d\'un fournisseur'], + ['code' => 'commercial.suppliers.accounting.manage', 'label' => 'Modifier l\'onglet Comptabilité d\'un fournisseur'], + ['code' => 'commercial.suppliers.archive', 'label' => 'Archiver / restaurer un fournisseur'], ]; } } diff --git a/src/Module/Commercial/Domain/Entity/Bank.php b/src/Module/Commercial/Domain/Entity/Bank.php index e690504..c1c637d 100644 --- a/src/Module/Commercial/Domain/Entity/Bank.php +++ b/src/Module/Commercial/Domain/Entity/Bank.php @@ -25,7 +25,7 @@ use Symfony\Component\Serializer\Attribute\Groups; #[ApiResource( operations: [ new GetCollection( - security: "is_granted('commercial.clients.view')", + security: "is_granted('commercial.clients.view') or is_granted('commercial.suppliers.view')", normalizationContext: ['groups' => ['bank:read']], // Tri par defaut spec M1 § 4.7 : position ASC puis label ASC. order: ['position' => 'ASC', 'label' => 'ASC'], @@ -33,11 +33,11 @@ use Symfony\Component\Serializer\Attribute\Groups; paginationClientEnabled: true, ), new Get( - security: "is_granted('commercial.clients.view')", + security: "is_granted('commercial.clients.view') or is_granted('commercial.suppliers.view')", normalizationContext: ['groups' => ['bank:read']], ), ], - security: "is_granted('commercial.clients.view')", + security: "is_granted('commercial.clients.view') or is_granted('commercial.suppliers.view')", )] #[ORM\Entity(repositoryClass: DoctrineBankRepository::class)] #[ORM\Table(name: 'bank')] diff --git a/src/Module/Commercial/Domain/Entity/PaymentDelay.php b/src/Module/Commercial/Domain/Entity/PaymentDelay.php index f45f225..739ccc3 100644 --- a/src/Module/Commercial/Domain/Entity/PaymentDelay.php +++ b/src/Module/Commercial/Domain/Entity/PaymentDelay.php @@ -25,7 +25,7 @@ use Symfony\Component\Serializer\Attribute\Groups; #[ApiResource( operations: [ new GetCollection( - security: "is_granted('commercial.clients.view')", + security: "is_granted('commercial.clients.view') or is_granted('commercial.suppliers.view')", normalizationContext: ['groups' => ['payment_delay:read']], // Tri par defaut spec M1 § 4.7 : position ASC puis label ASC. order: ['position' => 'ASC', 'label' => 'ASC'], @@ -33,11 +33,11 @@ use Symfony\Component\Serializer\Attribute\Groups; paginationClientEnabled: true, ), new Get( - security: "is_granted('commercial.clients.view')", + security: "is_granted('commercial.clients.view') or is_granted('commercial.suppliers.view')", normalizationContext: ['groups' => ['payment_delay:read']], ), ], - security: "is_granted('commercial.clients.view')", + security: "is_granted('commercial.clients.view') or is_granted('commercial.suppliers.view')", )] #[ORM\Entity(repositoryClass: DoctrinePaymentDelayRepository::class)] #[ORM\Table(name: 'payment_delay')] diff --git a/src/Module/Commercial/Domain/Entity/PaymentType.php b/src/Module/Commercial/Domain/Entity/PaymentType.php index 5402b68..6b87bc8 100644 --- a/src/Module/Commercial/Domain/Entity/PaymentType.php +++ b/src/Module/Commercial/Domain/Entity/PaymentType.php @@ -28,7 +28,7 @@ use Symfony\Component\Serializer\Attribute\Groups; #[ApiResource( operations: [ new GetCollection( - security: "is_granted('commercial.clients.view')", + security: "is_granted('commercial.clients.view') or is_granted('commercial.suppliers.view')", normalizationContext: ['groups' => ['payment_type:read']], // Tri par defaut spec M1 § 4.7 : position ASC puis label ASC. order: ['position' => 'ASC', 'label' => 'ASC'], @@ -36,11 +36,11 @@ use Symfony\Component\Serializer\Attribute\Groups; paginationClientEnabled: true, ), new Get( - security: "is_granted('commercial.clients.view')", + security: "is_granted('commercial.clients.view') or is_granted('commercial.suppliers.view')", normalizationContext: ['groups' => ['payment_type:read']], ), ], - security: "is_granted('commercial.clients.view')", + security: "is_granted('commercial.clients.view') or is_granted('commercial.suppliers.view')", )] #[ORM\Entity(repositoryClass: DoctrinePaymentTypeRepository::class)] #[ORM\Table(name: 'payment_type')] diff --git a/src/Module/Commercial/Domain/Entity/TvaMode.php b/src/Module/Commercial/Domain/Entity/TvaMode.php index 989fb02..f971e47 100644 --- a/src/Module/Commercial/Domain/Entity/TvaMode.php +++ b/src/Module/Commercial/Domain/Entity/TvaMode.php @@ -17,7 +17,8 @@ use Symfony\Component\Serializer\Attribute\Groups; * re-seede en dev/test par CommercialReferentialFixtures. * * Lecture seule au M1 (HP-M2-2) : seules GetCollection et Get sont exposees - * (ERP-56), sous la permission commercial.clients.view ; aucune ecriture + * (ERP-56), sous la permission commercial.clients.view (elargie aux roles + * fournisseurs au M2 via commercial.suppliers.view, ERP-90) ; aucune ecriture * declaree -> POST/PATCH/DELETE renvoient 405. * * Referentiel statique : pas de Timestampable/Blamable (whiteliste dans @@ -28,7 +29,7 @@ use Symfony\Component\Serializer\Attribute\Groups; #[ApiResource( operations: [ new GetCollection( - security: "is_granted('commercial.clients.view')", + security: "is_granted('commercial.clients.view') or is_granted('commercial.suppliers.view')", normalizationContext: ['groups' => ['tva_mode:read']], // Tri par defaut spec M1 § 4.7 : position ASC puis label ASC // (ordre des selecteurs comptables) — provider Doctrine par defaut. @@ -39,11 +40,11 @@ use Symfony\Component\Serializer\Attribute\Groups; paginationClientEnabled: true, ), new Get( - security: "is_granted('commercial.clients.view')", + security: "is_granted('commercial.clients.view') or is_granted('commercial.suppliers.view')", normalizationContext: ['groups' => ['tva_mode:read']], ), ], - security: "is_granted('commercial.clients.view')", + security: "is_granted('commercial.clients.view') or is_granted('commercial.suppliers.view')", )] #[ORM\Entity(repositoryClass: DoctrineTvaModeRepository::class)] #[ORM\Table(name: 'tva_mode')] diff --git a/src/Module/Core/Application/Rbac/RbacSeeder.php b/src/Module/Core/Application/Rbac/RbacSeeder.php index 6c6c327..b97c843 100644 --- a/src/Module/Core/Application/Rbac/RbacSeeder.php +++ b/src/Module/Core/Application/Rbac/RbacSeeder.php @@ -51,8 +51,9 @@ final class RbacSeeder * Definition unique des 4 roles + matrice § 2.7. La cle est le code du role, * `label` le libelle FR affichable, `permissions` la liste des codes RBAC a * attacher (vide pour usine : aucun acces ; admin n'apparait pas car il - * bypass tout via isAdmin ; `commercial.clients.archive` n'est attache a - * aucun role metier — admin seul). + * bypass tout via isAdmin ; `commercial.clients.archive` et + * `commercial.suppliers.archive` ne sont attaches a aucun role metier — + * admin seul). * * @var array}> */ @@ -62,6 +63,9 @@ final class RbacSeeder 'permissions' => [ 'commercial.clients.view', 'commercial.clients.manage', + // Fournisseurs (M2 § 2.9, ERP-90) : view + manage (hors Comptabilite). + 'commercial.suppliers.view', + 'commercial.suppliers.manage', // Lecture des referentiels transverses pour les selects client (ERP-102). 'catalog.categories.read_ref', 'sites.read_ref', @@ -73,6 +77,11 @@ final class RbacSeeder 'commercial.clients.view', 'commercial.clients.accounting.view', 'commercial.clients.accounting.manage', + // Fournisseurs (M2 § 2.9, ERP-90) : view + onglet Comptabilite uniquement + // (pas de manage global -> ne peut pas creer un fournisseur). + 'commercial.suppliers.view', + 'commercial.suppliers.accounting.view', + 'commercial.suppliers.accounting.manage', // Lecture des referentiels transverses pour les selects client (ERP-102). 'catalog.categories.read_ref', 'sites.read_ref', @@ -83,6 +92,10 @@ final class RbacSeeder 'permissions' => [ 'commercial.clients.view', 'commercial.clients.manage', + // Fournisseurs (M2 § 2.9, ERP-90) : view + manage, sans accounting + // (onglet Comptabilite masque/filtre pour la Commerciale). + 'commercial.suppliers.view', + 'commercial.suppliers.manage', // Lecture des referentiels transverses pour les selects client (ERP-102). 'catalog.categories.read_ref', 'sites.read_ref', diff --git a/src/Module/Core/Infrastructure/Console/SeedE2ECommand.php b/src/Module/Core/Infrastructure/Console/SeedE2ECommand.php index 9a59237..7e7545b 100644 --- a/src/Module/Core/Infrastructure/Console/SeedE2ECommand.php +++ b/src/Module/Core/Infrastructure/Console/SeedE2ECommand.php @@ -195,6 +195,14 @@ final class SeedE2ECommand extends Command 'commercial.clients.accounting.view', 'commercial.clients.accounting.manage', 'commercial.clients.archive', + // Commercial — Repertoire fournisseurs (M2, ERP-90). Meme + // logique que les clients : mappe sur le persona "tout". + // Miroir de frontend/tests/e2e/_fixtures/personas.ts. + 'commercial.suppliers.view', + 'commercial.suppliers.manage', + 'commercial.suppliers.accounting.view', + 'commercial.suppliers.accounting.manage', + 'commercial.suppliers.archive', ], ], [ -- 2.39.5