feat(technique) : sous-ressources Contacts / Adresses / RIBs (ERP-135)
Expose les sous-collections du prestataire en #[ApiResource] (POST sur le
parent + PATCH/DELETE/GET unitaires), edition complete par onglet (pas de
POST-only, RETEX M1/M2) :
- ProviderContact : POST /providers/{id}/contacts, PATCH/DELETE
/provider_contacts/{id} (security technique.providers.manage).
ProviderContactProcessor : normalisation RG-3.11 (nom/prenom Title Case,
telephones chiffres, email lowercase) + RG-3.04 (au moins un champ parmi
prenom/nom/telephone/email, miroir du CHECK chk_provider_contact_name -> 422).
- ProviderAddress : POST /providers/{id}/addresses, PATCH/DELETE
/provider_addresses/{id} (security technique.providers.manage).
ProviderAddressProcessor : rattachement parent + cloisonnement d'ecriture des
sites de l'adresse (RG-3.05 / § 2.13 : site hors user_site -> 422 sur sites).
- ProviderRib : POST /providers/{id}/ribs, PATCH/DELETE /provider_ribs/{id}
(security technique.providers.accounting.manage). ProviderRibProcessor :
RG-3.08 (DELETE du dernier RIB sous LCR -> 409).
Tests : ProviderSubResourceApiTest (19 cas) — CRUD chaque sous-ressource, 403
selon permission (Contacts/Adresses=manage, RIB=accounting.manage), 409 dernier
RIB LCR, 422 cloisonnement site adresse. Helpers addContact/addRib/paymentType
ajoutes a AbstractProviderApiTestCase.
This commit is contained in:
@@ -4,6 +4,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Module\Technique\Domain\Entity;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\Link;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use App\Module\Technique\Infrastructure\ApiPlatform\State\Processor\ProviderAddressProcessor;
|
||||
use App\Shared\Domain\Attribute\Auditable;
|
||||
use App\Shared\Domain\Contract\BlamableInterface;
|
||||
use App\Shared\Domain\Contract\CategoryInterface;
|
||||
@@ -32,11 +39,55 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
* type PRESTATAIRE attendu (RG-3.09, Assert\Callback validateCategoryType).
|
||||
*
|
||||
* Embarquee sous `provider.addresses` au detail (groupe provider:item:read,
|
||||
* maillon (a)). L'exposition en SOUS-RESSOURCE API est un ticket ulterieur du M3 :
|
||||
* pas d'#[ApiResource] ici.
|
||||
* maillon (a)).
|
||||
*
|
||||
* Sous-ressource API (ERP-135, spec § 4.5) :
|
||||
* - POST /api/providers/{providerId}/addresses : creation rattachee au prestataire
|
||||
* parent (Link toProperty 'provider'), security technique.providers.manage.
|
||||
* - PATCH / DELETE /api/provider_addresses/{id} : security technique.providers.manage.
|
||||
* - GET /api/provider_addresses/{id} : lecture unitaire (security view) — la lecture
|
||||
* courante reste via le parent. Pas de GET collection autonome.
|
||||
* Tout passe par le ProviderAddressProcessor (rattachement parent + cloisonnement
|
||||
* d'ecriture des sites, § 2.13). Les regles RG-3.05/3.06/3.09 sont portees par les
|
||||
* contraintes de l'entite (jouees avant le processor).
|
||||
*
|
||||
* Audite (#[Auditable]) + Timestampable / Blamable.
|
||||
*/
|
||||
#[ApiResource(
|
||||
operations: [
|
||||
new Get(
|
||||
security: "is_granted('technique.providers.view')",
|
||||
// site:read + category:read : embarquent les Site / Category lies
|
||||
// (maillon (c)) plutot que des IRI nus dans le retour.
|
||||
normalizationContext: ['groups' => ['provider:item:read', 'site:read', 'category:read', 'default:read']],
|
||||
),
|
||||
new Post(
|
||||
uriTemplate: '/providers/{providerId}/addresses',
|
||||
uriVariables: [
|
||||
'providerId' => new Link(fromClass: Provider::class, toProperty: 'provider'),
|
||||
],
|
||||
// read:false : pas de stade lecture du parent. Le Link toProperty
|
||||
// resoudrait l'enfant (SELECT ProviderAddress ... WHERE provider = :id)
|
||||
// et casse en NonUniqueResult des >= 2 enfants. Le parent est rattache
|
||||
// manuellement par ProviderAddressProcessor::linkParent (404 si absent).
|
||||
read: false,
|
||||
security: "is_granted('technique.providers.manage')",
|
||||
normalizationContext: ['groups' => ['provider:item:read', 'site:read', 'category:read', 'default:read']],
|
||||
denormalizationContext: ['groups' => ['provider:write:addresses']],
|
||||
processor: ProviderAddressProcessor::class,
|
||||
),
|
||||
new Patch(
|
||||
security: "is_granted('technique.providers.manage')",
|
||||
normalizationContext: ['groups' => ['provider:item:read', 'site:read', 'category:read', 'default:read']],
|
||||
denormalizationContext: ['groups' => ['provider:write:addresses']],
|
||||
processor: ProviderAddressProcessor::class,
|
||||
),
|
||||
new Delete(
|
||||
security: "is_granted('technique.providers.manage')",
|
||||
processor: ProviderAddressProcessor::class,
|
||||
),
|
||||
],
|
||||
)]
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'provider_address')]
|
||||
#[ORM\Index(name: 'idx_provider_address_provider', columns: ['provider_id'])]
|
||||
|
||||
Reference in New Issue
Block a user