+ sa valeur liee, il n'afficherait rien en readonly). allow-create :
+ si la BAN ne propose rien (ou erreur), le texte saisi est CONSERVE au
+ blur/Entree (saisie manuelle) — sinon il serait efface. La ville reste
+ pilotee par le code postal ; choisir une suggestion remplit rue+ville+CP. -->
update('street', v === null ? null : String(v))"
@search="onAddressSearch"
@select="onAddressSelect"
@@ -147,7 +168,7 @@
/>
-
+
(() => [
{ value: 'delivery', label: t('commercial.clients.form.address.addressTypeDelivery') },
{ value: 'billing', label: t('commercial.clients.form.address.addressTypeBilling') },
{ value: 'delivery_billing', label: t('commercial.clients.form.address.addressTypeDeliveryBilling') },
+ { value: 'broker', label: t('commercial.clients.form.address.addressTypeBroker') },
+ { value: 'distributor', label: t('commercial.clients.form.address.addressTypeDistributor') },
])
/** Applique le type choisi en repercutant les 3 drapeaux back (immutabilite). */
@@ -266,6 +289,11 @@ function update(field: K, value: AddressFormDr
emit('update:modelValue', { ...props.modelValue, [field]: value })
}
+/** Revele le 2e champ email de facturation (clic sur le « + »). */
+function revealSecondaryBillingEmail(): void {
+ emit('update:modelValue', { ...props.modelValue, hasSecondaryBillingEmail: true })
+}
+
/** Previent le parent (toast unique) que l'autocompletion est indisponible. */
function notifyUnavailable(): void {
if (!unavailableNotified) {
diff --git a/frontend/modules/commercial/components/__tests__/ClientAddressBlock.spec.ts b/frontend/modules/commercial/components/__tests__/ClientAddressBlock.spec.ts
index 20cb438..782349a 100644
--- a/frontend/modules/commercial/components/__tests__/ClientAddressBlock.spec.ts
+++ b/frontend/modules/commercial/components/__tests__/ClientAddressBlock.spec.ts
@@ -36,6 +36,7 @@ const MalioInputAutocompleteStub = defineComponent({
minSearchLength: { type: Number, default: 0 },
label: { type: String, default: '' },
readonly: { type: Boolean, default: false },
+ allowCreate: { type: Boolean, default: false },
},
emits: ['update:modelValue', 'search', 'select'],
setup(props) {
@@ -78,6 +79,14 @@ describe('ClientAddressBlock — affichage de l\'adresse persistee', () => {
expect(values).toContain('8 Boulevard du Port')
})
+
+ // ERP-119 : saisie manuelle possible quand la BAN ne trouve rien -> allow-create
+ // (sans cette prop, MalioInputAutocomplete efface le texte non selectionne au blur).
+ it('active allow-create sur le champ Adresse (saisie manuelle libre)', () => {
+ const wrapper = mountBlock(null)
+
+ expect(wrapper.findComponent(MalioInputAutocompleteStub).props('allowCreate')).toBe(true)
+ })
})
/**
@@ -134,6 +143,32 @@ describe('ClientAddressBlock — mapping erreur par champ (ERP-101)', () => {
)
expect(field?.attributes('data-error')).toBe('Code postal invalide.')
})
+
+ // ERP-119 : type d'adresse (propertyPath back `isProspect`), sites et
+ // categories sont obligatoires ; leurs violations 422 doivent s'afficher sous
+ // le champ correspondant (bindings :error de ClientAddressBlock).
+ it('affiche l\'erreur serveur sur type d\'adresse (propertyPath isProspect)', () => {
+ const wrapper = mountWithErrors({ isProspect: 'Le type d\'adresse est obligatoire.' })
+
+ const field = wrapper.findAll('malio-select-stub').find(
+ el => el.attributes('label') === 'commercial.clients.form.address.addressType',
+ )
+ expect(field?.attributes('error')).toBe('Le type d\'adresse est obligatoire.')
+ })
+
+ it('affiche les erreurs serveur sur sites et categories', () => {
+ const wrapper = mountWithErrors({
+ sites: 'Au moins un site est obligatoire.',
+ categories: 'Au moins une catégorie est obligatoire.',
+ })
+
+ const checkboxes = wrapper.findAll('malio-select-checkbox-stub')
+ const sitesField = checkboxes.find(el => el.attributes('label') === 'commercial.clients.form.address.sites')
+ const categoriesField = checkboxes.find(el => el.attributes('label') === 'commercial.clients.form.address.categories')
+
+ expect(sitesField?.attributes('error')).toBe('Au moins un site est obligatoire.')
+ expect(categoriesField?.attributes('error')).toBe('Au moins une catégorie est obligatoire.')
+ })
})
describe('ClientAddressBlock — recherche adresse robuste (erreur BAN)', () => {
diff --git a/frontend/modules/commercial/pages/clients/[id]/edit.vue b/frontend/modules/commercial/pages/clients/[id]/edit.vue
index be7ef59..dc043ce 100644
--- a/frontend/modules/commercial/pages/clients/[id]/edit.vue
+++ b/frontend/modules/commercial/pages/clients/[id]/edit.vue
@@ -82,7 +82,7 @@
@@ -114,6 +114,7 @@
v-model="information.foundedAt"
:label="t('commercial.clients.form.information.foundedAt')"
:readonly="businessReadonly"
+ :editable="true"
:error="informationErrors.errors.foundedAt"
/>
@@ -216,7 +217,7 @@