fix(commercial) : retrait RG-1.04 (onglet Information facultatif pour tous)
L'onglet Information n'est plus obligatoire pour le role metier Commerciale : il devient facultatif pour tous les roles, cote back comme cote front (le front l'etait deja). - Suppression du validateur ClientInformationCompletenessValidator et de son gating (validateInformationCompleteness / currentUserIsCommerciale) dans ClientProcessor. Security conserve (gating accounting/archive/manage). - Tests : retrait des 3 tests RG-1.04 (ClientProcessorTest) ; POST Commerciale attendu en 201 et suppression du test dedie (ClientRBACMatrixTest). - Coherence : commentaires de colonnes BDD (catalogue + migration d'init) passes a « Facultatif », nettoyage des references RG-1.04 (BusinessRoles, RbacSeeder, User, fixtures, front, specs M1, README). Le role metier Commerciale et ses permissions RBAC restent inchanges. - Pas de migration de schema (colonnes Information deja nullable).
This commit is contained in:
+3
-3
@@ -16,9 +16,9 @@ use Symfony\Component\Validator\ConstraintViolationList;
|
||||
* Type de reglement). La banque reste conditionnelle (RG-1.12) et les RIB aussi
|
||||
* (RG-1.13) : ils ne sont pas couverts ici.
|
||||
*
|
||||
* Calque sur ClientInformationCompletenessValidator (RG-1.04) : colonnes nullable
|
||||
* en base + validateur contextuel, plutot qu'un Assert\NotBlank sur l'entite (qui
|
||||
* casserait le POST de l'onglet principal, lequel n'envoie aucun champ comptable).
|
||||
* Parti pris : colonnes nullable en base + validateur contextuel, plutot qu'un
|
||||
* Assert\NotBlank sur l'entite (qui casserait le POST de l'onglet principal,
|
||||
* lequel n'envoie aucun champ comptable).
|
||||
*
|
||||
* Invoque par le ClientProcessor uniquement quand le payload porte les six champs
|
||||
* (= une validation d'onglet), jamais sur un PATCH ciblant un seul champ comptable.
|
||||
|
||||
-74
@@ -1,74 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Module\Commercial\Application\Validator;
|
||||
|
||||
use ApiPlatform\Validator\Exception\ValidationException;
|
||||
use App\Module\Commercial\Domain\Entity\Client;
|
||||
use Symfony\Component\Validator\ConstraintViolation;
|
||||
use Symfony\Component\Validator\ConstraintViolationList;
|
||||
|
||||
/**
|
||||
* Validator metier RG-1.04 (durcie ERP-74) : pour un utilisateur portant le
|
||||
* role metier Commerciale, TOUS les champs de l'onglet Information sont
|
||||
* obligatoires sur POST comme sur tout PATCH, independamment des champs
|
||||
* reellement envoyes.
|
||||
*
|
||||
* Invoque par le ClientProcessor des que l'utilisateur courant porte le role
|
||||
* Commerciale (plus de condition d'intersection avec l'onglet Information).
|
||||
* Pour les autres roles, ces champs restent optionnels — le validator n'est
|
||||
* pas appele.
|
||||
*
|
||||
* Leve une ValidationException (HTTP 422) listant chaque champ manquant, par
|
||||
* coherence avec les violations Symfony rendues par API Platform.
|
||||
*/
|
||||
final class ClientInformationCompletenessValidator
|
||||
{
|
||||
public function validate(Client $client): void
|
||||
{
|
||||
// Map champ -> valeur courante de l'onglet Information.
|
||||
$fields = [
|
||||
'description' => $client->getDescription(),
|
||||
'competitors' => $client->getCompetitors(),
|
||||
'foundedAt' => $client->getFoundedAt(),
|
||||
'employeesCount' => $client->getEmployeesCount(),
|
||||
'revenueAmount' => $client->getRevenueAmount(),
|
||||
'directorName' => $client->getDirectorName(),
|
||||
'profitAmount' => $client->getProfitAmount(),
|
||||
];
|
||||
|
||||
$violations = new ConstraintViolationList();
|
||||
|
||||
foreach ($fields as $property => $value) {
|
||||
if ($this->isMissing($value)) {
|
||||
$violations->add(new ConstraintViolation(
|
||||
sprintf('Ce champ est obligatoire pour le role Commerciale (champ "%s").', $property),
|
||||
null,
|
||||
[],
|
||||
$client,
|
||||
$property,
|
||||
$value,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if (count($violations) > 0) {
|
||||
throw new ValidationException($violations);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Une valeur est manquante si null ou, pour une chaine, vide apres trim.
|
||||
* Les zeros numeriques (employeesCount = 0, profitAmount = "0.00") sont des
|
||||
* valeurs valides : on ne les considere pas manquants.
|
||||
*/
|
||||
private function isMissing(mixed $value): bool
|
||||
{
|
||||
if (null === $value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return is_string($value) && '' === trim($value);
|
||||
}
|
||||
}
|
||||
+8
-37
@@ -9,11 +9,8 @@ use ApiPlatform\State\ProcessorInterface;
|
||||
use ApiPlatform\Validator\Exception\ValidationException;
|
||||
use App\Module\Commercial\Application\Service\ClientFieldNormalizer;
|
||||
use App\Module\Commercial\Application\Validator\ClientAccountingCompletenessValidator;
|
||||
use App\Module\Commercial\Application\Validator\ClientInformationCompletenessValidator;
|
||||
use App\Module\Commercial\Domain\Entity\Client;
|
||||
use App\Shared\Domain\Contract\BusinessRoleAwareInterface;
|
||||
use App\Shared\Domain\Contract\CategoryInterface;
|
||||
use App\Shared\Domain\Security\BusinessRoles;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
@@ -44,8 +41,8 @@ use Symfony\Component\Validator\ConstraintViolationList;
|
||||
* 2. Normalisation serveur (RG-1.18 a 1.21) via ClientFieldNormalizer.
|
||||
* 3. Regles metier : RG-1.03 (distributor/broker
|
||||
* exclusifs + type de categorie), RG-1.12 (Virement -> banque),
|
||||
* RG-1.13 (LCR -> >= 1 RIB), RG-1.04 (completude Information exigee sur POST
|
||||
* et tout PATCH pour le role Commerciale).
|
||||
* RG-1.13 (LCR -> >= 1 RIB). L'onglet Information est entierement facultatif
|
||||
* (RG-1.04 retiree : plus d'obligation, y compris pour le role Commerciale).
|
||||
* 4. Pose / retrait de archivedAt (RG-1.22 true=now, RG-1.23 false=null).
|
||||
* 5. Persistance via le persist_processor Doctrine, avec traduction des
|
||||
* collisions d'unicite en 409 (RG-1.16 doublon de nom ; RG-1.23 conflit de
|
||||
@@ -108,7 +105,6 @@ final class ClientProcessor implements ProcessorInterface
|
||||
#[Autowire(service: 'api_platform.doctrine.orm.state.persist_processor')]
|
||||
private readonly ProcessorInterface $persistProcessor,
|
||||
private readonly ClientFieldNormalizer $normalizer,
|
||||
private readonly ClientInformationCompletenessValidator $informationValidator,
|
||||
private readonly ClientAccountingCompletenessValidator $accountingValidator,
|
||||
private readonly Security $security,
|
||||
private readonly RequestStack $requestStack,
|
||||
@@ -136,7 +132,6 @@ final class ClientProcessor implements ProcessorInterface
|
||||
$this->validateDistributorBroker($data);
|
||||
$this->validateAccountingConsistency($data);
|
||||
$this->validateAccountingCompleteness($data);
|
||||
$this->validateInformationCompleteness($data);
|
||||
|
||||
try {
|
||||
return $this->persistProcessor->process($data, $operation, $uriVariables, $context);
|
||||
@@ -505,9 +500,9 @@ final class ClientProcessor implements ProcessorInterface
|
||||
* ce n'est pas une validation d'onglet (edition ponctuelle preservee). bank /
|
||||
* RIB restent geres par validateAccountingConsistency (RG-1.12 / RG-1.13).
|
||||
*
|
||||
* Colonnes nullable en base + validateur contextuel (meme parti que RG-1.04) :
|
||||
* un Assert\NotBlank sur l'entite casserait le POST de l'onglet principal, qui
|
||||
* n'envoie aucun champ comptable.
|
||||
* Colonnes nullable en base + validateur contextuel : un Assert\NotBlank sur
|
||||
* l'entite casserait le POST de l'onglet principal, qui n'envoie aucun champ
|
||||
* comptable.
|
||||
*/
|
||||
private function validateAccountingCompleteness(Client $data): void
|
||||
{
|
||||
@@ -520,21 +515,6 @@ final class ClientProcessor implements ProcessorInterface
|
||||
$this->accountingValidator->validate($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* RG-1.04 (durcie ERP-74) : si l'utilisateur porte le role metier
|
||||
* Commerciale, TOUS les champs de l'onglet Information sont obligatoires sur
|
||||
* POST comme sur TOUT PATCH — independamment des champs reellement envoyes
|
||||
* (plus de condition d'intersection avec INFORMATION_FIELDS). Garantit qu'un
|
||||
* client cree/edite par une Commerciale ne reste jamais avec un onglet
|
||||
* Information incomplet.
|
||||
*/
|
||||
private function validateInformationCompleteness(Client $data): void
|
||||
{
|
||||
if ($this->currentUserIsCommerciale()) {
|
||||
$this->informationValidator->validate($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vrai si au moins une categorie du client porte le code donne. S'appuie sur
|
||||
* CategoryInterface::getCode() (pas d'import de Category — regle ABSOLUE n°1).
|
||||
@@ -550,21 +530,12 @@ final class ClientProcessor implements ProcessorInterface
|
||||
return false;
|
||||
}
|
||||
|
||||
private function currentUserIsCommerciale(): bool
|
||||
{
|
||||
$user = $this->security->getUser();
|
||||
|
||||
return $user instanceof BusinessRoleAwareInterface
|
||||
&& $user->hasBusinessRole(BusinessRoles::COMMERCIALE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cles ecrivables effectivement presentes dans le payload : on retire les
|
||||
* cles JSON-LD (@id, @context, @var...) et tout champ non rattache a un
|
||||
* groupe d'ecriture connu. C'est la base du 422 d'archivage (RG-1.22) et du
|
||||
* declenchement conditionnel de RG-1.04 — sans elles, un PATCH
|
||||
* « representation complete » porteur de @id ferait croire a une
|
||||
* modification multi-onglets.
|
||||
* groupe d'ecriture connu. C'est la base du 422 d'archivage (RG-1.22) —
|
||||
* sans elles, un PATCH « representation complete » porteur de @id ferait
|
||||
* croire a une modification multi-onglets.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
|
||||
@@ -234,7 +234,7 @@ class ClientFixtures extends Fixture implements DependentFixtureInterface
|
||||
$this->addAddress($services, ['Chatellerault'], '86100', 'Châtellerault', '15 rue du Conseil', isDelivery: true);
|
||||
}
|
||||
|
||||
// === Onglet Information complet (RG-1.04) ===
|
||||
// === Onglet Information complet (exemple de fiche renseignee) ===
|
||||
[$holding, $isNew] = $this->ensureClient(
|
||||
$manager,
|
||||
companyName: 'Holding Premium Invest',
|
||||
|
||||
@@ -36,8 +36,8 @@ final class RbacSeeder
|
||||
{
|
||||
/**
|
||||
* Codes des roles metier (snake_case, regex Role respectee). `commerciale`
|
||||
* reference la constante Shared deja consommee par le ClientProcessor
|
||||
* (RG-1.04) pour eviter tout drift : un seul litteral pour ce code.
|
||||
* reference la constante Shared pour eviter tout drift : un seul litteral
|
||||
* pour ce code.
|
||||
*/
|
||||
public const string ROLE_BUREAU = 'bureau';
|
||||
public const string ROLE_COMPTA = 'compta';
|
||||
|
||||
@@ -344,8 +344,8 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, Busines
|
||||
/**
|
||||
* Implemente BusinessRoleAwareInterface : vrai si l'un des roles RBAC
|
||||
* rattaches porte le code donne. Permet aux modules tiers de detecter un
|
||||
* role metier (ex: `commerciale` pour RG-1.04 du M1 Clients) sans importer
|
||||
* cette classe. Comparaison stricte sur Role::code.
|
||||
* role metier (ex: `commerciale`) sans importer cette classe. Comparaison
|
||||
* stricte sur Role::code.
|
||||
*/
|
||||
public function hasBusinessRole(string $roleCode): bool
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user