Files
Starseed/tests/Module/Commercial/Api/AbstractCommercialApiTestCase.php
T
Matthieu e1b8f8a28d feat(commercial) : add Client API Platform provider + processor + business rules
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).
2026-05-29 16:41:40 +02:00

131 lines
4.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Tests\Module\Commercial\Api;
use ApiPlatform\Symfony\Bundle\Test\Client;
use App\Module\Catalog\Domain\Entity\Category;
use App\Module\Catalog\Domain\Entity\CategoryType;
use App\Module\Commercial\Domain\Entity\Client as ClientEntity;
use App\Module\Core\Domain\Entity\Role;
use App\Module\Core\Domain\Entity\User;
use App\Tests\Module\Core\Api\AbstractApiTestCase;
use DateTimeImmutable;
/**
* Base des tests fonctionnels du module Commercial (M1 — repertoire clients).
*
* Etend la base Core : ajoute des factories pour seeder vite des categories
* typees (DISTRIBUTEUR / COURTIER / SECTEUR) et des clients, plus un helper
* d'authentification admin.
*
* Cleanup : tearDown purge clients, categories `test_cli_cat_*` et users/roles
* `test_*`. Les category_types business sont fetch-or-create (idempotents) et
* laisses en place (pas de DELETE pour ne pas entrer en conflit avec d'autres
* suites). Pas de DAMA en local -> purge manuelle obligatoire.
*
* @internal
*/
abstract class AbstractCommercialApiTestCase extends AbstractApiTestCase
{
protected const string TEST_CATEGORY_PREFIX = 'test_cli_cat_';
protected function tearDown(): void
{
$this->cleanupCommercialTestData();
parent::tearDown();
}
protected function createAdminClient(): Client
{
return $this->authenticatedClient('admin', 'admin');
}
/**
* Recupere (ou cree) un CategoryType par son code metier. Idempotent : la
* contrainte d'unicite sur category_type.code interdit les doublons.
*/
protected function createCategoryType(string $code): CategoryType
{
$em = $this->getEm();
$existing = $em->getRepository(CategoryType::class)->findOneBy(['code' => $code]);
if (null !== $existing) {
return $existing;
}
$type = new CategoryType();
$type->setCode($code);
$type->setLabel(ucfirst(strtolower($code)));
$em->persist($type);
$em->flush();
return $type;
}
/**
* Cree une Category de test rattachee a un type metier donne (code).
*/
protected function createCategory(string $typeCode = 'SECTEUR'): Category
{
$em = $this->getEm();
$suffix = substr(bin2hex(random_bytes(4)), 0, 8);
$category = new Category();
$category->setName(self::TEST_CATEGORY_PREFIX.$suffix);
$category->setCategoryType($this->createCategoryType($typeCode));
$em->persist($category);
$em->flush();
return $category;
}
/**
* Seede directement un Client en base (sans passer par l'API), pour les
* tests de liste / archivage. Le client porte une categorie SECTEUR.
*/
protected function seedClient(string $companyName, bool $isArchived = false, string $categoryTypeCode = 'SECTEUR'): ClientEntity
{
$em = $this->getEm();
$client = new ClientEntity();
// Stocke en MAJUSCULES pour refleter l'etat normalise (RG-1.18) qu'aurait
// produit le ClientProcessor via l'API.
$client->setCompanyName(mb_strtoupper($companyName, 'UTF-8'));
$client->setLastName('Seed');
$client->setPhonePrimary('0102030405');
$client->setEmail(strtolower(str_replace(' ', '', $companyName)).'@seed.test');
$client->addCategory($this->createCategory($categoryTypeCode));
$client->setIsArchived($isArchived);
if ($isArchived) {
$client->setArchivedAt(new DateTimeImmutable());
}
$em->persist($client);
$em->flush();
return $client;
}
private function cleanupCommercialTestData(): void
{
$em = $this->getEm();
// Clients d'abord (la jointure client_category est purgee par
// ON DELETE CASCADE ; les auto-references distributor/broker sont
// ON DELETE SET NULL).
$em->createQuery('DELETE FROM '.ClientEntity::class)->execute();
// Categories de test ensuite (FK client_category deja purgee).
$em->createQuery(
'DELETE FROM '.Category::class.' c WHERE c.name LIKE :prefix',
)->setParameter('prefix', self::TEST_CATEGORY_PREFIX.'%')->execute();
// Users / roles jetables.
$em->createQuery(
'DELETE FROM '.User::class.' u WHERE u.username LIKE :prefix',
)->setParameter('prefix', 'test_%')->execute();
$em->createQuery(
'DELETE FROM '.Role::class.' r WHERE r.code LIKE :prefix',
)->setParameter('prefix', 'test_%')->execute();
}
}