e139d234a9
Auto Tag Develop / tag (push) Successful in 8s
## Contexte (ERP-110, dérivé de ERP-107) Sur les onglets à blocs dynamiques d'un client (Contacts / Adresses / RIB), le POST d'une sous-ressource sur un client ayant déjà **≥2 enfants** renvoyait une **500 `NonUniqueResultException`**, court-circuitant la validation (aucune 422 par champ). ## Cause racine Au stade « read » du POST, le `Link` `toProperty` faisait résoudre la collection enfant via `getOneOrNullResult()` (`ItemProvider`) : `SELECT o FROM ClientContact o INNER JOIN o.client c WHERE c.id = :clientId`. Dès 2 enfants → `NonUniqueResult` → 500 **avant** la déserialisation/validation. Les 3 sous-ressources partageaient la même config (même bug latent). Cause secondaire front : la boucle de soumission s'arrêtait au 1er bloc en erreur (`return` dans le `catch`). ## Correctif **Back** — `read: false` sur les 3 opérations `Post` (`ClientContact` / `ClientAddress` / `ClientRib`) : le parent est déjà rattaché manuellement par le `*Processor::linkParent`. Les 3 `linkParent` sont durcis (`NotFoundHttpException` si parent absent → **404 préservé**, sinon régression 500 au persist sur `client_id NOT NULL`). **Front** — nouveau helper `useClientFormErrors().submitRows()` qui tente **tous** les blocs et collecte les erreurs 422 par index (`hasError`), branché sur les 6 sites (`new.vue` + `edit.vue` × contacts/adresses/RIB). Feedback **inline seul** : pas de toast récap, pas de toast succès tant qu'un bloc reste en erreur. ## Tests - Back : non-régression POST contact/adresse/RIB sur client déjà peuplé (≥2 enfants) → 201, + 422 `propertyPath=email` (validation atteinte). Rouge avant fix (500), vert après. - Front : `submitRows` (Vitest) — tente tous les blocs, mappe les erreurs par index, n'arrête pas au 1er échec, délègue le fallback non-422, saute les blocs filtrés. ## Vérifications - `make test` : 474/474 OK - `make php-cs-fixer-allow-risky` : 0 fichier à corriger - `make nuxt-test` : 219/219 OK > Golden path manuel navigateur non exécuté (couvert par les tests automatisés). --------- Co-authored-by: tristan <tristan@yuno.malio.fr> Co-authored-by: Matthieu <contact@malio.fr> Reviewed-on: #61 Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr> Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
78 lines
2.7 KiB
PHP
78 lines
2.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Module\Commercial\Application\Validator;
|
|
|
|
use ApiPlatform\Validator\Exception\ValidationException;
|
|
use App\Module\Commercial\Domain\Entity\Client;
|
|
use Symfony\Component\Validator\ConstraintViolation;
|
|
use Symfony\Component\Validator\ConstraintViolationList;
|
|
|
|
/**
|
|
* Validator metier (spec-front § Onglet Comptabilite) : a la soumission complete
|
|
* de l'onglet Comptabilite, les six champs scalaires obligatoires doivent etre
|
|
* renseignes (SIREN, Numero de compte, Mode de TVA, N de TVA, Delai de reglement,
|
|
* Type de reglement). La banque reste conditionnelle (RG-1.12) et les RIB aussi
|
|
* (RG-1.13) : ils ne sont pas couverts ici.
|
|
*
|
|
* Calque sur ClientInformationCompletenessValidator (RG-1.04) : colonnes nullable
|
|
* en base + validateur contextuel, plutot qu'un Assert\NotBlank sur l'entite (qui
|
|
* casserait le POST de l'onglet principal, lequel n'envoie aucun champ comptable).
|
|
*
|
|
* Invoque par le ClientProcessor uniquement quand le payload porte les six champs
|
|
* (= une validation d'onglet), jamais sur un PATCH ciblant un seul champ comptable.
|
|
*
|
|
* Leve une ValidationException (HTTP 422) listant chaque champ manquant, par
|
|
* coherence avec les violations Symfony rendues par API Platform (mapping inline
|
|
* front via useFormErrors, ERP-101).
|
|
*/
|
|
final class ClientAccountingCompletenessValidator
|
|
{
|
|
public function validate(Client $client): void
|
|
{
|
|
// Map champ -> valeur courante des champs obligatoires de l'onglet.
|
|
$fields = [
|
|
'siren' => $client->getSiren(),
|
|
'accountNumber' => $client->getAccountNumber(),
|
|
'tvaMode' => $client->getTvaMode(),
|
|
'nTva' => $client->getNTva(),
|
|
'paymentDelay' => $client->getPaymentDelay(),
|
|
'paymentType' => $client->getPaymentType(),
|
|
];
|
|
|
|
$violations = new ConstraintViolationList();
|
|
|
|
foreach ($fields as $property => $value) {
|
|
if ($this->isMissing($value)) {
|
|
$violations->add(new ConstraintViolation(
|
|
'Ce champ est obligatoire.',
|
|
null,
|
|
[],
|
|
$client,
|
|
$property,
|
|
$value,
|
|
));
|
|
}
|
|
}
|
|
|
|
if (count($violations) > 0) {
|
|
throw new ValidationException($violations);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Une valeur est manquante si null ou, pour une chaine, vide apres trim. Les
|
|
* references (TvaMode / PaymentDelay / PaymentType) ne sont manquantes que
|
|
* lorsqu'elles valent null.
|
|
*/
|
|
private function isMissing(mixed $value): bool
|
|
{
|
|
if (null === $value) {
|
|
return true;
|
|
}
|
|
|
|
return is_string($value) && '' === trim($value);
|
|
}
|
|
}
|