feat(transport) : sous-ressource adresses transporteur (ERP-159) #115

Merged
matthieu merged 1 commits from feat/erp-159-carrier-addresses into feat/erp-156-qualimat-search 2026-06-16 13:47:34 +00:00
Owner

POST/PATCH/DELETE carrier_address + RG-4.05→4.07. Ticket ERP-159.

  • POST /api/carriers/{id}/addresses, PATCH/DELETE /api/carrier_addresses/{id} (security transport.carriers.manage), resource + processor dédiés (ApiResource Carrier non touché).
  • RG-4.06 : code postal ^[0-9]{4,5}$ (Assert\Regex, message FR ; autocomplete BAN = front).
  • RG-4.05 : transporteur affrété → adresse obligatoire (Pays/CP/Ville/Adresse), validation conditionnelle dans CarrierAddressProcessor (parent indisponible à la validation Symfony sur POST sous-ressource read:false), 422 par champ.
  • RG-4.07 : masquage bouton Valider si QUALIMAT = front ; back accepte le PATCH.
  • Tests : CP invalide 422, affrété incomplet 422, affrété complet 201, PATCH/DELETE OK (manage), 403 sans manage.
POST/PATCH/DELETE carrier_address + RG-4.05→4.07. Ticket ERP-159. - POST /api/carriers/{id}/addresses, PATCH/DELETE /api/carrier_addresses/{id} (security transport.carriers.manage), resource + processor dédiés (ApiResource Carrier non touché). - RG-4.06 : code postal ^[0-9]{4,5}$ (Assert\Regex, message FR ; autocomplete BAN = front). - RG-4.05 : transporteur affrété → adresse obligatoire (Pays/CP/Ville/Adresse), validation conditionnelle dans CarrierAddressProcessor (parent indisponible à la validation Symfony sur POST sous-ressource read:false), 422 par champ. - RG-4.07 : masquage bouton Valider si QUALIMAT = front ; back accepte le PATCH. - Tests : CP invalide 422, affrété incomplet 422, affrété complet 201, PATCH/DELETE OK (manage), 403 sans manage.
matthieu added the backM4-Transporteurtype/feat labels 2026-06-16 07:19:44 +00:00
tristan reviewed 2026-06-16 09:44:12 +00:00
tristan left a comment
Owner

Code review ERP-159 (sous-ressource Adresses transporteur) — relue contre spec-back § 4.5 (RG-4.05/4.06/4.07).

Verdict : PR propre, aucun constat bloquant. Jumelle fidèle de SupplierAddress (M2) / ProviderAddress (M3) :

  • POST /carriers/{carrierId}/addresses (Link toProperty + read:false pour éviter le NonUniqueResult — RETEX M1), PATCH/DELETE sur /carrier_addresses/{id}, security transport.carriers.manage (Get en view).
  • RG-4.06 : code postal ^[0-9]{4,5}$ (Assert\Regex), whitelist EXCLUDED_LENGTH_MIRROR justifiée. Messages FR sur toutes les contraintes.
  • RG-4.05 (adresse obligatoire si affrété) portée par le CarrierAddressProcessor — correctement, car le parent n'est pas disponible à la validation Symfony sur un POST read:false ; violation par champ via ValidationException (rendu Hydra inline, ERP-101).
  • RG-4.07 = front (PATCH accepté côté back) — correct.

Tests : RG-4.06, RG-4.05 (incomplet/complet), PATCH/DELETE manage, 403 sans manage. Quelques 🟡 mineurs en inline (assertion propertyPath, 404 parent non testé). Rien qui bloque.

Code review ERP-159 (sous-ressource Adresses transporteur) — relue contre spec-back § 4.5 (RG-4.05/4.06/4.07). **Verdict : PR propre, aucun constat bloquant.** Jumelle fidèle de `SupplierAddress` (M2) / `ProviderAddress` (M3) : - POST `/carriers/{carrierId}/addresses` (`Link toProperty` + `read:false` pour éviter le `NonUniqueResult` — RETEX M1), PATCH/DELETE sur `/carrier_addresses/{id}`, security `transport.carriers.manage` (Get en `view`). - RG-4.06 : code postal `^[0-9]{4,5}$` (`Assert\Regex`), whitelist `EXCLUDED_LENGTH_MIRROR` justifiée. Messages FR sur toutes les contraintes. - RG-4.05 (adresse obligatoire si affrété) portée par le `CarrierAddressProcessor` — correctement, car le parent n'est pas disponible à la validation Symfony sur un POST `read:false` ; violation par champ via `ValidationException` (rendu Hydra inline, ERP-101). - RG-4.07 = front (PATCH accepté côté back) — correct. Tests : RG-4.06, RG-4.05 (incomplet/complet), PATCH/DELETE manage, 403 sans manage. Quelques 🟡 mineurs en inline (assertion propertyPath, 404 parent non testé). Rien qui bloque.
@@ -23,0 +66,4 @@
// resoudrait l'enfant (SELECT CarrierAddress ... WHERE carrier = :id)
// et casse en NonUniqueResult des >= 2 enfants. Le parent est rattache
// manuellement par CarrierAddressProcessor::linkParent (404 si absent).
read: false,
Owner

🟢 Bon pattern (RETEX M1 #110/#542) : read: false + rattachement manuel du parent dans le processor évite le NonUniqueResult qu'un Link toProperty provoquerait sur un POST sous-ressource dès ≥2 enfants (le Link ferait un SELECT enfant WHERE carrier=:id). Le 404 parent introuvable est géré explicitement dans CarrierAddressProcessor::linkParent. Security manage sur l'écriture, view sur le Get. Conforme § 4.5.

🟢 Bon pattern (RETEX M1 #110/#542) : `read: false` + rattachement manuel du parent dans le processor évite le `NonUniqueResult` qu'un `Link toProperty` provoquerait sur un POST sous-ressource dès ≥2 enfants (le Link ferait un SELECT enfant WHERE carrier=:id). Le 404 parent introuvable est géré explicitement dans `CarrierAddressProcessor::linkParent`. Security `manage` sur l'écriture, `view` sur le Get. Conforme § 4.5.
@@ -0,0 +91,4 @@
// read:false sur le POST : sans stade lecture, un parent introuvable n'est
// plus intercepte en amont -> 404 explicite (sinon 500 au persist sur la
// contrainte carrier_id NOT NULL).
if (!$carrier instanceof Carrier) {
Owner

🟡 Trou de test (mineur). Ce 404 « Transporteur introuvable » est une logique explicite et non triviale (conséquence directe du read:false, sinon 500 au persist sur carrier_id NOT NULL). Aucun test ne l'exerce — un POST sur /api/carriers/999999/addresses devrait renvoyer 404. À ajouter pour figer ce comportement.

🟡 Trou de test (mineur). Ce 404 « Transporteur introuvable » est une logique explicite et non triviale (conséquence directe du `read:false`, sinon 500 au persist sur `carrier_id NOT NULL`). Aucun test ne l'exerce — un POST sur `/api/carriers/999999/addresses` devrait renvoyer 404. À ajouter pour figer ce comportement.
@@ -0,0 +106,4 @@
* payload), donc identique sur POST et sur PATCH partiel. Sans affretement,
* l'adresse reste partielle (champs nullable, RG-4.06 inchangee).
*/
private function guardCharteredAddress(CarrierAddress $address): void
Owner

🟢 RG-4.05 bien implémentée : garde portée par le processor (le parent affrété n'est pas disponible à la validation Symfony sur POST read:false), une ConstraintViolation par champ manquant (country/postalCode/city/street) avec propertyPath + message FR, levée via ValidationException API Platform → rendu Hydra 422 mappable inline (ERP-101). Validation sur l'état résultant → cohérent POST et PATCH partiel. RAS.

🟢 RG-4.05 bien implémentée : garde portée par le processor (le parent affrété n'est pas disponible à la validation Symfony sur POST `read:false`), une `ConstraintViolation` par champ manquant (`country`/`postalCode`/`city`/`street`) avec propertyPath + message FR, levée via `ValidationException` API Platform → rendu Hydra 422 mappable inline (ERP-101). Validation sur l'état résultant → cohérent POST et PATCH partiel. RAS.
@@ -0,0 +63,4 @@
self::assertResponseStatusCodeSame(422);
}
public function testCharteredCarrierIncompleteAddressReturns422(): void
Owner

🟡 Rigueur d'assertion (cohérence avec #113). Les tests 422 (RG-4.06 et RG-4.05) n'assertent que le code HTTP, pas le propertyPath des violations. Or tout l'intérêt de RG-4.05 ici est le mapping par champ (city/street inline, ERP-101) : une régression qui ferait perdre le propertyPath passerait au vert. La PR #113 assertait bien le path (assertViolationOnPath) — appliquer la même rigueur ici (vérifier que la 422 « affrété incomplet » porte des violations sur city ET street).

🟡 Rigueur d'assertion (cohérence avec #113). Les tests 422 (RG-4.06 et RG-4.05) n'assertent que le **code HTTP**, pas le `propertyPath` des violations. Or tout l'intérêt de RG-4.05 ici est le mapping **par champ** (`city`/`street` inline, ERP-101) : une régression qui ferait perdre le propertyPath passerait au vert. La PR #113 assertait bien le path (`assertViolationOnPath`) — appliquer la même rigueur ici (vérifier que la 422 « affrété incomplet » porte des violations sur `city` ET `street`).
matthieu force-pushed feat/erp-159-carrier-addresses from 54ac034c1b to 91dc02ab05 2026-06-16 13:06:28 +00:00 Compare
matthieu added 1 commit 2026-06-16 13:15:02 +00:00
POST /api/carriers/{id}/addresses + PATCH/DELETE /api/carrier_addresses/{id}
(security transport.carriers.manage), spec-back § 4.5. Jumelle de SupplierAddress
(M2) / ProviderAddress (M3), sans address_type ni M2M.

- CarrierAddress : ajout #[ApiResource] (Get/Post/Patch/Delete) + groupe
  d'ecriture carrier:write:addresses + contraintes FR. RG-4.06 : code postal
  ^[0-9]{4,5}$ (Assert\Regex). Mapping ORM/colonnes inchange.
- CarrierAddressProcessor : rattachement parent (404 si absent) + RG-4.05
  (transporteur affrete -> Pays/CP/Ville/Adresse obligatoires, 422 par champ).
  RG-4.05 portee par le processor car le parent est indisponible a la validation
  Symfony sur un POST sous-ressource read:false. RG-4.07 = front (PATCH accepte).
- EXCLUDED_LENGTH_MIRROR : CarrierAddress::postalCode (Regex borne la longueur).
- Tests : CP invalide 422, affrete incomplet 422, affrete complet 201,
  PATCH/DELETE OK (manage), 403 sans manage.
matthieu force-pushed feat/erp-159-carrier-addresses from 91dc02ab05 to 7012306a78 2026-06-16 13:15:02 +00:00 Compare
matthieu merged commit 7012306a78 into feat/erp-156-qualimat-search 2026-06-16 13:47:34 +00:00
matthieu deleted branch feat/erp-159-carrier-addresses 2026-06-16 13:47:34 +00:00
Sign in to join this conversation.