fix(front) : corrections review ecran ajouter client (ERP-63)
- Relation Distributeur/Courtier : libelles « Depend du distributeur/courtier », select optionnel ; le nom (distributeur ou courtier) devient requis quand la relation correspondante est choisie. - Categorie : au moins 1 obligatoire dans le formulaire principal (aligne sur Assert\Count(min:1) du back). - Bouton « Valider » de l'onglet Information desactive tant que le client n'est pas cree (l'onglet est actif par defaut) : evite tout PATCH premature. - Gestion d'erreur : les toasts d'erreur passent toujours une chaine (corrige un crash izitoast sur message undefined) et remontent le message de validation du serveur (violations 422) sur tous les onglets.
This commit is contained in:
@@ -115,9 +115,8 @@
|
|||||||
"addPhone": "Ajouter un numéro",
|
"addPhone": "Ajouter un numéro",
|
||||||
"categories": "Catégorie",
|
"categories": "Catégorie",
|
||||||
"relation": "Distributeur / Courtier",
|
"relation": "Distributeur / Courtier",
|
||||||
"relationNone": "Aucun",
|
"relationDistributor": "Dépend du distributeur",
|
||||||
"relationDistributor": "Distributeur",
|
"relationBroker": "Dépend du courtier",
|
||||||
"relationBroker": "Courtier",
|
|
||||||
"distributorName": "Nom du distributeur",
|
"distributorName": "Nom du distributeur",
|
||||||
"brokerName": "Nom du courtier",
|
"brokerName": "Nom du courtier",
|
||||||
"triageService": "Prestation de triage"
|
"triageService": "Prestation de triage"
|
||||||
|
|||||||
@@ -149,10 +149,13 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!isValidated('information')" class="mt-12 flex justify-center">
|
<div v-if="!isValidated('information')" class="mt-12 flex justify-center">
|
||||||
|
<!-- Desactive tant que le client n'est pas cree : evite un PATCH
|
||||||
|
avant le POST si l'utilisateur clique trop tot (le panneau
|
||||||
|
Information est l'onglet actif par defaut). -->
|
||||||
<MalioButton
|
<MalioButton
|
||||||
variant="primary"
|
variant="primary"
|
||||||
:label="t('commercial.clients.form.submit')"
|
:label="t('commercial.clients.form.submit')"
|
||||||
:disabled="tabSubmitting"
|
:disabled="tabSubmitting || clientId === null"
|
||||||
@click="submitInformation"
|
@click="submitInformation"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -386,6 +389,7 @@ import {
|
|||||||
type RibFormDraft,
|
type RibFormDraft,
|
||||||
} from '~/modules/commercial/types/clientForm'
|
} from '~/modules/commercial/types/clientForm'
|
||||||
import { formatPhoneFR } from '~/shared/utils/phone'
|
import { formatPhoneFR } from '~/shared/utils/phone'
|
||||||
|
import { extractApiErrorMessage } from '~/shared/utils/api'
|
||||||
|
|
||||||
// Masques de saisie (la normalisation finale reste serveur).
|
// Masques de saisie (la normalisation finale reste serveur).
|
||||||
const PHONE_MASK = '## ## ## ## ##'
|
const PHONE_MASK = '## ## ## ## ##'
|
||||||
@@ -407,6 +411,17 @@ function goBack(): void {
|
|||||||
router.push('/clients')
|
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') })
|
useHead({ title: t('commercial.clients.form.title') })
|
||||||
|
|
||||||
// Gating de la route : la creation est reservee a `manage`. Compta (accounting
|
// Gating de la route : la creation est reservee a `manage`. Compta (accounting
|
||||||
@@ -433,7 +448,7 @@ const main = reactive({
|
|||||||
lastName: null as string | null,
|
lastName: null as string | null,
|
||||||
email: null as string | null,
|
email: null as string | null,
|
||||||
categoryIris: [] as string[],
|
categoryIris: [] as string[],
|
||||||
relationType: 'aucun' as 'aucun' | 'distributeur' | 'courtier',
|
relationType: null as 'distributeur' | 'courtier' | null,
|
||||||
distributorIri: null as string | null,
|
distributorIri: null as string | null,
|
||||||
brokerIri: null as string | null,
|
brokerIri: null as string | null,
|
||||||
triageService: false,
|
triageService: false,
|
||||||
@@ -450,23 +465,37 @@ function addMainPhone(): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pas d'option « Aucun » : le select est vide par defaut (relationType = null).
|
||||||
const relationOptions = computed<RefOption[]>(() => [
|
const relationOptions = computed<RefOption[]>(() => [
|
||||||
{ value: 'aucun', label: t('commercial.clients.form.main.relationNone') },
|
|
||||||
{ value: 'distributeur', label: t('commercial.clients.form.main.relationDistributor') },
|
{ value: 'distributeur', label: t('commercial.clients.form.main.relationDistributor') },
|
||||||
{ value: 'courtier', label: t('commercial.clients.form.main.relationBroker') },
|
{ 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 isMainValid = computed(() => {
|
||||||
const filled = (v: string | null | undefined) => v !== null && v !== undefined && v.trim() !== ''
|
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)
|
return filled(main.companyName)
|
||||||
&& filled(main.email)
|
&& filled(main.email)
|
||||||
&& filled(mainPhones.value[0])
|
&& filled(mainPhones.value[0])
|
||||||
&& (filled(main.firstName) || filled(main.lastName))
|
&& (filled(main.firstName) || filled(main.lastName))
|
||||||
|
&& main.categoryIris.length >= 1
|
||||||
|
&& relationValid
|
||||||
})
|
})
|
||||||
|
|
||||||
async function onRelationChange(value: string | number | null): Promise<void> {
|
async function onRelationChange(value: string | number | null): Promise<void> {
|
||||||
const relation = String(value ?? 'aucun') as typeof main.relationType
|
const relation = (value === null || value === '')
|
||||||
|
? null
|
||||||
|
: (String(value) as 'distributeur' | 'courtier')
|
||||||
main.relationType = relation
|
main.relationType = relation
|
||||||
// Reinitialise la FK non concernee (une seule remplie a la fois, RG-1.03).
|
// Reinitialise la FK non concernee (une seule remplie a la fois, RG-1.03).
|
||||||
if (relation !== 'distributeur') main.distributorIri = null
|
if (relation !== 'distributeur') main.distributorIri = null
|
||||||
@@ -518,11 +547,14 @@ async function submitMain(): Promise<void> {
|
|||||||
toast.success({ title: t('commercial.clients.toast.createSuccess') })
|
toast.success({ title: t('commercial.clients.toast.createSuccess') })
|
||||||
}
|
}
|
||||||
catch (error) {
|
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
|
const status = (error as { response?: { status?: number } })?.response?.status
|
||||||
toast.error({
|
toast.error({
|
||||||
title: t('commercial.clients.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 {
|
finally {
|
||||||
@@ -611,8 +643,8 @@ async function submitInformation(): Promise<void> {
|
|||||||
completeTab('information')
|
completeTab('information')
|
||||||
toast.success({ title: t('commercial.clients.toast.updateSuccess') })
|
toast.success({ title: t('commercial.clients.toast.updateSuccess') })
|
||||||
}
|
}
|
||||||
catch {
|
catch (error) {
|
||||||
toast.error({ title: t('commercial.clients.toast.error') })
|
toast.error({ title: t('commercial.clients.toast.error'), message: apiErrorMessage(error) })
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
tabSubmitting.value = false
|
tabSubmitting.value = false
|
||||||
@@ -685,8 +717,8 @@ async function submitContacts(): Promise<void> {
|
|||||||
completeTab('contact')
|
completeTab('contact')
|
||||||
toast.success({ title: t('commercial.clients.toast.updateSuccess') })
|
toast.success({ title: t('commercial.clients.toast.updateSuccess') })
|
||||||
}
|
}
|
||||||
catch {
|
catch (error) {
|
||||||
toast.error({ title: t('commercial.clients.toast.error') })
|
toast.error({ title: t('commercial.clients.toast.error'), message: apiErrorMessage(error) })
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
tabSubmitting.value = false
|
tabSubmitting.value = false
|
||||||
@@ -783,8 +815,8 @@ async function submitAddresses(): Promise<void> {
|
|||||||
completeTab('address')
|
completeTab('address')
|
||||||
toast.success({ title: t('commercial.clients.toast.updateSuccess') })
|
toast.success({ title: t('commercial.clients.toast.updateSuccess') })
|
||||||
}
|
}
|
||||||
catch {
|
catch (error) {
|
||||||
toast.error({ title: t('commercial.clients.toast.error') })
|
toast.error({ title: t('commercial.clients.toast.error'), message: apiErrorMessage(error) })
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
tabSubmitting.value = false
|
tabSubmitting.value = false
|
||||||
@@ -878,8 +910,8 @@ async function submitAccounting(): Promise<void> {
|
|||||||
completeTab('accounting')
|
completeTab('accounting')
|
||||||
toast.success({ title: t('commercial.clients.toast.updateSuccess') })
|
toast.success({ title: t('commercial.clients.toast.updateSuccess') })
|
||||||
}
|
}
|
||||||
catch {
|
catch (error) {
|
||||||
toast.error({ title: t('commercial.clients.toast.error') })
|
toast.error({ title: t('commercial.clients.toast.error'), message: apiErrorMessage(error) })
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
tabSubmitting.value = false
|
tabSubmitting.value = false
|
||||||
|
|||||||
Reference in New Issue
Block a user