From 2b1071bedb386bb7d25f9b4011785f37a657303b Mon Sep 17 00:00:00 2001 From: tristan Date: Fri, 26 Jun 2026 16:29:56 +0200 Subject: [PATCH] =?UTF-8?q?fix(catalog)=20:=20M6=20=E2=80=94=20mapping=20i?= =?UTF-8?q?nline=20des=20erreurs=20422=20du=20formulaire=20produit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Les operations Post/Patch de Product n'avaient pas collectDenormalizationErrors : un null/type invalide sur une relation (category) levait un 400 qui court-circuitait toute la validation -> aucune violation propertyPath, donc aucune erreur mappee sous les champs (ajout comme modification). - Product : collectDenormalizationErrors: true sur Post + Patch (miroir Client/Supplier/WeighingTicket) -> 422 avec propertyPath au lieu de 400. - useProductForm : on omet la cle 'category' du payload quand aucune categorie n'est choisie (envoyer null casserait la denormalisation IRI et masquerait les autres violations) -> le back renvoie les 6 violations d'un coup, dont le NotNull propre sur category. --- .../composables/__tests__/useProductForm.spec.ts | 14 ++++++++++++++ .../modules/catalog/composables/useProductForm.ts | 11 +++++++++-- src/Module/Catalog/Domain/Entity/Product.php | 6 ++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/frontend/modules/catalog/composables/__tests__/useProductForm.spec.ts b/frontend/modules/catalog/composables/__tests__/useProductForm.spec.ts index 2c0b24a..9294f47 100644 --- a/frontend/modules/catalog/composables/__tests__/useProductForm.spec.ts +++ b/frontend/modules/catalog/composables/__tests__/useProductForm.spec.ts @@ -171,6 +171,20 @@ describe('useProductForm', () => { expect(payload.containsMolasses).toBe(false) }) + it('omet `category` du payload quand aucune categorie n\'est choisie', async () => { + // Envoyer category:null casserait la denormalisation back (type IRI + // attendu) et court-circuiterait les autres violations -> on l'omet. + mockPost.mockResolvedValueOnce({ id: 40 }) + const { form, submit } = useProductForm() + fillValidForm(form) + form.categoryIri = null + + await submit() + + const payload = mockPost.mock.calls[0][1] + expect(payload).not.toHaveProperty('category') + }) + it('mappe un 409 doublon de code sur errors.code + toast explicite', async () => { mockPost.mockRejectedValueOnce({ response: { status: 409, _data: {} } }) const { form, errors, submit } = useProductForm() diff --git a/frontend/modules/catalog/composables/useProductForm.ts b/frontend/modules/catalog/composables/useProductForm.ts index 094fea5..0d6237b 100644 --- a/frontend/modules/catalog/composables/useProductForm.ts +++ b/frontend/modules/catalog/composables/useProductForm.ts @@ -126,7 +126,7 @@ export function useProductForm() { formErrors.clearErrors() const editing = productId.value !== null try { - const payload = { + const payload: Record = { code: form.code || null, name: form.name || null, states: form.states, @@ -134,10 +134,17 @@ export function useProductForm() { // re-force, on garde le payload coherent). manufactured: isSale.value ? form.manufactured : false, containsMolasses: isSale.value ? form.containsMolasses : false, - category: form.categoryIri, sites: form.siteIris, storageTypes: form.storageTypeIris, } + // `category` attend un IRI (string) : envoyer null declencherait une + // erreur de denormalisation API Platform qui court-circuiterait TOUTES + // les autres violations. On omet la cle quand aucune categorie n'est + // choisie -> la contrainte NotNull renvoie un message propre, et les + // autres champs sont valides dans la meme 422 (mapping inline ERP-101). + if (form.categoryIri) { + payload.category = form.categoryIri + } const options = { headers: { Accept: 'application/ld+json' }, toast: false } if (editing) { await api.patch(`/products/${productId.value}`, payload, options) diff --git a/src/Module/Catalog/Domain/Entity/Product.php b/src/Module/Catalog/Domain/Entity/Product.php index 75605e7..7cc774b 100644 --- a/src/Module/Catalog/Domain/Entity/Product.php +++ b/src/Module/Catalog/Domain/Entity/Product.php @@ -81,12 +81,18 @@ use function in_array; security: "is_granted('catalog.products.manage')", normalizationContext: ['groups' => ['product:read', 'product:item:read', 'category:read', 'site:read', 'storage_type:read', 'default:read']], denormalizationContext: ['groups' => ['product:write']], + // Convertit les erreurs de denormalisation (type invalide / null sur une + // relation : category, sites, storageTypes) en violations 422 portant un + // propertyPath, au lieu d'un 400 qui court-circuite toute la validation + // (cf. Client/Supplier/WeighingTicket — mapping inline useFormErrors). + collectDenormalizationErrors: true, processor: ProductProcessor::class, ), new Patch( security: "is_granted('catalog.products.manage')", normalizationContext: ['groups' => ['product:read', 'product:item:read', 'category:read', 'site:read', 'storage_type:read', 'default:read']], denormalizationContext: ['groups' => ['product:write']], + collectDenormalizationErrors: true, provider: ProductProvider::class, processor: ProductProcessor::class, ),