fix(commercial) : POST sous-ressource client en read:false + parent 404 (corrige 500 NonUniqueResult, ERP-110)

This commit is contained in:
Matthieu
2026-06-04 11:33:46 +02:00
parent e61e653ea3
commit 571d80f75f
6 changed files with 39 additions and 6 deletions
@@ -63,6 +63,11 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
uriVariables: [ uriVariables: [
'clientId' => new Link(fromClass: Client::class, toProperty: 'client'), 'clientId' => new Link(fromClass: Client::class, toProperty: 'client'),
], ],
// read:false : pas de stade lecture du parent. Le Link toProperty
// resoudrait l'enfant (SELECT ClientAddress ... WHERE client = :id) et
// casse en NonUniqueResult des >= 2 enfants. Le parent est rattache
// manuellement par ClientAddressProcessor::linkParent (404 si absent).
read: false,
security: "is_granted('commercial.clients.manage')", security: "is_granted('commercial.clients.manage')",
normalizationContext: ['groups' => ['client_address:read']], normalizationContext: ['groups' => ['client_address:read']],
denormalizationContext: ['groups' => ['client_address:write']], denormalizationContext: ['groups' => ['client_address:write']],
@@ -50,6 +50,11 @@ use Symfony\Component\Validator\Constraints as Assert;
uriVariables: [ uriVariables: [
'clientId' => new Link(fromClass: Client::class, toProperty: 'client'), 'clientId' => new Link(fromClass: Client::class, toProperty: 'client'),
], ],
// read:false : pas de stade lecture du parent. Le Link toProperty
// resoudrait l'enfant (SELECT ClientContact ... WHERE client = :id) et
// casse en NonUniqueResult des >= 2 enfants. Le parent est rattache
// manuellement par ClientContactProcessor::linkParent (404 si absent).
read: false,
security: "is_granted('commercial.clients.manage')", security: "is_granted('commercial.clients.manage')",
normalizationContext: ['groups' => ['client_contact:read']], normalizationContext: ['groups' => ['client_contact:read']],
denormalizationContext: ['groups' => ['client_contact:write']], denormalizationContext: ['groups' => ['client_contact:write']],
@@ -54,6 +54,11 @@ use Symfony\Component\Validator\Constraints as Assert;
uriVariables: [ uriVariables: [
'clientId' => new Link(fromClass: Client::class, toProperty: 'client'), 'clientId' => new Link(fromClass: Client::class, toProperty: 'client'),
], ],
// read:false : pas de stade lecture du parent. Le Link toProperty
// resoudrait l'enfant (SELECT ClientRib ... WHERE client = :id) et
// casse en NonUniqueResult des >= 2 enfants. Le parent est rattache
// manuellement par ClientRibProcessor::linkParent (404 si absent).
read: false,
security: "is_granted('commercial.clients.accounting.manage')", security: "is_granted('commercial.clients.accounting.manage')",
normalizationContext: ['groups' => ['client_rib:read']], normalizationContext: ['groups' => ['client_rib:read']],
denormalizationContext: ['groups' => ['client_rib:write']], denormalizationContext: ['groups' => ['client_rib:write']],
@@ -12,6 +12,7 @@ use App\Module\Commercial\Domain\Entity\Client;
use App\Module\Commercial\Domain\Entity\ClientAddress; use App\Module\Commercial\Domain\Entity\ClientAddress;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** /**
* Processor d'ecriture de la sous-ressource Adresse d'un client (M1, § 4.5). * Processor d'ecriture de la sous-ressource Adresse d'un client (M1, § 4.5).
@@ -75,9 +76,14 @@ final class ClientAddressProcessor implements ProcessorInterface
? $clientId ? $clientId
: $this->em->getRepository(Client::class)->find($clientId); : $this->em->getRepository(Client::class)->find($clientId);
if ($client instanceof Client) { // read:false sur le POST : sans stade lecture, un parent introuvable n'est
$address->setClient($client); // plus intercepte en amont -> 404 explicite (sinon 500 au persist sur la
// contrainte client_id NOT NULL).
if (!$client instanceof Client) {
throw new NotFoundHttpException('Client introuvable.');
} }
$address->setClient($client);
} }
/** /**
@@ -14,6 +14,7 @@ use App\Module\Commercial\Domain\Entity\ClientContact;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException; use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Validator\ConstraintViolation; use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList; use Symfony\Component\Validator\ConstraintViolationList;
@@ -88,9 +89,14 @@ final class ClientContactProcessor implements ProcessorInterface
? $clientId ? $clientId
: $this->em->getRepository(Client::class)->find($clientId); : $this->em->getRepository(Client::class)->find($clientId);
if ($client instanceof Client) { // read:false sur le POST : sans stade lecture, un parent introuvable n'est
$contact->setClient($client); // plus intercepte en amont -> 404 explicite (sinon 500 au persist sur la
// contrainte client_id NOT NULL).
if (!$client instanceof Client) {
throw new NotFoundHttpException('Client introuvable.');
} }
$contact->setClient($client);
} }
/** /**
@@ -12,6 +12,7 @@ use App\Module\Commercial\Domain\Entity\ClientRib;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException; use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** /**
* Processor d'ecriture de la sous-ressource RIB d'un client (M1, § 4.5). * Processor d'ecriture de la sous-ressource RIB d'un client (M1, § 4.5).
@@ -77,9 +78,14 @@ final class ClientRibProcessor implements ProcessorInterface
? $clientId ? $clientId
: $this->em->getRepository(Client::class)->find($clientId); : $this->em->getRepository(Client::class)->find($clientId);
if ($client instanceof Client) { // read:false sur le POST : sans stade lecture, un parent introuvable n'est
$rib->setClient($client); // plus intercepte en amont -> 404 explicite (sinon 500 au persist sur la
// contrainte client_id NOT NULL).
if (!$client instanceof Client) {
throw new NotFoundHttpException('Client introuvable.');
} }
$rib->setClient($client);
} }
/** /**