d6790dd37d
Auto Tag Develop / tag (push) Successful in 7s
ERP-94 (etape front 7/7 du M2). **Stack sur #97** (base = `feature/ERP-97-suppliers-i18n-sidebar`, elle-meme sur #93) pour un diff isole. A recibler sur `develop` une fois #93 (MR #81) et #97 (MR #82) mergees. Page « Ajouter un fournisseur » — **replique a l'identique le fonctionnement de l'ecran Client** (workflow inline par onglets, blocs reutilisables, validation 422 inline ERP-101), avec les specificites M2. ## Architecture (miroir Client) - Workflow par onglets **inline dans `suppliers/new.vue`** (comme `clients/new.vue` — il n'existe pas de `useClientForm` monolithique). Helpers paralleles : `useSupplierReferentials`, `useSupplierFormErrors`, `supplierFormRules`, `supplierEdit` (payloads), `types/supplierForm`. - Blocs `SupplierContactBlock` / `SupplierAddressBlock` (miroir des blocs Client). - POST `/suppliers` puis PATCH partiels par onglet (mode strict, groupes de serialisation). Sous-ressources : `/suppliers/{id}/contacts|addresses|ribs`. - Validation ERP-101 : 422 `violations[].propertyPath` mappees inline par champ (`useFormErrors` / `mapViolationsToRecord`), `{ toast: false }`, bouton Valider toujours actif. ## Specificites M2 (vs M1) - Formulaire principal **sans contact inline** (ERP-106) : Entreprise + Categorie (type FOURNISSEUR, `?typeCode=FOURNISSEUR`). - Adresse : **radio exclusif** Prospect/Depart/Rendu (`addressType` enum, RG-2.09), champs **Bennes** (stepper) + **Prestation de triage**, **pas d'email de facturation**. - Information : champ **Volume previsionnel** (8e champ). - Compta (Admin+Compta) : banque si VIREMENT (RG-2.07), RIB si LCR (RG-2.08) ; RIB sous-ressource gardee par `accounting.manage`. ## Tests (mirroir strategie Client) - `make nuxt-test` : 338 passed (specs ajoutees : supplierFormRules, supplierEdit, useSupplierReferentials, SupplierContactBlock, SupplierAddressBlock). - ESLint propre ; `nuxi typecheck` (lance en container) : **0 erreur**. - Golden path navigateur valide end-to-end : POST /suppliers OK, companyName normalise UPPERCASE (RG-2.12), gating des onglets (Information actif, Contacts deverrouille). ## Note de revue ~30 `WARN Duplicated imports` au typecheck : les helpers Supplier exportent les memes noms generiques que leurs equivalents Client (`buildMainPayload`, `omitEmptyRequired`, `RefOption`...), tous deux auto-importes par Nuxt. **Sans impact runtime** : tous les consommateurs utilisent des imports explicites (qui priment). Consequence directe du miroir 1:1 ; une factorisation des generiques dans `shared/` pourrait etre un suivi. Reviewed-on: #83 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
191 lines
6.1 KiB
PHP
191 lines
6.1 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 testLcrWithoutRibIsRejectedOnPaymentTypePath(): void
|
|
{
|
|
$supplier = $this->validSupplier();
|
|
$supplier->setPaymentType($this->paymentType('LCR'));
|
|
|
|
// Miroir client : la violation LCR -> >= 1 RIB est portee sur `paymentType`
|
|
// (affichee sous le select « Type de règlement », `ribs` n'ayant pas de champ).
|
|
self::assertContains('paymentType', $this->violationPaths($supplier));
|
|
}
|
|
|
|
public function testLcrWithRibPasses(): void
|
|
{
|
|
$supplier = $this->validSupplier();
|
|
$supplier->setPaymentType($this->paymentType('LCR'));
|
|
$supplier->addRib(new SupplierRib());
|
|
|
|
self::assertNotContains('paymentType', $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;
|
|
}
|
|
}
|