diff --git a/frontend/i18n/locales/fr.json b/frontend/i18n/locales/fr.json
index c085ff4..c21d4ab 100644
--- a/frontend/i18n/locales/fr.json
+++ b/frontend/i18n/locales/fr.json
@@ -115,9 +115,8 @@
"addPhone": "Ajouter un numéro",
"categories": "Catégorie",
"relation": "Distributeur / Courtier",
- "relationNone": "Aucun",
- "relationDistributor": "Distributeur",
- "relationBroker": "Courtier",
+ "relationDistributor": "Dépend du distributeur",
+ "relationBroker": "Dépend du courtier",
"distributorName": "Nom du distributeur",
"brokerName": "Nom du courtier",
"triageService": "Prestation de triage"
diff --git a/frontend/modules/commercial/pages/clients/new.vue b/frontend/modules/commercial/pages/clients/new.vue
index 2438e22..b7768ea 100644
--- a/frontend/modules/commercial/pages/clients/new.vue
+++ b/frontend/modules/commercial/pages/clients/new.vue
@@ -149,10 +149,13 @@
/>
+
@@ -386,6 +389,7 @@ import {
type RibFormDraft,
} from '~/modules/commercial/types/clientForm'
import { formatPhoneFR } from '~/shared/utils/phone'
+import { extractApiErrorMessage } from '~/shared/utils/api'
// Masques de saisie (la normalisation finale reste serveur).
const PHONE_MASK = '## ## ## ## ##'
@@ -407,6 +411,17 @@ function goBack(): void {
router.push('/clients')
}
+/**
+ * Message d'erreur a afficher dans un toast a partir d'une erreur d'API.
+ * Retourne TOUJOURS une chaine (le composant de toast plante sur `undefined`) :
+ * le message de validation renvoye par le serveur (violations 422 / detail),
+ * sinon un libelle generique.
+ */
+function apiErrorMessage(error: unknown): string {
+ const data = (error as { data?: unknown })?.data
+ return extractApiErrorMessage(data) || t('commercial.clients.toast.error')
+}
+
useHead({ title: t('commercial.clients.form.title') })
// Gating de la route : la creation est reservee a `manage`. Compta (accounting
@@ -433,7 +448,7 @@ const main = reactive({
lastName: null as string | null,
email: null as string | null,
categoryIris: [] as string[],
- relationType: 'aucun' as 'aucun' | 'distributeur' | 'courtier',
+ relationType: null as 'distributeur' | 'courtier' | null,
distributorIri: null as string | null,
brokerIri: null as string | null,
triageService: false,
@@ -450,23 +465,37 @@ function addMainPhone(): void {
}
}
+// Pas d'option « Aucun » : le select est vide par defaut (relationType = null).
const relationOptions = computed(() => [
- { value: 'aucun', label: t('commercial.clients.form.main.relationNone') },
{ value: 'distributeur', label: t('commercial.clients.form.main.relationDistributor') },
{ value: 'courtier', label: t('commercial.clients.form.main.relationBroker') },
])
-// RG-1.01 : firstName OU lastName, + companyName / email / telephone principal requis.
+// Validation du formulaire principal (gate le bouton « Valider ») :
+// - companyName / email / telephone principal / >= 1 categorie obligatoires ;
+// - RG-1.01 : nom OU prenom du contact principal ;
+// - relation Distributeur/Courtier obligatoire (un des deux), ET le nom
+// correspondant obligatoire selon le choix (spec fonctionnelle).
const isMainValid = computed(() => {
const filled = (v: string | null | undefined) => v !== null && v !== undefined && v.trim() !== ''
+ // Relation Distributeur/Courtier OPTIONNELLE ; mais si « Depend du
+ // distributeur/courtier » est choisi, le nom correspondant devient requis.
+ const relationValid
+ = main.relationType === null
+ || (main.relationType === 'distributeur' && filled(main.distributorIri))
+ || (main.relationType === 'courtier' && filled(main.brokerIri))
return filled(main.companyName)
&& filled(main.email)
&& filled(mainPhones.value[0])
&& (filled(main.firstName) || filled(main.lastName))
+ && main.categoryIris.length >= 1
+ && relationValid
})
async function onRelationChange(value: string | number | null): Promise {
- const relation = String(value ?? 'aucun') as typeof main.relationType
+ const relation = (value === null || value === '')
+ ? null
+ : (String(value) as 'distributeur' | 'courtier')
main.relationType = relation
// Reinitialise la FK non concernee (une seule remplie a la fois, RG-1.03).
if (relation !== 'distributeur') main.distributorIri = null
@@ -518,11 +547,14 @@ async function submitMain(): Promise {
toast.success({ title: t('commercial.clients.toast.createSuccess') })
}
catch (error) {
- // 409 = doublon nom de societe (RG d'unicite) → message explicite.
+ // 409 = doublon nom de societe (RG d'unicite) → message explicite ;
+ // sinon on remonte le message de validation du serveur (ex: 422).
const status = (error as { response?: { status?: number } })?.response?.status
toast.error({
title: t('commercial.clients.toast.error'),
- message: status === 409 ? t('commercial.clients.form.duplicateCompany') : undefined,
+ message: status === 409
+ ? t('commercial.clients.form.duplicateCompany')
+ : apiErrorMessage(error),
})
}
finally {
@@ -611,8 +643,8 @@ async function submitInformation(): Promise {
completeTab('information')
toast.success({ title: t('commercial.clients.toast.updateSuccess') })
}
- catch {
- toast.error({ title: t('commercial.clients.toast.error') })
+ catch (error) {
+ toast.error({ title: t('commercial.clients.toast.error'), message: apiErrorMessage(error) })
}
finally {
tabSubmitting.value = false
@@ -685,8 +717,8 @@ async function submitContacts(): Promise {
completeTab('contact')
toast.success({ title: t('commercial.clients.toast.updateSuccess') })
}
- catch {
- toast.error({ title: t('commercial.clients.toast.error') })
+ catch (error) {
+ toast.error({ title: t('commercial.clients.toast.error'), message: apiErrorMessage(error) })
}
finally {
tabSubmitting.value = false
@@ -783,8 +815,8 @@ async function submitAddresses(): Promise {
completeTab('address')
toast.success({ title: t('commercial.clients.toast.updateSuccess') })
}
- catch {
- toast.error({ title: t('commercial.clients.toast.error') })
+ catch (error) {
+ toast.error({ title: t('commercial.clients.toast.error'), message: apiErrorMessage(error) })
}
finally {
tabSubmitting.value = false
@@ -878,8 +910,8 @@ async function submitAccounting(): Promise {
completeTab('accounting')
toast.success({ title: t('commercial.clients.toast.updateSuccess') })
}
- catch {
- toast.error({ title: t('commercial.clients.toast.error') })
+ catch (error) {
+ toast.error({ title: t('commercial.clients.toast.error'), message: apiErrorMessage(error) })
}
finally {
tabSubmitting.value = false