[ERP-63] Page Ajouter un client (formulaire principal + onglets) #46
@@ -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