0c9b563cae
Auto Tag Develop / tag (push) Successful in 9s
**MR stackée sur ERP-54** — cible = `feature/ERP-54-creer-entites-client-m1` (PAS `develop`). Tristan validera le stack en fin de chaîne. Branche l'API REST du répertoire clients (M1) sur l'entité `Client` d'ERP-54. ## Périmètre - **ClientProvider** : liste paginée (Paginator ORM aligné ERP-72, `?pagination=false`), exclusion archives+soft-delete par défaut (RG-1.24), `?includeArchived=true` (RG-1.25), tri `companyName ASC` (RG-1.26), filtres `?search` (fuzzy) + `?categoryType`, détail 404 si soft-deleted + embarque contacts/adresses/ribs. - **ClientProcessor** : normalisation (RG-1.18→1.21), 409 doublon nom (RG-1.16) + 409 restauration (RG-1.23), gating par onglet `accounting.manage`/`archive` + mode strict 403 (RG-1.28), archivage exclusif + `archivedAt` (RG-1.22), RG-1.01 / RG-1.03 (mutex + type catégorie) / RG-1.12 / RG-1.13 / RG-1.04. - **ClientReadGroupContextBuilder** : ajout conditionnel du groupe `client:read:accounting` selon `commercial.clients.accounting.view`. - **CategoryReferenceDenormalizer** : résout les IRI catégorie vers `Category` (dénormalisation impossible sur l'interface sinon). - **Contrats Shared** : `CategoryInterface::getCategoryTypeCode()`, `BusinessRoleAwareInterface` + `BusinessRoles::COMMERCIALE`. ## Coordination stack - Permissions `commercial.clients.*` **référencées** ici, déclarées en **ERP-59** (tests RBAC en **ERP-60**). - Rôle métier `commerciale` seedé par **ERP-74** (RG-1.04 dormante d'ici là). - Config globale pagination (itemsPerPage client / max 50) portée par **ERP-72**. - Référentiels comptables (PaymentType/Bank/...) exposés en **ERP-56** → RG-1.12/1.13 testées en unitaire ici (pas d'IRI référentiel disponible avant ERP-56). ## Tests 31 tests Commercial (intégration admin sur les RG métier + unitaires sur le gating / RG-1.04 / RG-1.12 / RG-1.13 / context builder). Suite complète verte (343 tests). Règle n°1 respectée (aucun import inter-modules dans Commercial). --------- Co-authored-by: tristan <tristan@yuno.malio.fr> Co-authored-by: Matthieu <contact@malio.fr> Co-authored-by: Matthieu <mtholot19@gmail.com> Reviewed-on: #31 Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr> Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
131 lines
4.5 KiB
PHP
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();
|
|
}
|
|
}
|