f813c1732e
Volet A — relation Category <-> CategoryType passee de ManyToOne a ManyToMany (jonction category_category_type). Au moins un type obligatoire (Assert\Count), unicite du nom desormais GLOBALE parmi les actifs (uq_category_name_active). Migration avec backfill + drop de l'ancienne colonne. Contrat Shared CategoryInterface : getCategoryTypeCode() -> getCategoryTypeCodes(): array ; RG-2.10 fournisseurs (Supplier / SupplierAddress / fixtures) revalident « contient FOURNISSEUR ». Provider/Repository : filtre type via sous-requete EXISTS (sans tronquer la collection embarquee), eager-load anti-N+1. Volet B — bouton « Filtres » sur la liste des categories (recherche nom + types multi en OR), sur le modele du Repertoire Clients ; etat local, jamais persiste en URL. Filtres back ?name= et ?typeId[]= sur la collection. Front : multi-select MalioSelectCheckbox, useCategoryForm en categoryTypeIds[], colonne « Types », i18n. ColumnCommentsCatalog + makefile test-db-setup alignes sur le nouvel index partiel. Tests Catalog/Commercial adaptes + CategoryFilterTest.
189 lines
5.9 KiB
PHP
189 lines
5.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Module\Commercial\Domain\Entity;
|
|
|
|
use App\Module\Commercial\Domain\Entity\Bank;
|
|
use App\Module\Commercial\Domain\Entity\PaymentType;
|
|
use App\Module\Commercial\Domain\Entity\Supplier;
|
|
use App\Module\Commercial\Domain\Entity\SupplierRib;
|
|
use App\Shared\Domain\Contract\CategoryInterface;
|
|
use PHPUnit\Framework\TestCase;
|
|
use Symfony\Component\Validator\Validation;
|
|
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
|
|
|
/**
|
|
* Tests des contraintes inter-champs de l'entite Supplier portees par
|
|
* Assert\Callback (decision figee ERP-89) : RG-2.10 (categorie de type
|
|
* FOURNISSEUR), RG-2.07 (Virement -> banque), RG-2.08 (LCR -> >= 1 RIB).
|
|
*
|
|
* On valide l'entite avec le validator Symfony (mapping par attributs) et on
|
|
* assert le propertyPath exact de chaque violation (contrat ERP-101 :
|
|
* exploitable par extractApiViolations). Pas de base : les Callback ne touchent
|
|
* que des champs en memoire (categories via un double CategoryInterface).
|
|
*
|
|
* @internal
|
|
*/
|
|
final class SupplierValidationTest extends TestCase
|
|
{
|
|
private ValidatorInterface $validator;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->validator = Validation::createValidatorBuilder()
|
|
->enableAttributeMapping()
|
|
->getValidator()
|
|
;
|
|
}
|
|
|
|
// === RG-2.10 : categories de type FOURNISSEUR ===
|
|
|
|
public function testFournisseurCategoryIsAccepted(): void
|
|
{
|
|
$supplier = $this->validSupplier();
|
|
|
|
self::assertSame([], $this->violationPaths($supplier));
|
|
}
|
|
|
|
public function testNonFournisseurCategoryIsRejectedOnCategoriesPath(): void
|
|
{
|
|
$supplier = new Supplier();
|
|
$supplier->setCompanyName('Recycla SAS');
|
|
$supplier->addCategory($this->category('CLIENT'));
|
|
|
|
self::assertContains('categories', $this->violationPaths($supplier));
|
|
}
|
|
|
|
public function testMultiTypeCategoryContainingFournisseurIsAccepted(): void
|
|
{
|
|
// RG-2.10 sous ManyToMany : une categorie qui PORTE FOURNISSEUR (parmi
|
|
// d'autres types) reste autorisee sur un fournisseur.
|
|
$supplier = new Supplier();
|
|
$supplier->setCompanyName('Recycla SAS');
|
|
$supplier->addCategory($this->category('CLIENT', 'FOURNISSEUR'));
|
|
|
|
self::assertNotContains('categories', $this->violationPaths($supplier));
|
|
}
|
|
|
|
// === RG-2.07 : Virement impose une banque ===
|
|
|
|
public function testVirementWithoutBankIsRejectedOnBankPath(): void
|
|
{
|
|
$supplier = $this->validSupplier();
|
|
$supplier->setPaymentType($this->paymentType('VIREMENT'));
|
|
|
|
self::assertContains('bank', $this->violationPaths($supplier));
|
|
}
|
|
|
|
public function testVirementWithBankPasses(): void
|
|
{
|
|
$supplier = $this->validSupplier();
|
|
$supplier->setPaymentType($this->paymentType('VIREMENT'));
|
|
$supplier->setBank(new Bank());
|
|
|
|
self::assertNotContains('bank', $this->violationPaths($supplier));
|
|
}
|
|
|
|
// === RG-2.08 : LCR impose au moins un RIB ===
|
|
|
|
public function testLcrWithoutRibIsRejectedOnRibsPath(): void
|
|
{
|
|
$supplier = $this->validSupplier();
|
|
$supplier->setPaymentType($this->paymentType('LCR'));
|
|
|
|
self::assertContains('ribs', $this->violationPaths($supplier));
|
|
}
|
|
|
|
public function testLcrWithRibPasses(): void
|
|
{
|
|
$supplier = $this->validSupplier();
|
|
$supplier->setPaymentType($this->paymentType('LCR'));
|
|
$supplier->addRib(new SupplierRib());
|
|
|
|
self::assertNotContains('ribs', $this->violationPaths($supplier));
|
|
}
|
|
|
|
public function testNeutralPaymentTypeRequiresNeitherBankNorRib(): void
|
|
{
|
|
// Un type de reglement neutre (ni VIREMENT ni LCR) n'exige ni banque ni RIB.
|
|
$supplier = $this->validSupplier();
|
|
$supplier->setPaymentType($this->paymentType('CHEQUE'));
|
|
|
|
$paths = $this->violationPaths($supplier);
|
|
self::assertNotContains('bank', $paths);
|
|
self::assertNotContains('ribs', $paths);
|
|
}
|
|
|
|
/**
|
|
* Fournisseur valide (nom + 1 categorie FOURNISSEUR), sans onglet
|
|
* Comptabilite renseigne : sert de base aux tests RG-2.07/2.08.
|
|
*/
|
|
private function validSupplier(): Supplier
|
|
{
|
|
$supplier = new Supplier();
|
|
$supplier->setCompanyName('Recycla SAS');
|
|
$supplier->addCategory($this->category('FOURNISSEUR'));
|
|
|
|
return $supplier;
|
|
}
|
|
|
|
/**
|
|
* @return list<string> propertyPaths des violations levees par le validator
|
|
*/
|
|
private function violationPaths(Supplier $supplier): array
|
|
{
|
|
$paths = [];
|
|
foreach ($this->validator->validate($supplier) as $violation) {
|
|
$paths[] = $violation->getPropertyPath();
|
|
}
|
|
|
|
return $paths;
|
|
}
|
|
|
|
/**
|
|
* Double minimal de CategoryInterface (pas d'acces base) PORTANT les codes de
|
|
* type voulus — seul element regarde par validateCategoryType. Variadic pour
|
|
* couvrir le cas multi-types (ManyToMany).
|
|
*
|
|
* @return list<string> n'est pas le type de retour : helper renvoyant un double
|
|
*/
|
|
private function category(string ...$typeCodes): CategoryInterface
|
|
{
|
|
return new class(array_values($typeCodes)) implements CategoryInterface {
|
|
/** @param list<string> $typeCodes */
|
|
public function __construct(private readonly array $typeCodes) {}
|
|
|
|
public function getId(): ?int
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
public function getName(): ?string
|
|
{
|
|
return 'Categorie test';
|
|
}
|
|
|
|
public function getCode(): ?string
|
|
{
|
|
return 'TEST';
|
|
}
|
|
|
|
/** @return list<string> */
|
|
public function getCategoryTypeCodes(): array
|
|
{
|
|
return $this->typeCodes;
|
|
}
|
|
};
|
|
}
|
|
|
|
private function paymentType(string $code): PaymentType
|
|
{
|
|
$type = new PaymentType();
|
|
$type->setCode($code);
|
|
$type->setLabel($code);
|
|
|
|
return $type;
|
|
}
|
|
}
|