[ERP-59] Déclarer les permissions Commercial.clients + synchroniser RBAC (3 sources) #32

Closed
matthieu wants to merge 9 commits from feature/ERP-59-declarer-permissions-commercial-clients-rbac into develop
Owner
No description provided.
tristan reviewed 2026-06-01 09:05:29 +00:00
tristan left a comment
Owner

Revue de code ERP-59 (déclaration des permissions Commercial.clients + synchronisation des 3 miroirs RBAC), stackée sur ERP-55.

PR propre et conforme. Points vérifiés OK :

  • Règle des 3 miroirs (n°8) bien respectée : config/sidebar.php (item + permission commercial.clients.view), personas.ts ET SeedE2ECommand.php mettent à jour le même persona e2e.user-full avec les 5 mêmes codes. Alignés au caractère près.
  • Les 5 permissions correspondent exactement à celles référencées par ERP-55 : view / manage (security des opérations), accounting.view (ClientReadGroupContextBuilder), accounting.manage + archive (ClientProcessor). Aucun code orphelin, aucune permission référencée manquante.
  • CommercialModule::permissions() utilise la même forme ['code','label'] que Core/Catalog ; sync-permissions valide les clés + le préfixe commercial. et accepte sans souci les codes 4-segments (commercial.clients.accounting.view) — pas de regex stricte sur le nombre de segments.
  • Module commercial actif dans config/modules.php → les permissions seront bien synchronisées et le seed e2e (qui exige leur existence en base) ne lèvera pas.
  • Le test sidebar-visibility.spec.ts n'itère que sur expectedAdminLinks (section Administration) → l'ajout du lien dans la section Commercial ne casse aucune assertion ; expectedAdminLinks inchangé est correct.
  • i18n sidebar.commercial.clients ajouté.

2 remarques mineures ci-dessous. Rien de bloquant.

Revue de code ERP-59 (déclaration des permissions Commercial.clients + synchronisation des 3 miroirs RBAC), stackée sur ERP-55. PR propre et conforme. Points vérifiés OK : - **Règle des 3 miroirs (n°8)** bien respectée : `config/sidebar.php` (item + permission `commercial.clients.view`), `personas.ts` ET `SeedE2ECommand.php` mettent à jour **le même persona** `e2e.user-full` avec les 5 mêmes codes. Alignés au caractère près. - Les **5 permissions correspondent exactement** à celles référencées par ERP-55 : `view` / `manage` (security des opérations), `accounting.view` (ClientReadGroupContextBuilder), `accounting.manage` + `archive` (ClientProcessor). Aucun code orphelin, aucune permission référencée manquante. - `CommercialModule::permissions()` utilise la même forme `['code','label']` que Core/Catalog ; `sync-permissions` valide les clés + le préfixe `commercial.` et accepte sans souci les codes 4-segments (`commercial.clients.accounting.view`) — pas de regex stricte sur le nombre de segments. - Module `commercial` actif dans `config/modules.php` → les permissions seront bien synchronisées et le seed e2e (qui exige leur existence en base) ne lèvera pas. - Le test `sidebar-visibility.spec.ts` n'itère que sur `expectedAdminLinks` (section Administration) → l'ajout du lien dans la section Commercial ne casse aucune assertion ; `expectedAdminLinks` inchangé est correct. - i18n `sidebar.commercial.clients` ajouté. 2 remarques mineures ci-dessous. Rien de bloquant.
@@ -105,1 +105,4 @@
'items' => [
[
'label' => 'sidebar.commercial.clients',
'to' => '/commercial/clients',
Owner

Incohérence de convention de route dans la section Commercial.

Contexte : le nouvel item pointe vers /commercial/clients (imbriqué), alors que l'item voisin suppliers pointe vers /suppliers (plat), et les items Administration vers /admin/*.

Cause : côté front, le module commercial n'expose aujourd'hui que pages/commercial.vue (→ /commercial). Tant qu'ERP-73 n'a pas créé la page, le lien /commercial/clients mènera à un 404 si on clique dessus (sans impact e2e : le test n'asserte que la présence des liens Administration, pas la navigation).

Recommandation : confirmer qu'ERP-73 implémentera bien la page à l'emplacement exact /commercial/clients (ex. pages/commercial/clients.vue), et trancher la convention du module Commercial (imbriqué /commercial/* vs plat /suppliers) pour rester cohérent. Mineur, à coordonner avec ERP-73.

**Incohérence de convention de route dans la section Commercial.** **Contexte** : le nouvel item pointe vers `/commercial/clients` (imbriqué), alors que l'item voisin `suppliers` pointe vers `/suppliers` (plat), et les items Administration vers `/admin/*`. **Cause** : côté front, le module `commercial` n'expose aujourd'hui que `pages/commercial.vue` (→ `/commercial`). Tant qu'ERP-73 n'a pas créé la page, le lien `/commercial/clients` mènera à un 404 si on clique dessus (sans impact e2e : le test n'asserte que la présence des liens Administration, pas la navigation). **Recommandation** : confirmer qu'ERP-73 implémentera bien la page à l'emplacement exact `/commercial/clients` (ex. `pages/commercial/clients.vue`), et trancher la convention du module Commercial (imbriqué `/commercial/*` vs plat `/suppliers`) pour rester cohérent. Mineur, à coordonner avec ERP-73.
@@ -68,0 +70,4 @@
// commerciale/usine) seedes par ERP-74. Pas de nouveau persona
// (regle ABSOLUE n°7). commercial.clients.view n'ajoute pas de lien
// dans la section Administration, donc expectedAdminLinks reste inchange.
'commercial.clients.view',
Owner

Mapping RBAC e2e volontairement grossier — gradations non couvertes jusqu'à ERP-74.

Contexte : les 5 permissions sont toutes accordées au seul persona e2e.user-full.

Cause : du coup aucun persona ne représente un utilisateur ayant commercial.clients.view/manage SANS accounting.manage ni archive. Or c'est précisément la différenciation que le ClientProcessor d'ERP-55 met en œuvre (gating 403 sur l'onglet Comptabilité, sur l'archivage, RG-1.28 strict). Ce comportement différentiel reste donc vérifié uniquement par les tests unitaires du Processor, pas de bout en bout.

Recommandation : acceptable et déjà documenté (les vrais rôles métier bureau/compta/commerciale/usine arrivent en ERP-74). Penser à ajouter à ce moment-là un persona « view sans accounting/archive » pour exercer le gating en e2e. Aucune action requise sur cette MR.

**Mapping RBAC e2e volontairement grossier — gradations non couvertes jusqu'à ERP-74.** **Contexte** : les 5 permissions sont toutes accordées au seul persona `e2e.user-full`. **Cause** : du coup aucun persona ne représente un utilisateur ayant `commercial.clients.view`/`manage` SANS `accounting.manage` ni `archive`. Or c'est précisément la différenciation que le ClientProcessor d'ERP-55 met en œuvre (gating 403 sur l'onglet Comptabilité, sur l'archivage, RG-1.28 strict). Ce comportement différentiel reste donc vérifié uniquement par les tests unitaires du Processor, pas de bout en bout. **Recommandation** : acceptable et déjà documenté (les vrais rôles métier bureau/compta/commerciale/usine arrivent en ERP-74). Penser à ajouter à ce moment-là un persona « view sans accounting/archive » pour exercer le gating en e2e. Aucune action requise sur cette MR.
tristan added the back label 2026-06-01 10:02:41 +00:00
matthieu added the type/feat label 2026-06-01 11:08:12 +00:00
Author
Owner

Suite review (merci) — pas de changement back, 2 points relevant de décisions tracées :

  • Route /commercial/clients (imbriquée) vs /suppliers (plate) : 🔵 décision front, à arbitrer ensemble côté ERP-73. Tant que le module commercial n'expose que pages/commercial.vue, je ne change pas la route côté back sans calage front commun.
  • Mapping RBAC e2e volontairement grossier : 🟢 volontaire et tracé. Les gradations (view/manage sans accounting.manage ni archive) seront couvertes via ERP-74 / ERP-60. Le ClientProcessor distingue déjà ces permissions ; seul le persona e2e est agrégé pour l'instant.

(Branche rebasée sur la stack corrigée → nouveau SHA.)

Suite review (merci) — pas de changement back, 2 points relevant de décisions tracées : - **Route `/commercial/clients` (imbriquée) vs `/suppliers` (plate)** : 🔵 décision **front**, à arbitrer ensemble côté ERP-73. Tant que le module `commercial` n'expose que `pages/commercial.vue`, je ne change pas la route côté back sans calage front commun. - **Mapping RBAC e2e volontairement grossier** : 🟢 volontaire et tracé. Les gradations (`view`/`manage` sans `accounting.manage` ni `archive`) seront couvertes via ERP-74 / ERP-60. Le `ClientProcessor` distingue déjà ces permissions ; seul le persona e2e est agrégé pour l'instant. _(Branche rebasée sur la stack corrigée → nouveau SHA.)_
malio changed target branch from feature/ERP-55-impl-provider-processor-client to develop 2026-06-01 19:28:06 +00:00
malio added 8 commits 2026-06-01 19:28:07 +00:00
fix(commercial) : down() orphan-only + index FK referentiels (review ERP-53)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 1m29s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m15s
034301ceaf
Entites metier (Client, ClientContact, ClientAddress, ClientRib) avec
#[Auditable] + Timestampable/Blamable, et 4 referentiels comptables statiques
(TvaMode, PaymentDelay, PaymentType, Bank). 8 repositories interfaces + impl
Doctrine. Aucun ApiResource (Provider/Processor = ERP-55).

- Client : 2 FK auto-referentes distributor/broker (mutuellement exclusives,
  CHECK en base), M2M categories, FK referentiels comptables, groupes de
  serialisation par onglet. Pas de #[ORM\UniqueConstraint] : unicite du nom de
  societe portee par l'index partiel Postgres (decision Q4).
- ClientRib : tous les champs audites, aucun #[AuditIgnore] sur iban/bic
  (decision 29/05, audit admin-only).
- M2M Category via le contrat Shared CategoryInterface + resolve_target_entities
  (regle n°1, pas d'import inter-modules) ; sites via SiteInterface.
- CommercialReferentialFixtures : re-seed idempotent des 4 referentiels (sinon
  vides apres db-reset car desormais tables mappees, purgees par les fixtures).
- Referentiels whitelistes dans EntitiesAreTimestampableBlamableTest::EXCLUDED.
- doctrine.yaml : mapping ORM du module Commercial + resolve CategoryInterface.
- ColumnCommentsCatalog : ajout des colonnes M1 (chemin schema:update/test) ;
  migration retrofit Version20260528120000 filtree sur les tables existantes
  pour ne pas casser sur les tables des modules crees plus tard.
- makefile test-db-setup : recreation de l'index partiel uq_client_company_name_active.

Refs ERP-54.
Branche l'API REST du repertoire clients (M1) sur l'entite Client preparee en
ERP-54. Operations GetCollection / Get / Post / Patch (pas de Delete au M1 :
l'archivage passe par PATCH isArchived).

ClientProvider :
- liste paginee (Paginator ORM, aligne sur la convention ERP-72) + echappatoire
  ?pagination=false
- exclut archives + soft-deletes par defaut (RG-1.24), ?includeArchived=true
  reintegre les archives (RG-1.25)
- tri companyName ASC (RG-1.26), filtres ?search (fuzzy companyName/lastName/
  email) et ?categoryType=<code>
- detail : 404 sur soft-delete, embarque contacts/adresses/ribs

ClientProcessor :
- normalisation serveur via ClientFieldNormalizer (RG-1.18 a 1.21)
- 409 sur doublon de nom de societe (RG-1.16) ; 409 dedie sur conflit de
  restauration (RG-1.23)
- gating par onglet : champ comptable -> accounting.manage, isArchived ->
  archive, mode strict 403 sur tout le payload (RG-1.28) ; archivage exclusif
  (RG-1.22) + pose/retrait archivedAt
- regles metier RG-1.01 (prenom/nom), RG-1.03 (distributor/broker exclusifs +
  controle du type de categorie), RG-1.12 (Virement -> banque), RG-1.13 (LCR ->
  >= 1 RIB), RG-1.04 (completude Information pour le role Commerciale)

Lecture comptable conditionnelle : ClientReadGroupContextBuilder ajoute le
groupe client:read:accounting selon commercial.clients.accounting.view.

Resolution des references categorie : CategoryReferenceDenormalizer resout les
IRI vers Category quand la propriete est type-hintee par le contrat
CategoryInterface (denormalisation impossible sur une interface sinon).

Contrats Shared :
- CategoryInterface::getCategoryTypeCode() (implemente par Category) pour la
  verification de type sans import inter-modules
- BusinessRoleAwareInterface (implemente par User) + BusinessRoles::COMMERCIALE
  pour detecter le role metier ; le code de role sera seede par ERP-74 et
  reutilise par ERP-59/60. RG-1.04 reste dormante tant qu'aucun user ne porte
  ce role.

Coordination stack :
- chaines de permission commercial.clients.* referencees ici, declarees en
  ERP-59 (tests RBAC complets en ERP-60)
- config globale de pagination (itemsPerPage client, max 50) portee par ERP-72
- referentiels comptables (PaymentType/Bank/...) exposes en ERP-56

Tests : 31 tests Commercial (integration admin sur les regles metier + unitaires
sur le gating, RG-1.04/1.12/1.13 et le context builder). Suite complete verte
(339 tests).
Ajoute CommercialModule::permissions() (5 codes commercial.clients.* :
view, manage, accounting.view, accounting.manage, archive) — alignes sur
les is_granted deja references par ERP-55 (Client ApiResource, ClientProcessor,
ClientReadGroupContextBuilder).

Synchronise les 3 sources RBAC (regle ABSOLUE n8) : item sidebar
"Repertoire clients" (commercial.clients.view), persona user-full dans
personas.ts et SeedE2ECommand.php, cle i18n sidebar.commercial.clients.

Les roles metier Bureau/Compta/Commerciale/Usine sont seedes par ERP-74 :
les 5 permissions sont mappees ici sur le seul persona technique user-full
en attendant, sans creer de nouveau persona (regle n7).
malio added 1 commit 2026-06-01 19:28:20 +00:00
[ERP-56] Exposer les 4 référentiels comptables en lecture seule (#34)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Failing after 54s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m11s
a5de67940b
## ERP-56 — Référentiels comptables en lecture seule

Expose les 4 référentiels comptables (M1 Commercial) en **lecture seule** via API Platform. Aucune nouvelle entité ni migration : seule la couche API (`#[ApiResource]`) est ajoutée sur des entités existantes (ERP-53/54).

> **Stacked PR** — base = `feature/ERP-59-declarer-permissions-commercial-clients-rbac` (la security `commercial.clients.view` est déclarée par ERP-59).

### Endpoints exposés
| Méthode | URL |
|---|---|
| `GET` | `/api/tva_modes` · `/api/tva_modes/{id}` |
| `GET` | `/api/payment_delays` · `/api/payment_delays/{id}` |
| `GET` | `/api/payment_types` · `/api/payment_types/{id}` |
| `GET` | `/api/banks` · `/api/banks/{id}` |

OpenAPI exposée automatiquement.

### Détails techniques
- **Opérations** : `GetCollection` + `Get` uniquement. Aucune écriture déclarée → `POST` / `PATCH` / `DELETE` renvoient **405**.
- **Security** : `is_granted('commercial.clients.view')` au niveau opérations **et** ressource.
- **Tri par défaut** : `position ASC` puis `label ASC` (spec § 4.7) via `order:` sur `GetCollection` (provider Doctrine par défaut, aligné sur le pattern `CategoryType` ERP-46 — pas de provider custom car référentiels sans filtre).
- **Pagination (ERP-72)** : pagination serveur conservée sur ces collections autonomes. `paginationClientEnabled: true` par opération pour activer l'échappatoire `?pagination=false` (alimenter un `<MalioSelect>` complet). Note : `client_enabled` est `false` globalement, d'où l'activation explicite par opération.

### Tests (`tests/Module/Commercial/Api/ReferentialApiTest.php`)
`make test` → **364 tests OK** (dont 21 nouveaux, 70 assertions) :
- 4 endpoints → **200** avec le seed (`CommercialReferentialFixtures`) ;
- tri **position ASC** vérifié + départage **label ASC** (lignes de test purgées en `tearDown`) ;
- `GET` item → 200 ;
- `POST` (×4) / `PATCH` / `DELETE` → **405** ;
- user authentifié sans `commercial.clients.view` → **403** ;
- anonyme → **401** ;
- pagination serveur active (page 2 vide) + `?pagination=false` cohérent.

`make php-cs-fixer-allow-risky` : clean.

### Review
Reviewer souhaité : @tristan
À **squash merge** (sélectionner manuellement dans l'UI Gitea).

---------

Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #34
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
matthieu closed this pull request 2026-06-01 21:09:34 +00:00
matthieu added the M1-Client label 2026-06-01 21:14:59 +00:00
Some checks are pending
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Failing after 54s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m11s

Pull request closed

Sign in to join this conversation.