[ERP-63] Page Ajouter un client (formulaire principal + onglets) #46
@@ -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"
|
||||
|
||||
@@ -149,10 +149,13 @@
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
variant="primary"
|
||||
:label="t('commercial.clients.form.submit')"
|
||||
:disabled="tabSubmitting"
|
||||
:disabled="tabSubmitting || clientId === null"
|
||||
@click="submitInformation"
|
||||
/>
|
||||
</div>
|
||||
@@ -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<RefOption[]>(() => [
|
||||
{ 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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user