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:
@@ -14,8 +14,8 @@ use App\Module\Sites\Domain\Entity\Site;
|
||||
*
|
||||
* Authentifies en ADMIN (bypass RBAC via isAdmin) : on valide ici les regles
|
||||
* METIER (normalisation, unicite, distributor/broker, archivage, liste). Le
|
||||
* gating par permission (accounting.manage / archive / RG-1.28 strict, RG-1.04
|
||||
* Commerciale) est couvert par les tests unitaires du ClientProcessor : il
|
||||
* gating par permission (accounting.manage / archive / RG-1.28 strict) est
|
||||
* couvert par les tests unitaires du ClientProcessor : il
|
||||
* exige des users non-admin portant des permissions `commercial.clients.*` qui
|
||||
* ne sont declarees qu'en ERP-59 (tests RBAC complets en ERP-60).
|
||||
*
|
||||
|
||||
@@ -14,8 +14,7 @@ use Symfony\Component\Console\Output\NullOutput;
|
||||
/**
|
||||
* Matrice RBAC complete du repertoire clients par role metier (spec-back M1
|
||||
* § 2.7 + cahier ERP-74). Valide 200/403 par verbe et par onglet pour
|
||||
* bureau / compta / commerciale / usine, plus le durcissement RG-1.04
|
||||
* (Commerciale) au POST.
|
||||
* bureau / compta / commerciale / usine.
|
||||
*
|
||||
* Les comptes demo et la matrice sont seedes via la commande reelle
|
||||
* `app:seed-rbac --with-demo-users` (le MEME chemin qu'en recette), idempotente.
|
||||
@@ -174,14 +173,14 @@ final class ClientRBACMatrixTest extends AbstractCommercialApiTestCase
|
||||
$client->request('GET', '/api/clients', ['headers' => ['Accept' => self::LD]]);
|
||||
self::assertResponseStatusCodeSame(200);
|
||||
|
||||
// manage : la creation passe la security d'operation (pas un 403 comme
|
||||
// Compta) mais bute sur RG-1.04 (onglet Information incomplet) -> 422.
|
||||
// C'est la preuve que Commerciale porte `manage` (sinon 403).
|
||||
// manage : la creation passe la security d'operation et aboutit -> 201
|
||||
// (l'onglet Information est facultatif pour tous depuis le retrait de
|
||||
// RG-1.04). C'est la preuve que Commerciale porte `manage` (sinon 403).
|
||||
$client->request('POST', '/api/clients', [
|
||||
'headers' => ['Content-Type' => self::LD],
|
||||
'json' => $this->validMainPayload('Commerciale Post'),
|
||||
]);
|
||||
self::assertResponseStatusCodeSame(422);
|
||||
self::assertResponseStatusCodeSame(201);
|
||||
|
||||
// PAS accounting : edition onglet Comptabilite refusee
|
||||
$client->request('PATCH', '/api/clients/'.$seed->getId(), [
|
||||
@@ -198,27 +197,6 @@ final class ClientRBACMatrixTest extends AbstractCommercialApiTestCase
|
||||
self::assertResponseStatusCodeSame(403);
|
||||
}
|
||||
|
||||
public function testRG104CommercialePostIncompleteIs422AdminIs201(): void
|
||||
{
|
||||
$cat = $this->createCategory('SECTEUR');
|
||||
|
||||
// RG-1.04 durcie : Commerciale POST sans onglet Information complet -> 422.
|
||||
$commerciale = $this->authAs('commerciale');
|
||||
$commerciale->request('POST', '/api/clients', [
|
||||
'headers' => ['Content-Type' => self::LD],
|
||||
'json' => $this->validMainPayload('RG104 Commerciale', $cat->getId()),
|
||||
]);
|
||||
self::assertResponseStatusCodeSame(422);
|
||||
|
||||
// Meme payload par un Admin (non gate par RG-1.04) -> 201.
|
||||
$admin = $this->createAdminClient();
|
||||
$admin->request('POST', '/api/clients', [
|
||||
'headers' => ['Content-Type' => self::LD],
|
||||
'json' => $this->validMainPayload('RG104 Admin', $cat->getId()),
|
||||
]);
|
||||
self::assertResponseStatusCodeSame(201);
|
||||
}
|
||||
|
||||
public function testComptaFullRepresentationPatchWithUnchangedCategoriesIsNotForbidden(): void
|
||||
{
|
||||
// FIX review MR #40 : un Compta (accounting.manage, PAS manage) faisant un
|
||||
|
||||
@@ -12,8 +12,8 @@ namespace App\Tests\Module\Commercial\Api;
|
||||
* - 403 si l'utilisateur authentifie ne porte pas `commercial.clients.view`.
|
||||
*
|
||||
* ⚠ La matrice RBAC differenciee par role metier (bureau / compta / commerciale
|
||||
* / usine) et le test fonctionnel RG-1.04 sont DELEGUES a ERP-74 (#493) : ils
|
||||
* exigent les roles seedes apres le merge de la stack. NE PAS les ajouter ici.
|
||||
* / usine) est DELEGUEE a ERP-74 (#493) : elle exige les roles seedes apres le
|
||||
* merge de la stack. NE PAS l'ajouter ici.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
|
||||
@@ -9,7 +9,6 @@ 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\Bank;
|
||||
use App\Module\Commercial\Domain\Entity\Client;
|
||||
use App\Module\Commercial\Domain\Entity\ClientRib;
|
||||
@@ -17,8 +16,6 @@ use App\Module\Commercial\Domain\Entity\PaymentDelay;
|
||||
use App\Module\Commercial\Domain\Entity\PaymentType;
|
||||
use App\Module\Commercial\Domain\Entity\TvaMode;
|
||||
use App\Module\Commercial\Infrastructure\ApiPlatform\State\Processor\ClientProcessor;
|
||||
use App\Shared\Domain\Contract\BusinessRoleAwareInterface;
|
||||
use App\Shared\Domain\Security\BusinessRoles;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@@ -27,13 +24,11 @@ use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* Tests unitaires du ClientProcessor : gating par permission (accounting.manage
|
||||
* / archive / RG-1.28 strict) et regles metier non testables en HTTP admin
|
||||
* (RG-1.04 Commerciale, RG-1.12 Virement, RG-1.13 LCR), grace a un Security et
|
||||
* un RequestStack stubbes.
|
||||
* (RG-1.12 Virement, RG-1.13 LCR), grace a un Security et un RequestStack stubbes.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
@@ -342,62 +337,6 @@ final class ClientProcessorTest extends TestCase
|
||||
self::assertInstanceOf(Client::class, $processor->process($client, $this->operation()));
|
||||
}
|
||||
|
||||
public function testCommercialeIncompleteInformationIsUnprocessable(): void
|
||||
{
|
||||
// RG-1.04 : role Commerciale + onglet Information incomplet -> 422.
|
||||
$client = $this->minimalClient();
|
||||
$client->setDescription('Une description'); // les autres champs Information restent null
|
||||
|
||||
$processor = $this->makeProcessor(
|
||||
granted: [],
|
||||
payload: ['description' => 'Une description'],
|
||||
user: $this->commercialeUser(),
|
||||
);
|
||||
|
||||
$this->expectException(ValidationException::class);
|
||||
$processor->process($client, $this->operation());
|
||||
}
|
||||
|
||||
public function testCommercialeIncompleteInformationOnNonInformationPatchIsUnprocessable(): void
|
||||
{
|
||||
// RG-1.04 durcie (ERP-74) : pour une Commerciale, la completude de
|
||||
// l'onglet Information est exigee meme quand le payload ne touche PAS
|
||||
// l'onglet Information (ici seulement companyName). L'ancienne condition
|
||||
// d'intersection avec INFORMATION_FIELDS a ete retiree.
|
||||
$client = $this->minimalClient();
|
||||
$client->setCompanyName('Renamed Co'); // onglet principal uniquement, Information vide
|
||||
|
||||
$processor = $this->makeProcessor(
|
||||
granted: ['commercial.clients.manage'],
|
||||
payload: ['companyName' => 'Renamed Co'],
|
||||
user: $this->commercialeUser(),
|
||||
managed: true,
|
||||
originalData: [
|
||||
'companyName' => 'TEST CO',
|
||||
'triageService' => false,
|
||||
'isArchived' => false,
|
||||
],
|
||||
);
|
||||
|
||||
$this->expectException(ValidationException::class);
|
||||
$processor->process($client, $this->operation());
|
||||
}
|
||||
|
||||
public function testNonCommercialeSkipsInformationCompleteness(): void
|
||||
{
|
||||
// Meme payload incomplet, mais user non-Commerciale -> aucun blocage.
|
||||
$client = $this->minimalClient();
|
||||
$client->setDescription('Une description');
|
||||
|
||||
$processor = $this->makeProcessor(
|
||||
granted: [],
|
||||
payload: ['description' => 'Une description'],
|
||||
user: null,
|
||||
);
|
||||
|
||||
self::assertInstanceOf(Client::class, $processor->process($client, $this->operation()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $granted Permissions accordees a l'utilisateur courant
|
||||
* @param array<string, mixed> $payload Corps JSON simule de la requete
|
||||
@@ -407,7 +346,6 @@ final class ClientProcessorTest extends TestCase
|
||||
private function makeProcessor(
|
||||
array $granted,
|
||||
array $payload,
|
||||
?UserInterface $user = null,
|
||||
bool $managed = false,
|
||||
array $originalData = [],
|
||||
): ClientProcessor {
|
||||
@@ -422,7 +360,6 @@ final class ClientProcessorTest extends TestCase
|
||||
$security->method('isGranted')->willReturnCallback(
|
||||
static fn (mixed $attribute): bool => is_string($attribute) && in_array($attribute, $granted, true),
|
||||
);
|
||||
$security->method('getUser')->willReturn($user);
|
||||
|
||||
$requestStack = new RequestStack();
|
||||
$requestStack->push(new Request([], [], [], [], [], [], json_encode($payload, JSON_THROW_ON_ERROR)));
|
||||
@@ -440,7 +377,6 @@ final class ClientProcessorTest extends TestCase
|
||||
return new ClientProcessor(
|
||||
$persist,
|
||||
new ClientFieldNormalizer(),
|
||||
new ClientInformationCompletenessValidator(),
|
||||
new ClientAccountingCompletenessValidator(),
|
||||
$security,
|
||||
$requestStack,
|
||||
@@ -493,26 +429,4 @@ final class ClientProcessorTest extends TestCase
|
||||
{
|
||||
return $this->createStub(Operation::class);
|
||||
}
|
||||
|
||||
private function commercialeUser(): UserInterface
|
||||
{
|
||||
return new class implements UserInterface, BusinessRoleAwareInterface {
|
||||
public function hasBusinessRole(string $roleCode): bool
|
||||
{
|
||||
return BusinessRoles::COMMERCIALE === $roleCode;
|
||||
}
|
||||
|
||||
public function getRoles(): array
|
||||
{
|
||||
return ['ROLE_USER'];
|
||||
}
|
||||
|
||||
public function eraseCredentials(): void {}
|
||||
|
||||
public function getUserIdentifier(): string
|
||||
{
|
||||
return 'commerciale-test';
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user