# #13 — Alignement du formulaire co-exposants

> Statut : ✅ **fait** — alignement des offres le 17/08/2026 (migration **0.9**), moquette le
> 22/08 (**0.14**), règle d'éligibilité le 23/08 (**0.16**).
> Reste bloquant pour la mise en ligne : **les tarifs de co-exposition en modes fidélité et
> partenaire**, que le client n'a jamais fournis.

**Formulaire :** `offres_stands_coexposants_2027` (id 57) · **Charge :** M
**Dépend de :** ~~[#05](REEX2027-05-section-electricite.md)~~ et ~~[#06](REEX2027-06-mobiliers.md)~~ — tous deux faits

## Demande client

> Formulaire "offres stands co-exposants" : https://reuseeconomyexpo.com/ecrire/?exec=formulaire&id_formulaire=40
>
> Mobiliers à actualiser : [même liste que pour les exposants]
>
> \+ dupliquer les sections électricité et personnalisations du stands dans ce formulaire.

⚠️ **L'URL citée pointe le formulaire 2026** (`id_formulaire=40`). Le 2027 est l'**id 57**. À
signaler au client pour qu'il ne travaille pas sur l'ancien.

## Ce qui a été fait

Le formulaire est passé de **3 saisies à 15**. Il propose désormais, dans l'ordre du formulaire
exposants :

```
case        | case_1        | Forfait de co-exposition obligatoire
explication | explication_1
radio       | radio_2       | Électricité               ← 25 options
explication | explication_5
fieldset    | fieldset_2    | Mobiliers                 ← 8 articles
explication | explication_4
fieldset    | fieldset_1    | Personnalisation du stand ← 3 articles
explication | explication_3
textarea    | textarea_1    | Autres besoins
```

### Les sections sont recopiées, pas réécrites

La migration lit `offres_stands_exposants_2027` et y prélève les blocs. Les redéclarer aurait créé
une **seconde définition de la même offre**, dont l'écart avec la première ne se serait vu qu'au
moment où un co-exposant commande un article dont la clé diffère d'une lettre. C'est exactement ce
que [#00](REEX2027-00-socle-prix-formidable-js.md) existe pour empêcher.

Conséquence heureuse : les huit mobiliers, les vingt-quatre coffrets et les trois personnalisations
arrivent avec des libellés, des classes et des clés tarifaires **identiques par construction**.
`contract_form.js` n'a demandé aucune ligne de code.

## 🐛 Un défaut de facturation corrigé au passage

La saisie mobilier du formulaire co-exposants stockait ses quantités sous les valeurs **`0` à `4`**,
là où le formulaire exposants les préfixe (`packs_mobiliers_small_2`).

| | Valeur stockée | Récapitulatif de contrat |
| --- | --- | --- |
| Exposant | `packs_mobiliers_small_2` | ✅ ligne facturée |
| Co-exposant | `2` | ❌ **aucune ligne** |

Le total affiché au moment du choix était **juste** — `updatePacksMobiliersPrice()` dépouille le
préfixe et retombe sur le bon nombre. Le récapitulatif, lui, reconnaît une ligne à son préfixe : le
mobilier d'un co-exposant était donc **compté dans le total envoyé, puis absent du détail
contractuel**.

Ce défaut n'est pas nouveau. Il existe depuis au moins **2024**, et en base **quatre réponses
mobilier de co-exposants 2025** sont dans ce cas. Rien n'a été sous-facturé — le total les
incluait — mais leur contrat ne montrait pas la ligne correspondante.

Depuis #00, ce genre d'écart ne serait plus muet : le récapitulatif compare le total enregistré au
total recalculé et signale la différence. C'est précisément ce contrôle qui aurait fait apparaître
le problème.

### Un contrôle pour que ça ne revienne pas

```sh
/Applications/MAMP/bin/php/php8.0.8/bin/php tests/formulaires/comparer_offres.php
```

Compare les deux formulaires : mêmes classes tarifaires, mêmes préfixes de valeurs stockées. Il ne
peut pas vivre dans `node --test` — il lit la base. Sur 2024, 2025 et 2026 il **échoue**, et c'est
la démonstration du défaut.

## L'éligibilité à la co-exposition — traité, migration 0.16

### Ce qui a été trouvé en s'y mettant

Le constat de départ était que
[article-bloc-coexposants.html:4](../squelettes/compositions/article-bloc-coexposants.html#L4)
conditionnait l'affichage du bloc à une **liste de stands codée en dur**. Sur la grille 2027 :

| Surface | Variantes ouvrant la co-exposition |
| ---: | --- |
| 4 m² | 0 / 7 |
| **8 m²** | **4 / 9** |
| 12 m² | 0 / 9 |
| 16 m² | 0 / 6 |
| 24 m² | 0 / 6 |
| 32 m² | 0 / 6 |
| 64 m² | 0 / 3 |

**4 stands sur 47.** Un exposant qui prend 64 m² ne pouvait pas ajouter de co-exposant ; un 8 m²
premium non plus, sans raison apparente.

⚠️ **Correction : cette règle ne s'appliquait en réalité nulle part.** Vérification faite en base,
la composition `bloc-coexposants` **n'est appliquée à aucun objet du site** — ni article, ni
rubrique, et aucun squelette ne l'inclut. C'est un vestige de 2023, quand le formulaire de
co-exposition était rattaché à un article ; depuis 2025 il l'est à une rubrique, rendue par
`rubrique-section-formulaire-espace-exposants.html`, qui ne vérifiait rien.

Autrement dit, le formulaire de co-exposition était offert à **tout exposant ayant payé son
acompte**, quelle que soit la taille de son stand, et le seul garde-fou était les trois options du
menu. La liste périmée n'excluait personne : elle ne servait plus. Le point signalé au client était
donc juste sur le fond — la règle ne tenait plus — mais faux dans son chiffrage.

Sur les 13 entrées de la liste, **9 désignent des stands qui n'existent pas** — 7 supprimés par
[#02](REEX2027-02-grille-stands-2027.md), et `16m2_4_angles` / `20m2_4_angles` qui n'ont jamais
existé dans aucune édition.

Le formulaire `coexposition_2027` dit la même chose autrement : « 1 co-exposant (stand de 8 m²) »,
« 2 co-exposants (stand de **10 m²**) », « 3 co-exposants (stand de **16 et 20 m²**) ». Deux des
trois paliers reposent sur des surfaces supprimées. La table des tarifs, elle, connaît un palier à
4 co-exposants que le formulaire ne propose pas.

### La règle retenue

**4 m² par exposant, l'exposant principal compris** — réponse du client du 21/08/2026. Le plafond
d'un stand vaut donc `surface ÷ 4 − 1`, borné par le nombre de niveaux tarifés.

| Stand | 4 m² | 8 | 12 | 16 | 24 | 32 | 64 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| Co-exposants | 0 | 1 | 2 | 3 | 5 | 7 | **15** |

Le client avait d'abord proposé un plafond arbitraire à 4 au-delà de 24 m², puis confirmé la
formule et la montée **jusqu'à 15, au prix linéaire**. Les quatre premières valeurs de la formule
coïncident exactement avec ce qu'il avait écrit à la main : c'est ce qui a permis de retenir la
formule plutôt que la liste.

Elle vit dans [reex_coexposition.php](../plugins/reuseeconomyexpo/inc/reex_coexposition.php), en un
seul endroit, et se déduit de `reex_stand_decrire()` : la prochaine refonte de grille ne la
périmera pas.

### Où elle s'applique, et pourquoi à trois endroits

| Où | Quoi |
| --- | --- |
| `reex_coexposition.php` | la règle, le barème, les options du formulaire |
| `rubrique-section-formulaire-espace-exposants.html` | dépose `data-coexposants-max` ; remplace le formulaire par un encart quand il vaut 0 |
| `contract_form.js` | masque les niveaux hors de portée du stand |

Le partage est le même que pour les stands : **le formulaire propose tout ce qui est tarifé**, le
JavaScript n'en laisse voir qu'une partie. La limite dépend de l'auteur, pas du formulaire — un
formulaire Formidable ne sait pas quel stand a choisi celui qui le remplit. Le calcul reste donc
côté serveur, et ne traverse que sous forme de nombre : il n'y a pas de transposition JavaScript de
la règle, contrairement aux stands.

Un stand de 4 m² ne voit pas le formulaire du tout, mais un encart qui explique pourquoi. Lui
présenter un formulaire pour lui refuser toutes les options serait pire que de ne pas le présenter.

### Le formulaire, migration 0.16

- **L'encart est composé depuis la grille.** Il énumérait 8, 10, 16 et 20 m² — trois surfaces qui
  n'existent plus. Il est maintenant produit par `reex_coexposants_bareme()`, donc juste par
  construction.
- **Le choix monte à 15**, options déduites de la table des tarifs, et les libellés ne mentionnent
  plus de surface — « 2 co-exposants (stand de 10 m²) » était faux depuis la refonte.
- **Douze paires de champs** email + société ont été ajoutées, révélées au fil du compteur par
  l'`afficher_si` de Saisies. Aucun JavaScript à écrire : le mécanisme existait déjà dans ce
  formulaire.

⚠️ **La condition d'affichage est énumérée en `==`, pas exprimée en `>=`.** L'opérateur existe,
mais les deux implémentations de Saisies divergent : `saisies_afficher_si.js` force un `Number()`,
si bien que `'aucun' >= 5` y vaut faux, tandis que PHP compare deux chaînes et le tient pour
**vrai**. Un champ obligatoire masqué à l'écran et exigé à la validation aurait bloqué l'envoi du
formulaire sans rien montrer. Un test interdit désormais toute comparaison ordonnée dans ces
conditions.

## 🔴 Ce qui reste bloqué : deux modes tarifaires

La grille de co-exposition ne connaît que **adhérent** et **non-adhérent**. Les modes **fidélité**
et **partenaire** manquent à tous les niveaux, et le forfait d'inscription manque en partenaire.

Depuis [#00](REEX2027-00-socle-prix-formidable-js.md), un mode sans prix ne vaut plus zéro : il fait
apparaître un bandeau d'anomalie et barre le total. Un exposant fidèle qui veut un co-exposant le
verra donc. C'est le comportement voulu — mieux vaut une alerte visible qu'un contrat au mauvais
prix — mais **cela empêche l'ouverture des inscriptions**.

⚠️ Attention à la formulation dans la relance : la réponse du client « nous n'avions que deux
tarifs, 350 € adhérent et 550 € non-adhérent » décrit le **forfait d'inscription**, pas la grille de
co-exposition, qui facture en plus 330 € / 500 € par co-exposant. Agir sur cette base effacerait une
ligne de recette.

## Critères d'acceptation

- [x] Les 8 mobiliers sont proposés au co-exposant, aux mêmes prix que l'exposant.
- [x] Les sections électricité et personnalisation sont présentes et correctement totalisées —
      mêmes classes, donc même traitement par `contract_form.js`.
- [x] Le récapitulatif de contrat du co-exposant reflète ses choix — c'est ce que corrige le
      défaut de préfixe ci-dessus.
- [x] Concordance des clés de prix entre les deux formulaires, vérifiée explicitement
      (`tests/formulaires/comparer_offres.php`), comme le demandait le point d'attention JS.
- [x] Moquette proposée des deux côtés — migration 0.14, propagée par recopie.
- [x] Règle d'éligibilité appliquée, et déduite de la surface plutôt qu'énumérée.
- [x] Le barème affiché dans le formulaire est composé depuis la grille, non saisi à la main.
- [x] Vérifié bout en bout sur un auteur réel : 4 m² → 0, 8 m² → 1, 24 m² → 5, 64 m² → 15,
      stand habillé → 0, aucun stand choisi → 0. Base restaurée à l'identique.
- [ ] Contrôle visuel dans un navigateur — demande un compte co-exposant.
- [ ] **Tarifs fidélité et partenaire** — décision client, bloquante pour la mise en ligne.
