---
suivi: 1595
date: 2026-09-17
sujet: Prorogation des conditions suspensives par avenant
chantier: foncier / promesses
type: feature
statut: poussé
hash: 16b41ef0
fichiers:
  - app/Models/ConditionSuspensive.php
  - app/Models/ConditionSuspensiveProrogation.php
  - app/Models/PromesseAvenant.php
  - app/Services/Foncier/ConditionSuspensiveService.php
  - app/Services/Foncier/PromesseAvenantService.php
  - app/Http/Controllers/Foncier/PromesseAvenantController.php
  - app/Http/Controllers/Foncier/PromesseFonciereController.php
  - routes/web.php
  - database/migrations/2026_09_17_160000_create_condition_suspensive_prorogations_table.php
  - resources/js/Pages/Foncier/Promesses/Show.vue
  - tests/Unit/Promesse1595CsProrogationSourceTest.php
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - public/build/
  - docs/suivi/SUIVI_1595_prorogation_conditions_suspensives.md
---

# SUIVI #1595 — Prorogation des CS par avenant

## PROMPT ENVOYÉ

Option A Robin : une CS prorogée par un avenant reçoit une nouvelle
date butoir. L’ancienne est conservée. Mention de l’avenant.
Pas de jeu de CS par avenant. Pas de bouton indépendant des avenants.
Alertes recalculées sur la nouvelle date. Réactivation proposée,
jamais imposée. Doublons prod non fusionnés.

## FICHES LUES

- **#1090 LEÇON** — CS = type + date_butoir + statut, aucune
  transition auto. Alertes sur `date_butoir` (moteur #1077 intact).
  Impact : on **met à jour** `date_butoir` (effectif) pour que les
  alertes suivent sans toucher `ConditionSuspensiveEcheanceType`.
- **#1118 LEÇON** — avenants empilés, jamais recopiés dans la
  promesse. Audit explicite : CS `date_butoir` **hors scope**.
  Impact : confirmé, un avenant ne portait **aucune** notion de CS.
  Expiration effective = dernier avenant non null sinon origine.
- **#1594 LEÇON** — même écran `Promesses/Show.vue` déjà pleine
  largeur. Impact : on ne retouche ni largeur ni ventilation.
  Avenant overlay prix scalaire inchangé.

## STRUCTURE ÉTABLIE

### Condition suspensive

Table `conditions_suspensives`
(`database/migrations/2026_07_30_190100_create_conditions_suspensives_table.php`) :
`promesse_id`, `type_cs_id`, `date_butoir`, `statut` (défaut
`a_lever`), `date_levee`, `alerte_active` (défaut true),
`commentaire`.

Statuts (`FoncierReferentiels::STATUTS_CS`) : voir mapping.

Mécanisme d’alerte
(`app/Support/Foncier/Alertes/Types/ConditionSuspensiveEcheanceType.php`) :
statut `a_lever` + `date_butoir`. Paliers J-30/15/7/0 si
`alerte_active`. Dépassement (`joursRestants < 0`) → tâche
d’arbitrage même si alerte coupée.

Actions UI (`ConditionSuspensiveController` + Show.vue) :
- **Lever** : `lever()` → statut `levee` + `date_levee` aujourd’hui.
- **Couper alerte** : PUT `alerte_active = false`.
- **Réactiver alerte** : PUT `alerte_active = true`.
Dépassée non levée : `estDepasseeNonLevee()` =
`statut === a_lever` ET `joursRestants < 0`
(`app/Models/ConditionSuspensive.php` — méthodes `joursRestants` /
`estDepasseeNonLevee`).

Lever : `ConditionSuspensiveController.php` méthode `lever`.
Couper / réactiver : PUT `alerte_active` via `update`
(`ConditionSuspensiveController.php` validate `alerte_active`).

### Avenant

Table `promesse_avenants`
(`database/migrations/2026_07_31_110000_create_promesse_avenants_table.php`) :
`numero`, `type`, `date_avenant`, `nouvelle_date_expiration`,
`nouvelle_date_acte_prevue`, `nouveau_prix_numeraire`,
`nouveau_prix_valorise`, `motif`, `createur_id`.
**Aucune colonne CS.**

Expiration effective : `PromesseFonciere::dateExpirationEffective()`
— dernier avenant avec `nouvelle_date_expiration` non null, sinon
`promesses.date_expiration`. D’où « — → 30/06/2027 (2 avenants) ».

Pas d’endpoint `update` avenant : création seulement (#1118).
Pour corriger la promesse #5 (avenants déjà là) : action
`prorogerConditions` **sur un avenant existant**, pas un bouton CS
isolé (option C écartée).

### Un avenant prorogeait-il déjà les CS ?

**Non.** #1118 fiche : « CS (#1090) `date_butoir` Hors scope ».
`PromesseAvenantService::creer` n’écrivait que l’avenant + clôture
tâche expiration promesse.

## MÉCANISME D’HISTORIQUE RETENU

Colonnes existantes **insuffisantes** : une seule `date_butoir`,
écrasement silencieux. Table dédiée justifiée :

`condition_suspensive_prorogations`
(`date_butoir_avant`, `date_butoir_apres`, `promesse_avenant_id`,
`alerte_reactivatee`, `createur_id`).

`conditions_suspensives.date_butoir` = date **effective** (alertes
#1090 inchangées). Origine = première `date_butoir_avant`.
Qui / quand = `createur_id` + `created_at` + audit
`cs_prorogation` sur la CS et la promesse.

Pas un jeu de CS par avenant.

## ALERTES APRÈS PROROGATION

Nouvelle `date_butoir` → `estDepasseeNonLevee` faux si date future.
Moteur d’alerte relit `date_butoir` au prochain passage.
Si plus dépassée : clôture tracée de la tâche d’arbitrage CS
(`resoudreCause`, même patron que l’expiration promesse).
Réactivation `alerte_active` **seulement** si l’utilisateur coche
« Réactiver l’alerte ». Défaut : non coché.

## DOUBLONS

Aucune fusion, aucune suppression. Requête SQL ci-dessous.

## SQL

Conditions dépassées sur promesses prorogées :

```sql
SELECT p.id AS promesse_id, df.reference AS dossier,
       cs.id AS cs_id, tcs.libelle AS type_cs,
       cs.date_butoir, cs.statut, cs.alerte_active
FROM conditions_suspensives cs
JOIN promesses p ON p.id = cs.promesse_id AND p.deleted_at IS NULL
JOIN dossiers_fonciers df ON df.id = p.dossier_foncier_id
JOIN types_conditions_suspensives tcs ON tcs.id = cs.type_cs_id
WHERE cs.statut = 'a_lever'
  AND cs.date_butoir < CURDATE()
  AND EXISTS (
    SELECT 1 FROM promesse_avenants a
    WHERE a.promesse_id = p.id
      AND a.nouvelle_date_expiration IS NOT NULL
  );
```

Doublons de type CS sur une même promesse :

```sql
SELECT p.id AS promesse_id, df.reference AS dossier,
       tcs.libelle AS type_cs, COUNT(*) AS n,
       GROUP_CONCAT(cs.id ORDER BY cs.id) AS cs_ids,
       GROUP_CONCAT(cs.date_butoir ORDER BY cs.id) AS butoirs
FROM conditions_suspensives cs
JOIN promesses p ON p.id = cs.promesse_id AND p.deleted_at IS NULL
JOIN dossiers_fonciers df ON df.id = p.dossier_foncier_id
JOIN types_conditions_suspensives tcs ON tcs.id = cs.type_cs_id
GROUP BY p.id, df.reference, cs.type_cs_id, tcs.libelle
HAVING COUNT(*) > 1;
```

MySQL local 3306 refusé : exécuter sur OVH.

## SYNTHÈSE

Option A : à la création d’avenant, et sur un avenant existant,
l’utilisateur coche les CS non levées, date (défaut = expiration
avenant), réactivation d’alerte optionnelle. Historique table
dédiée. `date_butoir` reste l’effectif.

## DÉPLOIEMENT-TEST

```
git pull origin main
php artisan migrate --force
php artisan optimize:clear
php artisan view:clear
php artisan route:clear
php artisan journal:sync
```

Promesse #5 DF-2026-0009 : sur l’avenant n°2 (expiration
30/06/2027), « Proroger des conditions », cocher les CS périmées,
date 2027-06-30, cocher réactiver alerte si besoin. Ne pas
supprimer le second « PC purgé » (2027-06-30) — ménage manuel.

## LEÇON

#1118 a volontairement laissé les CS hors avenant. Une prorogation
de promesse sans toucher `date_butoir` laisse les alertes CS
hurler un an. L’effectif doit vivre sur `date_butoir` (le moteur
#1090 n’en connaît pas d’autre) ; l’historique va dans une table
fille, comme les avenants vis-à-vis de la promesse.

Pas d’endpoint `update` avenant : pour une promesse déjà prorogée,
l’action doit être **sur l’avenant existant**, sinon on force un
troisième avenant ou on retombe sur l’option C.
