feat(commercial) : onglet adresse — Select « Type d'adresse » + Sites en multiselect, ligne 1 réagencée (ERP-110)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 2m0s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m19s

- Sites Starseed : 3 cases -> multiselect a tags « Sites » (required).
- Usage adresse : 3 cases Prospect/Livraison/Facturation -> Select unique « Type d'adresse » (Prospect / Livraison / Facturation / Adresse + Facturation), obligatoire sans option vide, conditionnant le bouton « Valider ». Pur sucre front : le back recoit toujours isProspect/isDelivery/isBilling (aucune RG modifiee), exclusivite Prospect devenue structurelle.
- Email de facturation conditionnel (Facturation / Adresse + Facturation) deplace en ligne 1.
- Ligne 1 : Type d'adresse | Sites | Contact rattache | Email ; le reste (Categorie, Pays, CP, Ville, Adresse...) en lignes suivantes.
- Email : MalioInputText -> MalioInputEmail (lowercase, ERP-101/RG-1.21) sur facturation ET contact.
- Helpers front testables addressFlagsFromType / addressTypeFromFlags + gating canValidateAddresses (type obligatoire) dans new.vue / edit.vue.
This commit is contained in:
2026-06-04 15:58:01 +02:00
parent c2282fdac5
commit 5834d7b225
7 changed files with 146 additions and 83 deletions
@@ -1,5 +1,7 @@
import { describe, it, expect } from 'vitest'
import {
addressFlagsFromType,
addressTypeFromFlags,
applyProspectExclusivity,
buildClientFormTabKeys,
canSelectDeliveryOrBilling,
@@ -197,6 +199,32 @@ describe('isBillingEmailRequired (RG-1.11)', () => {
})
})
describe('type d\'adresse (Select front) <-> drapeaux back', () => {
it('addressFlagsFromType mappe chaque type vers les bons drapeaux', () => {
expect(addressFlagsFromType('prospect')).toEqual({ isProspect: true, isDelivery: false, isBilling: false })
expect(addressFlagsFromType('delivery')).toEqual({ isProspect: false, isDelivery: true, isBilling: false })
expect(addressFlagsFromType('billing')).toEqual({ isProspect: false, isDelivery: false, isBilling: true })
expect(addressFlagsFromType('delivery_billing')).toEqual({ isProspect: false, isDelivery: true, isBilling: true })
})
it('addressTypeFromFlags reconstruit le type (Prospect prioritaire, livraison+facturation groupes)', () => {
expect(addressTypeFromFlags({ isProspect: true, isDelivery: false, isBilling: false })).toBe('prospect')
expect(addressTypeFromFlags({ isProspect: false, isDelivery: true, isBilling: false })).toBe('delivery')
expect(addressTypeFromFlags({ isProspect: false, isDelivery: false, isBilling: true })).toBe('billing')
expect(addressTypeFromFlags({ isProspect: false, isDelivery: true, isBilling: true })).toBe('delivery_billing')
})
it('addressTypeFromFlags retourne null quand aucun drapeau (amorce vierge -> bouton bloque)', () => {
expect(addressTypeFromFlags({ isProspect: false, isDelivery: false, isBilling: false })).toBeNull()
})
it('aller-retour type -> drapeaux -> type stable pour les 4 types', () => {
for (const type of ['prospect', 'delivery', 'billing', 'delivery_billing'] as const) {
expect(addressTypeFromFlags(addressFlagsFromType(type))).toBe(type)
}
})
})
describe('regles type de reglement (RG-1.12 / RG-1.13)', () => {
it('banque obligatoire si VIREMENT', () => {
expect(isBankRequiredForPaymentType('VIREMENT')).toBe(true)
@@ -187,6 +187,45 @@ export function isBillingEmailRequired(flags: AddressFlagsDraft): boolean {
return flags.isBilling
}
/**
* Type d'adresse expose a l'utilisateur (Select unique remplacant les trois
* cases a cocher). Sucre purement front : le back continue de recevoir les
* drapeaux isProspect / isDelivery / isBilling (aucune RG modifiee). Les seules
* combinaisons proposees respectent l'exclusivite Prospect (RG-1.06/07/08).
*/
export type AddressType = 'prospect' | 'delivery' | 'billing' | 'delivery_billing'
/**
* Mappe le type d'adresse choisi vers les trois drapeaux back.
* « Adresse + Facturation » = livraison ET facturation sur la meme adresse.
*/
export function addressFlagsFromType(type: AddressType): AddressFlagsDraft {
switch (type) {
case 'prospect':
return { isProspect: true, isDelivery: false, isBilling: false }
case 'delivery':
return { isProspect: false, isDelivery: true, isBilling: false }
case 'billing':
return { isProspect: false, isDelivery: false, isBilling: true }
case 'delivery_billing':
return { isProspect: false, isDelivery: true, isBilling: true }
}
}
/**
* Reconstruit le type d'adresse a partir des drapeaux (consultation / edition
* d'une adresse persistee, ou amorce vierge). Retourne null si aucun drapeau
* n'est positionne — le Select reste alors a saisir (et bloque la validation).
*/
export function addressTypeFromFlags(flags: AddressFlagsDraft): AddressType | null {
if (flags.isProspect) return 'prospect'
if (flags.isDelivery && flags.isBilling) return 'delivery_billing'
if (flags.isDelivery) return 'delivery'
if (flags.isBilling) return 'billing'
return null
}
/** Code stable du type de reglement « virement » (cf. PaymentType.code, RG-1.12). */
const PAYMENT_TYPE_TRANSFER = 'VIREMENT'