# PROJECT.md — ERP Immobilier Interne

> Fichier de contexte à coller en début de chaque session Claude.
> Mettre à jour au fil de l'avancement du projet.

---

## 🔁 Méthode de travail (mécanique des sessions Claude ↔ Cursor ↔ OVH)

> **À lire en priorité par toute nouvelle session Claude.** Décrit exactement comment Robin (DSI) et Claude collaborent. Respecter cette mécanique à l'identique.

### Rôles
- **Robin** = DSI seul sur le projet. Pilote, teste en prod, fait les déploiements OVH, colle les retours.
- **Claude** = architecte / orchestrateur. NE code PAS directement. Claude **rédige des prompts numérotés "SUIVI #NNN"** destinés à Cursor, cadre les décisions métier (souvent via questions à choix), diagnostique les bugs à partir des logs/captures, et tient ce PROJECT.md à jour.
- **Cursor** = exécutant. Reçoit le prompt SUIVI #NNN, écrit le code, build le front, commit et push sur GitHub (`HECTAREG/erp-immo`), puis renvoie une **synthèse** (avec le hash du commit).

### Boucle type d'un changement
1. Robin décrit un besoin ou colle une capture d'écran d'un bug.
2. Claude **pose des questions de cadrage** si besoin (choix d'options), puis rédige un **prompt SUIVI #NNN** complet (diagnostic d'abord si c'est un bug ou un sujet sensible, sinon directement le fix).
3. Robin colle le prompt dans Cursor → Cursor code/commit/push → renvoie une **synthèse** (cause, fix, fichiers, **bundle JS**, **commit hash**, post-déploiement).
4. Robin colle la synthèse à Claude.
5. Claude donne la **séquence de déploiement OVH exacte** (voir ci-dessous) avec le hash attendu.
6. Robin déploie en SSH, teste, colle le retour (ou une capture).
7. Claude confirme / corrige, et passe au suivant.

### Format des prompts SUIVI #NNN (rédigés par Claude)
- Première ET dernière ligne = `SUIVI #NNN`.
- Pour un bug ou un sujet sensible (compta, paiement, workflow) : **PHASE 1 DIAGNOSTIC d'abord (ne pas coder)**, puis PHASE 2 fix après validation.
- Toujours inclure : contexte, règle métier, fix attendu, ⚠️ garde-fous (ne pas casser X), TESTS, et LIVRABLE (diff + .vue → rebuild + hash bundle + commit + push HECTAREG + `git log --oneline -1 origin/main`).
- Demander une **SYNTHÈSE dans un bloc de code markdown copiable** à la fin.
- Rappeler `php -l` + `ReflectionClass` sur les fichiers PHP modifiés avant push (2 crises 500 sur classes/redéclarations non détectées par les tests sqlite skipped).

### Règles de déploiement OVH (cluster113 mutualisé) — IMPÉRATIF
- **JAMAIS** sur OVH : `npm run build`, `composer install`, `optimize`, `config:cache` (vendor/ et public/build/ sont COMMITÉS ; Cursor re-suggère `npm run build` à tort → IGNORER).
- Séquence standard : `cd /home/hectare-envol/envol.hectare.fr` → `git pull origin main` → `php artisan optimize:clear` → `php artisan view:clear` (+ `route:clear` si nouvelles routes, + `migrate --force` si migration) → hard refresh Ctrl+F5.
- Vérifier le hash du bundle : `grep -o "NomBundle-xxxx\.js" public/build/manifest.json`.
- **Sorties HTTPS BLOQUÉES depuis SSH/CLI** → tout appel Intacct/externe doit passer par le **web (PHP-FPM)** via un endpoint admin (ex. `POST /admin/intacct/sync-fournisseurs`), JAMAIS via `php artisan` en CLI.
- Toujours vérifier `git log --oneline origin/main` avant de déployer (Cursor oublie parfois de push ou push en parallèle).
- Tests sqlite de Cursor souvent "skipped" (pas de pdo_sqlite) → un `use` manquant / une redéclaration ne déclenchent AUCUNE alerte → **toujours tester manuellement la compta après un commit touchant DepotFactureController / ComptabilisationController** ; diagnostiquer les 500 via `grep "production.ERROR" storage/logs/laravel.log`.

### Type de rendu attendu de Claude
- Réponses concises, en français. Donne la séquence de déploiement en bloc bash copiable.
- Pour les décisions métier : utiliser des questions à choix (cadrage) avant de rédiger le prompt.
- Tenir ce PROJECT.md à jour en fin de session (sections datées + cases cochées dans "En cours"). ⚠️ Réserve : si le tab DOC-TECH a modifié le fichier entre-temps, faire un diff avant d'écraser.

---

## Contexte général

- **Type** : ERP métier interne (pas un produit à vendre)
- **Secteur** : Promotion immobilière
- **Objectif** : Remplacer Pegao (trop cher en développements spécifiques)
- **Équipe dev** : DSI seul, ancien développeur web (HTML, PHP, JS)
- **Horizon** : 6 à 12 mois pour les premiers modules en production

---

## Stack technique

| Couche | Technologie |
|---|---|
| Backend | **Laravel 13 (PHP 8.3)** |
| Base de données | **MySQL OVH cluster113 mutualisé** |
| Cache / Files | **File cache** (pas de Redis sur mutualisé) ; `QUEUE_CONNECTION=sync` |
| Frontend | Vue.js 3 + Inertia.js |
| CSS | Tailwind CSS |
| Composants UI | PrimeVue |
| Authentification | SSO Microsoft 365 via Laravel Socialite |
| Intégration M365 | Microsoft Graph API |
| Hébergement | **OVHcloud mutualisé (FR)** — cluster113 |
| IA intégrée | API Claude (Anthropic) |
| Versioning | GitHub HECTAREG/erp-immo (repo privé) |
| IDE | Cursor + Claude Code (debug intensif) |

### ⚠️ Conventions front à respecter (notes techniques permanentes)

- **Ziggy n'est PAS installé** dans le projet. Le helper `route()` côté Vue retourne `undefined`.
- Pour tout appel HTTP depuis Vue : **toujours utiliser des URLs directes** (`/api/...`, `/programmes/{id}/...`).
- **Jamais** d'appel à `route('...')` côté Vue.
- 👉 À mentionner systématiquement dans les prompts Cursor qui touchent du front Vue avec endpoints.

### ⚠️ Conventions Cursor & assets (notes techniques permanentes)

- **`public/build/` est committé** dans le repo (cohérent avec `vendor/` committé) → cf. Workflow déploiement OVH plus bas.
- **Cursor oublie souvent `public/build/`** lors des commits front : **toujours vérifier** que le build est bien committé avant de pousser, sinon la prod n'a pas les assets.
- **Cursor pousse parfois des modifs incomplètes** : relire les diffs avant push, surtout sur les migrations et les seeders.

---

## Architecture d'hébergement

```
OVHcloud Public Cloud (🇫🇷 France — Roubaix / Gravelines)
├── Instance B2-7 (2 vCores, 7GB RAM)  → Laravel + Redis (~25€/mois)
├── PostgreSQL managé                   → Base de données (~25€/mois)
├── Redis managé                        → Cache et jobs (~10€/mois)
└── Object Storage                      → Exports, backups (~5€/mois)
Total hébergement                       → ~65€/mois

Microsoft 365 (existant)
├── Entra ID            → Authentification SSO
└── SharePoint          → GED (documents programmes)
```

> Les documents restent sur SharePoint. Pas de GED custom à développer.
> OVHcloud gère automatiquement : backups PostgreSQL, SSL, mises à jour OS.

### ⚠️ Contraintes infra OVH constatées (mai 2026)

- **Sorties HTTPS depuis SSH/CLI bloquées** sur l'hébergement OVH (notamment `graph.facebook.com`, `api.intacct.com`).
- Conséquence : les commandes artisan qui appellent des APIs externes ne fonctionnent pas en CLI.
- **Workaround systématique** : exposer toute commande artisan utile via un **endpoint admin équivalent** qui passe par la voie HTTP PHP-FPM (non bloquée).
- Pattern type : `php artisan x:y` ↔ route `POST /admin/x/y` qui appelle le même service.
- Limite upload PHP plafonnée à 128M sur cluster mutualisé (ticket OVH en cours pour les AO > 350 Mo).

---

## Budget mensuel estimé

| Poste | Prix/mois |
|---|---|
| OVHcloud Public Cloud | ~65€ |
| API Claude (extraction factures) | ~20-50€ |
| Cursor (développement) | ~20€ |
| GitHub (privé) | Gratuit |
| **Total** | **~105-135€/mois** |
| **Pegao actuel** | **2 400€/mois** |
| **Économie mensuelle** | **~2 265€** |
| **Économie annuelle** | **~27 000€** |

---

## Portabilité et indépendance

- **Code** : sur GitHub, exportable à tout moment
- **Données** : PostgreSQL standard, export CSV/JSON/Excel natif depuis l'app
- **Hébergeur** : changement possible en quelques heures (pg_dump + git clone)
- **Aucun enfermement propriétaire** : stack 100% open source standard
- **Reprise par une ESN** : Laravel est une stack connue de tous les prestataires

---

## Prototype Lovable — acquis réutilisables

> Un collègue a réalisé un prototype React/TypeScript (Vite + shadcn/ui) sur Lovable.
> Ce n'est pas une base de code à migrer (stack différente, données en mémoire, pas de backend),
> mais c'est une mine d'information pour accélérer le développement Laravel.

### Repo : `facturationenvol-main` (React + TypeScript + shadcn/ui)

### Ce qui est directement exploitable

**Logique métier validée (fichier `src/lib/calculations.ts`)** — à transposer en PHP/Laravel :
```typescript
// Montant total marché avec avenants
totalMarcheHT(marche, avenants) = marche.montant_marche_ht + SUM(avenants.montant_ht)

// Net TTC d'une situation après RG et compte prorata
situationNetTTC(s) = montant_ht * (1 - rg_pct/100) * (1 - cp_pct/100) * (1 + tva/100)

// Reste à régler HT
resteAReglerHT = totalMarcheHT - SUM(situations.montant_ht)

// % avancement
avancementPct = SUM(situations.montant_ht) / totalMarcheHT * 100

// Delta marge TMA
tmaDeltaMargeTTC = facture_client_ttc - facture_entreprise_ttc
```

**Modèle de données confirmé (types TypeScript → migrations PostgreSQL)** — voir section "Tables clés" ci-dessous.

**Deux jeux de données réels** utilisables comme fixtures de test :
- `arboreaData.ts` — Programme ARBORÉA (Montpellier, réf. 340031-ARBOREA, 13 lots travaux + 17 lots prestataires, marchés réels)
- `louMarquesData.ts` — Programme LOU MARQUES (données similaires)

**Lots techniques réels du programme ARBORÉA** (confirmés dans le prototype) :

| Code | Libellé | Entreprise |
|---|---|---|
| 01 | Gros œuvre | ST ROCH BTP |
| 02 | Étanchéité | VINOIS |
| 03 | Men. Ext. | LES ZELLES |
| 04 | Men. Int. | BLACHERE |
| 05 | Cloisons | OCCIPLAC |
| 06 | Carrelage | ATC |
| 07 | Serrurerie | HELIX |
| 08 | Plomberie | ACLIMATIS |
| 09 | Électricité | IDEM |
| 10 | Peinture | RICCADI |
| 11 | Façades | BSA |
| 12 | Ascenseurs | ORONA |
| 13 | Espaces verts | SARIVIERE |
| P00 | Achat terrain | SERM |
| P01a | Architecte | A+ |
| P01b | Architecte intérieur | A+ (partie TORRES) |
| P02 | Assurance | SMA BTP |
| P07/P08 | Étude de sol | EGSA |
| P12 | Contrôle Technique | SOCOTEC |
| P14 | Certification NF | Cerqual |
| P15 | CSPS | SOCOTEC |
| P16/P16B | Repro | Arts Helio / Digitalis |
| P17x | Commercial | MADN, IMP'ACT, KUTCH STUDIO, UNLATCH, ECOPUB, ADIMO |

**Concessionnaires identifiés** : ENEDIS, Réseau de chaleur, AEP/EU/EP, Orange

**Pages UI validées fonctionnellement** (à transposer en Vue.js + PrimeVue) :
- Dashboard (KPIs + courbe S + alertes paiements en retard + synthèses)
- Travaux & Situations (par lot, avec avenants et barre d'avancement)
- Prestataires
- TMA / TS (Travaux Modificatifs Acquéreurs / Travaux Supplémentaires)
- Concessionnaires (VRD)
- Commercialisation (lots + dépenses communication)
- Taxes
- Plan de Trésorerie (tableau glissant N mois, recettes/dépenses, solde cumulé)
- Reste à Payer (travaux + RG + compte prorata + concessionnaires + taxes)

**Catégories TMA confirmées** : Oubli/Erreur, Modification, Prestations sup., Aléas, Transfert +, Transfert -, Offres

**Types de communication** : PUBLICITE, AGENCES, DIVERS

**Statuts commerciaux** : En stock, Réservé, Acté, Annulé

### Ce que le prototype ne couvre pas (à développer entièrement dans Laravel)
- Auth SSO Microsoft / Entra ID + groupes M365
- Backend, base de données, persistance (tout est en mémoire dans le prototype)
- Bilan financier 5 rubriques (Terrain / VRD / Construction / FG / Charges directes)
- Workflow de validation factures (NV1 → PDG → Comptable → DAF)
- Intégration Sage Intacct (synchro fournisseurs, factures, paiements)
- Intégration SharePoint / Graph API (GED + arborescence automatique)
- Code analytique PRG-{département}-{code}
- CRM acquéreurs + réservations VEFA + prescripteurs
- OCR factures via API Claude

---

## Modules à développer

| Priorité | Module | Statut | Notes |
|---|---|---|---|
| 1 | Socle technique + Auth M365 | 🔲 À faire | SSO + Graph API + migrations de base |
| 2 | CRM acquéreurs | 🔲 À faire | Très spécifique à nos process |
| 3 | Finance / bilan promoteur | 🔲 À faire | Cœur métier, à bien sécuriser |
| 4 | Marchés & Factures | 🔲 À faire | Circuit validation + synchro Intacct |
| 5 | Développement foncier | 🔲 À faire | Parcelles, promesses, faisabilité |
| 6 | Suivi chantier | 🔲 À faire | Marchés, situations de travaux |
| 7 | Livraison / SAV / GPA | 🔲 À faire | Le plus complexe, en dernier |

---

## Roadmap indicative

| Période | Objectif |
|---|---|
| M1 | Socle Laravel + Auth SSO M365 + intégration Graph API |
| M2-M3 | CRM acquéreurs (prospects → réservation → contrat VEFA) |
| M4-M5 | Finance / bilan promoteur |
| M6-M7 | Marchés & Factures + synchro Sage Intacct |
| M8-M9 | Développement foncier |
| M10-M12 | Suivi chantier (version basique) |
| Post M12 | Livraison / SAV / GPA |
| Futur | Remplacement Unlatch (CRM commercial, signature Yousign, purge AR24, portail acquéreur) |

---

## Modèle de données principal

```
SOCIÉTÉ
└── PROGRAMME
    ├── LOTS
    │   ├── GRILLES DE PRIX (historique)
    │   ├── OFFRES COMMERCIALES (temporaires)
    │   ├── ACQUÉREURS (CRM)
    │   │   └── RÉSERVATIONS / CONTRATS VEFA
    │   └── TMA (Travaux Modificatifs Acquéreurs)
    ├── BILAN FINANCIER (5 rubriques)
    │   ├── 1. TERRAIN
    │   ├── 2. VRD (Concessionnaires)
    │   ├── 3. CONSTRUCTION
    │   ├── 4. FRAIS GÉNÉRAUX
    │   └── 5. CHARGES DIRECTES
    ├── MARCHÉS (prestataires + travaux)
    │   ├── AVENANTS
    │   ├── SITUATIONS DE TRAVAUX
    │   └── FACTURES → VALIDATIONS
    ├── PRESCRIPTEURS → COMMISSIONS
    ├── ASSOCIÉS → PARTICIPATIONS → APPELS DE FONDS
    └── DOCUMENTS (SharePoint via Graph API)

FOURNISSEURS (synchronisés depuis Sage Intacct)
PRESTATIONS (référentiel lots/codes comptables)
```

---

## Référentiel des prestations (issu du fichier LISTES_LOTS_ENVOL.csv)

57 prestations organisées en 6 catégories avec codes comptables :

```sql
prestations
id, libellé, catégorie, code_prestation, description, compte_comptable

-- Catégories :
-- 1_HONORAIRES  → comptes 331xxx / 334xxx / 622xxx
-- 2_TRAVAUX     → compte 333000 (tous les lots travaux)
-- 3_CONCESSIONNAIRES → compte 332000
-- 4_TAXES       → compte 332100
-- 5_COMMERCIAL  → comptes 623xxx / 707xxx / 334xxx
```

Exemples clés :
- `101_ARCHITECTE` → 331453
- `102_MOE` → 331452
- `203_GROS_OEUVRE` → 333000
- `505_TMA` → 707201 (compte recette acquéreur)
- `Commisson` → 334402

---

## Structure du bilan financier (5 rubriques — nomenclature Flora)

```
RUBRIQUE 1 — TERRAIN
├── Achat terrain
├── Étude de sol
├── Géomètre + EDD
├── Notaire
├── Huissier
├── Diagnostics
├── Référé préventif
├── Taxe aménagement
├── Taxe archéologique
├── Taxe raccordement
├── PUP
├── Exonération taxes
└── Honoraires montage

RUBRIQUE 2 — VRD
├── ENEDIS
├── Orange (Télécom)
├── AEP (eau potable)
├── EU (eaux usées)
├── Pluvial
├── GRDF (gaz)
└── Transformateur

RUBRIQUE 3 — CONSTRUCTION
├── Travaux + VRD + Désamiantage
├── Provision imprévus
├── NF Habitat Cerqual / Label
├── BET Structure
├── BET Fluides
├── BET VRD
├── CT + Vérif + Attestation Handicap + DPE
├── SPS (CSPS)
├── MOE / OPC
├── Architecte
└── Honoraires groupe

RUBRIQUE 4 — FRAIS GÉNÉRAUX
├── Assurance
├── Frais et garanties financiers
├── Honoraires vente (libre + social)
├── Logiciels
├── Provision SAV / Juridique
├── Commissions vente
├── Frais divers
├── Divers notaires
└── Honoraires groupe

RUBRIQUE 5 — CHARGES DIRECTES
└── Publicité

RECETTES
├── Chiffre d'affaires HT (lots)
└── TMA (recettes acquéreurs)

INDICATEURS
├── Marge = Recettes - Charges
├── Honoraires groupe (séparés)
├── Marge + Honoraires
├── Taux de marge sur CA HT
└── Taux de marge avec honoraires sur CA TTC
```

**3 colonnes de comparaison par poste :**
- Étude fi bq (prévisionnel initial)
- Marchés signés (sans avenants)
- Bilan fi réel (avec avenants + facturé réel)
- Delta Étude/Signé
- Delta Étude/Bilan
- Commentaires

---

## Tables clés mises à jour (données réelles Flora + prototype Lovable)

**lots** *(enrichi)*
```sql
id, programme_id, référence, type (T2/T3/T4/T3D...),
bâtiment, étage, surface_habitable, surface_annexe,
orientation,
-- Prix et grilles
prix_acte_ttc,            -- prix figé à la réservation
statut (disponible/réservé/acté),
-- Prescripteur
prescripteur_id,
taux_commission_pct,      -- variable par lot (2.5% à 5.25%)
-- Financement acquéreur
type_financement (I/RP/investissement),
acquéreur_id,
sharepoint_folder_url
```

**lots_techniques** *(confirmé par prototype — distinction travaux / prestataires)*
```sql
id, programme_id,
code,                     -- ex: "01", "P01a", "P17b"
libelle,                  -- ex: "Gros œuvre", "Architecte"
type (travaux/prestataire/concessionnaire),
entreprise_id             -- fournisseur Intacct associé
```

**grilles_prix** *(nouveau — issu Flora : 14 versions sur 3 ans)*
```sql
id, lot_id, programme_id,
date_application,
prix_ttc,
créée_par, created_at
-- Historique complet de toutes les grilles
-- Le prix d'acte est figé à la date de réservation
```

**tma** *(enrichi — issu Flora + prototype)*
```sql
id, lot_id, acquéreur_id,
localisation,             -- ex: "SDB", "Cellier"
désignation,              -- description de la modification
prestation_id,            -- lot technique concerné
montant_facturé_entreprise_ht,
montant_facturé_entreprise_ttc,
montant_facturé_client_ht,
montant_facturé_client_ttc,
delta_marge_ttc,          -- calculé auto : client - entreprise
catégorie,                -- Oubli/Erreur | Modification | Prestations sup. | Aléas | Transfert + | Transfert - | Offres
badges,                   -- Social | Investisseur
observations,
statut (devis/validé/facturé/réglé),
marché_id,
documents_sharepoint_url
```

**ts** *(Travaux Supplémentaires — nouveau issu prototype)*
```sql
id, programme_id,
appartement,              -- référence logement concerné
nature_travaux,
lot_technique_id,
entreprise_id,
validation_client (bool),
devis_client_ht, devis_client_ttc,
facture_ent_ht, facture_ent_ttc,
catégorie,                -- même enum que TMA
motif,
avenant_num,
facture_num,
date_reglement
```

**marchés** *(enrichi)*
```sql
id, programme_id, fournisseur_id, prestation_id,
numéro_lot_technique,     -- ex: "01a - Architecte", "01 - Gros œuvre"
référence, intitulé,
type (forfait/bordereau),
montant_marché_ht,        -- montant initial
montant_total_ht,         -- marché + avenants (calculé)
taux_tva, montant_total_ttc,
date_signature, date_début, date_fin_prev,
retenue_garantie_pct,     -- 5% standard (confirmé Flora + prototype)
a_caution (bool),
montant_caution,
compte_prorata_pct,       -- 1.5% standard (confirmé prototype)
statut (en_cours/soldé/résilié),
-- Suivi calculés automatiquement
total_facturé_ht,         -- SUM(situations)
pct_avancement,           -- total_facturé / montant_total
reste_à_régler_ht,        -- montant_total - total_facturé
documents_sharepoint_url,
intacct_id, intacct_synced_at, intacct_error
```

**avenants** *(enrichi)*
```sql
id, marché_id, numéro,    -- jusqu'à 4 avenants observés sur Flora
motif, montant_ht,
tva,
date_signature, statut
```

**situations** *(confirmé prototype — jusqu'à N°26 sur Flora)*
```sql
id, marché_id, numéro,
montant_ht,
tva,
retenue_garantie_pct,     -- copié du marché à la création
compte_prorata_pct,       -- copié du marché à la création
date_facture,
date_paiement,            -- date effective du paiement
statut (émise/validée/payée/en_retard),
facture_id                -- lien vers la facture validée
```

**concessionnaire_factures** *(confirmé prototype)*
```sql
id, programme_id,
titulaire,                -- ENEDIS | Réseau de chaleur | AEP-EU-EP | Orange
type (devis/marché/acompte),
désignation,
montant_ht, tva, montant_ttc,
date_validation,
date_paiement,
delta_budget_ht           -- écart vs prévisionnel
```

**lots_commerciaux** *(confirmé prototype — vue commerciale du lot)*
```sql
id, programme_id,
numero_lot,               -- référence commerciale (A101, B203...)
type_logt,                -- T2, T3, T3D, T4...
grille_prix,              -- prix catalogue actuel TTC
prix_vente,               -- prix négocié TTC
difference,               -- calculé : prix_vente - grille_prix
statut (en_stock/réservé/acté/annulé),
tma_montant,              -- montant TMA facturé client TTC
remises_commerciales_ttc,
agence_nom,
agence_commission_ttc,
commentaires
```

**communication_depenses** *(confirmé prototype)*
```sql
id, programme_id,
rubrique (PUBLICITE/AGENCES/DIVERS),
objet,
montant,
tva_incluse (bool),
date
```

**postes_budgétaires** *(enrichi — nomenclature Flora)*
```sql
id, programme_id,
rubrique (terrain/vrd/construction/frais_generaux/charges_directes),
libellé,                  -- ex: "ACHAT TERRAIN", "ENEDIS", "MOE OPC"
prestation_id,            -- lien vers référentiel prestations
code_comptable_id,
-- Colonnes bilan
montant_etude_fi,         -- prévisionnel initial
montant_marché_signé,     -- marchés signés sans avenants
montant_bilan_réel,       -- facturé réel avec avenants
commentaire,
-- Calculés automatiquement
delta_etude_signe,        -- étude - signé
delta_etude_bilan         -- étude - réel
```

**commissions** *(enrichi — taux variables par lot)*
```sql
id, prescripteur_id, lot_id, acquéreur_id,
taux_pct,                 -- taux spécifique au lot (2.5% à 5.25%)
montant_ht, tva, montant_ttc,
type_pub,                 -- ECOPUB, MADN, COBRA, HABITEO, UNLATCH...
statut (calculée/bon_émis/facture_reçue/payée),
date_déclenchement, date_paiement,
intacct_id
```

**financements_programme** *(enrichi — issu Flora)*
```sql
id, programme_id,
type (fonds_propres/credit_promo/refinancement),
partenaire,               -- ex: "Homunity" (refinancement FP)
pourcentage_qp,           -- ex: 60% pour Envol
montant_total,
montant_récupéré,
banque, numéro_ligne, montant_autorisé,
taux_interet, date_obtention, date_fin,
statut (en_attente/actif/soldé)
```

---

## Acte d'Engagement — template (issu Modèle_AE_2022.docx)

Généré automatiquement à la création d'un marché. Champs auto-remplis :

```
En-tête     : département, commune, numéro dossier, adresse terrain
Projet      : nom programme, nb étages, nb logements, nb parkings, parcelle cadastrale
Titulaire   : depuis fiche fournisseur Intacct (nom, SIRET, adresse, représentant légal)
Prix        : montant HT, TVA 20%, TTC, montant en lettres (auto)
Règlement   : 45 jours fin de mois, 90%/5%/5% RG (standard Envol)
Délai       : dates démarrage et fin saisies dans le marché
Validité    : 90 jours (fixe)
Signatures  : Maître d'ouvrage (SAS ENVOL, 1729 Avenue de la Pompignane, Montpellier)
```

---

## Documents fournis — liste mise à jour

- [x] **Liste lots/prestations** → LISTES_LOTS_ENVOL.csv ✅
- [x] **Exemple de facturation programme** → Facturation_Mauguio_Flora.xlsx ✅
- [x] **Modèle Acte d'Engagement** → Modèle_AE_2022.docx ✅
- [x] **Prototype Lovable** → facturationenvol-main.zip ✅ (logique métier + données réelles ARBORÉA + LOU MARQUES)
- [ ] Étude financière / bilan prévisionnel complet (structure postes détaillée)
- [ ] Plan comptable Sage Intacct complet
- [ ] Workflow de validation factures actuel
- [ ] Liste des rôles utilisateurs et droits précis
- [ ] Seuils de validation (montants par niveau)
- [ ] Fiche client / acquéreur actuelle (Unlatch)
- [ ] Arborescence SharePoint actuelle (capture écran)

---

## Fonctionnalités commerciales

### Offres commerciales temporaires
- Créées par la Direction uniquement
- Prix réduit sur un lot pendant une période définie
- Expiration automatique à la date de fin
- Alerte direction 3 jours avant expiration
- Si lot réservé pendant l'offre → prix offre appliqué + étude fi mise à jour
- Historique complet et non modifiable

### Prescripteurs
- Fiche prescripteur (CGP, agent immo, courtier...)
- Commission calculée automatiquement à la réservation ou à l'acte
- Bon de commission généré en PDF
- Export vers Intacct (code comptable commissions)
- Suivi des commissions par programme et par prescripteur

### TMA (Travaux Modificatifs Acquéreurs)
- Demande acquéreur liée à un lot
- Chiffrage par l'entreprise concernée (marché existant)
- Devis TMA → acceptation acquéreur
- Si accepté → avenant prix lot + OS entreprise généré
- Impact étude fi : recettes + et dépenses +

### TS (Travaux Supplémentaires)
- Distinct des TMA : travaux décidés par le conducteur, non demandés par l'acquéreur
- Lié à un lot technique et une entreprise
- Avenant au marché concerné
- Suivi facturation entreprise vs devis client

### Associés
- Tour de table par programme (% participation)
- Appels de fonds associés avec suivi des règlements
- Comptes courants d'associés
- Répartition automatique de la marge selon %
- Documents : pacte d'associés, PV AG → SharePoint

---

## Tableaux de bord et graphiques

### Dashboard direction (multi-programmes)
- Commercialisation par programme (barres)
- CA prévisionnel vs sécurisé (consolidé)
- Marge consolidée et par programme
- Factures en attente de validation
- Trésorerie prévisionnelle 12 mois (courbe)
- Alertes : documents manquants, paiements en retard, offres expirant

### Dashboard par programme (confirmé prototype)
- KPIs : Engagé Total HT / Payé à Date TTC / Reste à Payer TTC / Marge TMA TTC
- Avancement financier par lot (barres : Engagé HT vs Payé HT)
- Courbe S — avancement cumulé vs budget (lignes)
- Alertes paiements en retard (statut "Émise" sans date paiement)
- Synthèses : Taxes / Concessionnaires / Commercialisation (camembert statuts)

### Plan de trésorerie (confirmé prototype)
- Tableau glissant sur N mois (paramétrable : 6, 12, 18 mois)
- Recettes : lots actés (consolidés) + lots en stock/réservés (ligne par lot)
- Dépenses : Travaux / Prestataires / Concessionnaires / Taxes
- Solde mensuel et cumulé
- Export Excel

### Reste à Payer (confirmé prototype — 4 onglets)
- Travaux : reste HT par lot technique
- Retenues de Garantie : RG cumulée / caution / RG effective / restante
- Compte Prorata : base éligible × taux par lot (lots 01 et 02 exclus)
- Concessionnaires + Taxes

### Technologie graphiques
- **ApexCharts** intégré dans Vue.js (ou Recharts si composants partagés avec prototype)
- Export PDF du dashboard en un clic
- Données temps réel depuis PostgreSQL

---

## RGPD

### Principes
- Consentement tracé à la création de la fiche acquéreur
- Minimisation des données — pas de données inutiles
- Durée de conservation : 5 ans après acte ou abandon dossier
- Anonymisation (pas suppression) pour garder cohérence comptable
- Dossier SharePoint acquéreur : accès restreint ERP-DAF + ERP-Admin uniquement
- Logs d'accès sur toutes les fiches acquéreurs

### Droits acquéreurs
- Export de toutes ses données en PDF (depuis admin)
- Modification depuis l'ERP
- Anonymisation sur demande

### Registre des traitements (à documenter)
- Traitement : Gestion acquéreurs
- Finalité : Suivi acquisition immobilière
- Base légale : Contrat
- Durée : 5 ans après acte
- Destinataires : Notaire, banque acquéreur
- Transfert hors UE : Aucun

---

## Documents à fournir avant de coder

> ⚠️ Ces documents sont nécessaires pour que les prompts Cursor collent à votre réalité métier.
> Anonymisez les données sensibles avant envoi.

- [ ] **Étude financière / bilan prévisionnel** (Excel) — structure des postes
- [ ] **Template de budget par programme** — nomenclature des postes de dépenses
- [ ] **Exemple de situation de travaux** (Excel ou PDF)
- [ ] **Modèle d'Ordre de Service** actuel (Word ou PDF)
- [ ] **Exemple de fiche fournisseur** depuis Intacct (anonymisé)
- [ ] **Plan comptable** utilisé dans Sage Intacct
- [ ] **Codes analytiques** par type de programme
- [ ] **Workflow de validation** des factures actuel
- [ ] **Liste des rôles utilisateurs** et leurs droits précis
- [ ] **Seuils de validation** (montants par niveau)
- [ ] **Fiche client / acquéreur** actuelle (Unlatch ou Pegao)
- [ ] **Template suivi TMA** si existant
- [ ] **Arborescence SharePoint** actuelle (capture écran)
- [ ] **Liste types de marchés** (lots techniques utilisés)

---

## Numérique Responsable (RSE)

> Principe directeur : sobriété, localité, efficacité, mesure.

### Hébergement
- ✅ OVHcloud France (Roubaix / Gravelines) — hébergeur français, énergie bas carbone
- ✅ Dimensionnement serveur au plus juste — pas de surprovisionnement
- ✅ Services managés — pas de serveur inutile à faire tourner
- 🔲 Extinction automatique des environnements dev/staging la nuit (scheduler Laravel)

### Architecture
- ✅ Réutilisation de SharePoint existant — pas d'infra GED supplémentaire
- ✅ Cache Redis agressif — réduction des requêtes base de données
- ✅ Stack épurée — pas de logiciels tiers inutiles
- 🔲 Compression images et assets — réduction bande passante
- 🔲 Optimisation requêtes SQL dès le départ — moins de charge serveur

### IA responsable
- Appels API Claude uniquement quand nécessaire — pas d'appels automatiques inutiles
- Prompts courts et ciblés — moins de tokens = moins d'énergie
- Aucune donnée personnelle dans les prompts envoyés à l'API

### Outils de développement
- ✅ Cursor — tourne en local, pas de cloud permanent
- ✅ GitHub — versioning, compense ses émissions carbone
- ✅ OVHcloud — hébergeur français engagé

### Mesure (à implémenter)
- Dashboard RSE numérique interne :
  - Consommation estimée OVHcloud
  - Nombre d'appels API Claude / mois
  - Taux de cache Redis (requêtes évitées)
  - Uptime et efficacité serveur

---

## Intégration Signature Électronique — Yousign

- **Solution retenue** : Yousign (🇫🇷 français, conforme eIDAS, RGPD)
- **Usage** : signature des contrats de réservation VEFA par les acquéreurs
- **Déclenchement** : automatique à la création de la réservation

### Workflow

```
Réservation créée dans l'ERP
        ↓
Génération automatique du contrat VEFA (PDF)
        ↓
Envoi via API Yousign → email à l'acquéreur
        ↓
Acquéreur signe en ligne (mobile ou desktop)
        ↓
Webhook Yousign → ERP mis à jour automatiquement
        ↓
Document signé stocké sur SharePoint
```

### Points clés
- API REST bien documentée
- Webhooks pour mise à jour statut en temps réel
- Document signé archivé automatiquement sur SharePoint via Graph API

---

## Intégration AR24 — Purge délai de rétractation SRU

- **Solution retenue** : AR24 (Lettre Recommandée Électronique à valeur légale)
- **Alternative possible** : Maileva (La Poste) — valeur légale incontestable
- **Usage** : notification SRU acquéreur → déclenche le délai de rétractation 10 jours

### Workflow

```
Contrat VEFA signé (Yousign)
        ↓
ERP génère la lettre de notification SRU
        ↓
Envoi via API AR24 (LRE avec accusé de réception)
        ↓
Webhook AR24 → statut mis à jour dans l'ERP
        ↓
Délai 10 jours déclenché automatiquement
        ↓
Alerte automatique à J+10 → lot passé en "vendu confirmé"
```

### Points clés
- Accusé de réception horodaté = preuve légale
- Comptage automatique des 10 jours dans l'ERP
- Alerte direction si délai dépassé sans confirmation

---

## GED — Gestion documentaire via SharePoint

### Principe
- Documents stockés **uniquement sur SharePoint** — pas de doublon
- L'ERP pointe vers les fichiers via leur URL SharePoint
- Upload toujours via l'ERP → classement automatique dans le bon dossier SharePoint

### Arborescence SharePoint (créée automatiquement à la création d'un programme)
```
SharePoint / Sites / ERP-Immo / Programmes
└── PRG-2024-001 — Résidence Les Pins
    ├── 01-Foncier (Actes, Promesses, Études)
    ├── 02-Administratif (PC, DO, DAACT, Assurances)
    ├── 03-Marchés
    │   └── MCH-001 — Gros œuvre
    │       ├── Marché signé
    │       └── Factures
    ├── 04-Finance
    └── 05-Livraison
```

### Types de documents (classification à l'upload)
Acte d'achat, Promesse de vente, Permis de construire, DO, DAACT, Assurance DO, Marché signé, Avenant, Facture, Situation de travaux, PV de réception, Autre

### Checklist documentaire par programme
```
✅ Acte d'achat          12/03/2024   [Ouvrir]
✅ Permis de construire  05/06/2024   [Ouvrir]
⏳ DO                    manquant     [Uploader]
⏳ Assurance DO          manquant     [Uploader]
```

### Synchro depuis SharePoint
- Toutes les 10 minutes via Graph API
- Fichiers uploadés directement dans SharePoint apparaissent dans l'ERP
- Classement automatique basé sur la position dans l'arborescence

---

## Gestion des droits — Groupes de sécurité M365

- **Identité et appartenance aux groupes** : Microsoft Entra ID (source de vérité)
- Groupes lus via Microsoft Graph API à la connexion → mis en cache Redis
- **Droits fins par fonctionnalité** : système ERP `features_permissions` au-dessus des groupes M365 (voir section "Système de droits par fonctionnalité")

### Groupes M365 à créer
```
ERP-Admin        → accès total, administration
ERP-DAF          → finance, bilan, validation factures niveau 2
ERP-Conducteur   → marchés, factures, chantier, validation niveau 1
ERP-Direction    → lecture globale + validation finale (si > seuil)
ERP-Lecture      → tableaux de bord uniquement
```

---

## Intégration Sage Intacct

- **Type d'API** : XML over HTTPS (pas REST classique)
- **Fournisseurs** : viennent exclusivement de Sage Intacct — aucune création dans l'ERP
- **Synchronisation** : par lot (jobs Laravel + Redis)

### Jobs de synchronisation

| Job | Sens | Fréquence |
|---|---|---|
| SyncFournisseursDepuisIntacct | Intacct → ERP | 1x/nuit à 2h + bouton admin |
| SyncProgrammesVersIntacct | ERP → Intacct | 1x/nuit à 3h |
| EnvoyerFacturesValidees | ERP → Intacct | Toutes les 2h |
| RecupererPaiementsIntacct | Intacct → ERP | Toutes les 2h |

### Bouton admin "Synchroniser maintenant"
- Disponible pour chaque job sur la page admin
- Affiche le résultat immédiatement ("247 fournisseurs importés")

### Champs de synchro
```sql
intacct_id, intacct_synced_at, intacct_error
```

### Gestion des erreurs
- 3 tentatives avec backoff exponentiel (1min, 5min, 15min)
- Dashboard monitoring : statut, erreurs, dernière exécution

### IA sur les factures
- Extraction automatique via API Claude : numéro, montant HT, TVA, TTC, date, fournisseur, référence marché
- Détection d'anomalies : facture > marché, doublon, TVA incorrecte

---

## Workflow de validation des factures

### Étapes complètes

```
Facture reçue (upload ERP ou import Intacct)
        ↓
Détection automatique du type → assignation validateur NV1
        ↓
NV1 — Conducteur / Commercial / Juriste
(selon type de facture, paramétrable en admin)
        ↓
NV2 — PDG (toutes les factures sans exception)
        ↓
Mise en paiement — Comptable
(prépare dans Intacct → export)
        ↓
Exécution virement — DAF
(dans la suite bancaire, hors ERP)
        ↓
Confirmation paiement effectif
(Intacct → ERP automatique)
        ↓
Bilan mis à jour + notification
```

### Validateur NV1 selon type de facture (paramétrable admin)

| Type de facture | Validateur NV1 |
|---|---|
| Situation de travaux | Conducteur de travaux |
| Honoraires MOE / BET / OPC | Conducteur de travaux |
| Frais commerciaux / prescripteurs | Responsable commercial |
| Frais juridiques / notaire | Juriste |
| Autres | Responsable programme |

### Notifications automatiques

| Événement | Notifié |
|---|---|
| Facture reçue | Validateur NV1 concerné |
| NV1 validé | PDG |
| PDG validé | Comptable |
| Mise en paiement Intacct | DAF (+ lien suite bancaire) |
| Paiement effectif confirmé | Conducteur + Responsable financier |

### Gestion des délais et relances
- NV1 non traité après 48h → relance automatique
- NV1 non traité après 72h → alerte Responsable programme
- PDG non traité après 48h → relance automatique

### Tables workflow
```sql
-- types_factures
id, libellé, validateur_nv1_role, code_comptable_défaut

-- workflow_validations
id, facture_id,
niveau (nv1/nv2/mise_paiement/paiement_effectif),
validateur_id, statut (en_attente/validé/rejeté/relancé),
date_assignation, date_validation,
commentaire, relances_count
```

### Tableau de bord comptable
```
💳 PAIEMENTS EN COURS
─────────────────────────────────────
À préparer (PDG validé)     : 4 factures   89 200€
Préparés (en attente DAF)   : 3 factures  125 400€
Exécutés ce mois            : 12 factures 380 000€
```

---

## Responsables par programme

### Rôles définis par programme
```
👤 Responsable programme      → pilote global
👤 Responsable commercial     → ventes, prescripteurs, offres
👤 Responsable suivi client   → acquéreurs, TMA, livraison
👤 Conducteur de travaux      → marchés, factures, chantier
👤 Responsable financier      → bilan, validation financière
```

Un utilisateur peut avoir des rôles différents selon les programmes.

### Table programme_responsables
```sql
id, programme_id, user_id,
rôle (responsable_programme/commercial/
      suivi_client/conducteur_travaux/responsable_financier),
date_début, date_fin
```

---

## Code analytique programme

### Format retenu
```
PRG - {département} - {code_projet}
ex: PRG-75-LESPINS
    PRG-69-BELLECOUR
    PRG-13-CASTEL
```

### Règles
- Département : liste déroulante 01-976
- Code projet : lettres/chiffres uniquement, max 10 caractères, majuscules
- Unicité garantie — impossible de créer deux programmes avec le même code
- **Non modifiable après création** — cohérence avec Intacct
- Généré à la création du programme → envoyé vers Intacct automatiquement
- Comptables utilisent ce code dans Intacct pour toutes les factures du programme

---

## Factures — Import depuis Intacct

### Principe de filtrage
- L'ERP génère le code analytique PRG-xx-xxxx
- Les comptables l'utilisent dans Intacct sur les factures de programme
- L'ERP n'importe **que** les factures avec un code analytique PRG-xx-xxxx
- Les factures générales (sans code PRG) restent dans Intacct uniquement

### Job d'import
```
RecupererFacturesDepuisIntacct (toutes les 2h)
        ↓
Factures Intacct avec code analytique PRG-xx-xxxx
non encore importées
        ↓
Import dans l'ERP + association programme
        ↓
Association automatique au marché
(si un seul marché actif pour ce fournisseur)
ou proposition manuelle (si plusieurs marchés)
        ↓
Déclenchement workflow validation NV1
```

### Champs supplémentaires sur factures
```sql
source (erp_upload/intacct_import/facturx_intacct),
intacct_bill_id,
code_analytique,
association_auto (booléen)
```

---

## Factur-X

- Obligatoire progressivement : réception 2026, émission 2027-2028
- Sage Intacct gère la plateforme d'agrément → l'ERP ne gère pas la transmission
- **Réception** : factures Factur-X reçues via Intacct → données XML extraites automatiquement → pas d'OCR nécessaire
- **Émission** : appels de fonds générés dans l'ERP → envoyés vers Intacct → Intacct transmet à la plateforme
- Bibliothèque PHP : `atgp/facturx` intégrée dans Laravel

---

## MVP — Sprints prioritaires

### Sprint 1 — Fondations (2-3 jours)
- Auth SSO Microsoft + groupes de sécurité M365
- Gestion des rôles globaux (Admin / DAF / Conducteur / Direction / Lecture)
- Structure de base : sociétés, programmes + code analytique PRG-xx-xxxx
- Responsables par programme (conducteur, commercial, juriste, financier...)
- Synchro fournisseurs Intacct (prérequis pour les marchés)
- Bouton admin "Synchroniser fournisseurs maintenant"
- Création automatique arborescence SharePoint à la création d'un programme

### Sprint 2 — Bilan financier prévisionnel (3-4 jours)
- Création budget par programme (postes de dépenses + codes comptables)
- Import depuis fichiers Excel existants
- Gestion comptes bancaires par programme (fonds propres / ligne de crédit)
- Tableau de bord financier en temps réel
- Mise à jour automatique quand marchés / factures ajoutés

### Sprint 3 — Marchés & Suivi chantier (2-3 jours)
- Création marché depuis devis fournisseur Intacct
- Génération automatique OS en PDF
- Avenants
- Situations de travaux (avec RG et compte prorata — logique validée dans le prototype)
- TS (Travaux Supplémentaires)
- Mise à jour automatique du bilan
- Upload marché signé → SharePoint (dossier automatique)
- Vue "Reste à Payer" (travaux + RG + compte prorata + concessionnaires + taxes)
- Plan de trésorerie glissant

### Sprint 4 — Factures (3-4 jours)
- Upload facture PDF depuis l'ERP + OCR via Claude API
- Import automatique depuis Intacct (filtre code analytique PRG)
- Association au marché (auto ou manuelle)
- Workflow complet : NV1 (selon type) → PDG → Comptable → DAF → Paiement
- Notifications à chaque étape + relances automatiques
- Stockage automatique dans SharePoint

### Sprint 5 — Intacct + Paiement (2 jours)
- Export facture validée vers Sage Intacct
- Récupération paiement effectif depuis Intacct
- Mise à jour bilan en temps réel
- Notification DAF avec lien suite bancaire
- Notification confirmation paiement

---

## Décisions techniques prises

- ✅ Pas de GED custom → on s'appuie sur SharePoint existant
- ✅ Hébergement français → OVHcloud Public Cloud (France)
- ✅ Stack épurée → OVHcloud + GitHub + Cursor uniquement
- ✅ Auth SSO → Microsoft Entra ID
- ✅ Frontend → Vue.js 3 + Inertia.js
- ✅ IA → API Claude pour extraction factures PDF + détection anomalies
- ✅ Comptabilité → Sage Intacct synchronisé par lots (jobs Redis)
- ✅ Workflow factures → NV1 (selon type) → PDG → Comptable → DAF (suite bancaire)
- ✅ Code analytique → PRG-{département}-{code} généré par l'ERP, source de vérité
- ✅ Import factures Intacct → filtre code analytique PRG uniquement
- ✅ Factur-X → géré par Intacct + plateforme agrément existante
- ✅ Responsables par programme → rôles définis programme par programme
- ✅ Signature électronique → Yousign — phase 2
- ✅ Purge SRU → AR24 — phase 2
- ✅ Portabilité totale → stack open source, export natif, changement hébergeur en quelques heures
- ✅ Prototype Lovable → utilisé comme source de vérité pour la logique métier (calculs, modèle de données, UX) et fixtures de test (données ARBORÉA + LOU MARQUES)

---

## Environnement existant

- **Microsoft 365** : utilisé par toute l'équipe
- **SharePoint** : documents de programmes déjà organisés dessus
- **Pegao** : ERP actuel (2 400€/mois), utilisé pour tous les modules → à remplacer progressivement
- **Unlatch** : outil commercial actuel (CRM vente, signature eIDAS, recommandé électronique, portail acquéreur) → à remplacer en phase 2 par Yousign + AR24 intégrés dans l'ERP

---

## Avancement réel de l'ERP (mise à jour 01/06/2026)

> Le projet est largement plus avancé que la roadmap initiale prévoyait. L'ERP est en production sur https://envol.hectare.fr (OVH cluster113), accessible aux utilisateurs M365 du groupe Hectare.
> **Jalon 01/06/2026** : la **comptabilisation Intacct multi-fournisseur** (principal + sous-traitants, retenues, autoliquidation, pénalités) est désormais validée de bout en bout en prod — c'est la pièce maîtresse de la chaîne factures qui est maintenant opérationnelle.

### Stack réelle déployée
- **Backend** : Laravel 13 / PHP 8.3
- **Base de données** : MySQL OVH (pas PostgreSQL initialement prévu)
- **Frontend** : Vue 3 + Inertia + PrimeVue + Tailwind
- **Hébergement** : OVH mutualisé cluster113 (pas Public Cloud)
- **Queue** : `QUEUE_CONNECTION=sync` (pas de worker Redis)
- **Auth** : SSO M365 via Laravel Socialite + Microsoft Graph
- **Build front** : `npm run build` en local, `public/build/` poussé dans git avec `git add -f`
- **Vendor** : committé dans le repo
- **Composer** : pas global sur OVH, mais `composer.phar` disponible (`php composer.phar dump-autoload -o`)
- **Repo** : HECTAREG/erp-immo (GitHub privé)

### Groupes M365 utilisés
- `GS_ADMIN_ERP` — administration
- `GS_COMPTABILITE` — finance/factures
- `GS_DIRECTION_GENERALE` — direction
- `GS_DSI` — DSI
- `GS_ENVOL` — utilisateurs Envol

### Modules en production
- ✅ **Auth SSO M365** + groupes
- ✅ **Annuaire** (utilisateurs, fournisseurs Intacct)
- ✅ **Programmes** + code analytique PRG-{dpt}-{code}
- ✅ **Suivi Chantier** : marchés, avenants, situations (modèle issu prototype Lovable)
- ✅ **Bilan financier** prévisionnel
- ✅ **Factures** : workflow validation NV1/NV2/compta + dépôt + association marchés
- ✅ **Synchro Sage Intacct** : fournisseurs, factures
- ✅ **Devis multi-versions** par indice AAAAMMJJ
- ✅ **Appels d'Offres V1** : DCE.zip + Estimatif MOE.pdf via AoDocumentService
- ✅ **Module Capture Leads Meta Ads** (livré mai 2026 — voir section dédiée)
- ✅ **Système de droits par fonctionnalité** (livré 24 mai 2026 — features_permissions + UI /admin/droits)
- ✅ **Dashboard widgets personnalisables** (livré 24 mai 2026 — 10 widgets, drag & drop, layout par user)
- ✅ **Refonte page /admin** (livré 24 mai 2026 — onglets par catégorie, header avec toggle emails + synchro Intacct)
- ✅ **Refonte UX Service client** (livré 25 mai 2026, commit `44dde03` — TMA + AdF regroupés en page conteneur)
- ✅ **Liaison Stades VEFA ↔ Chronologie** (livré 25 mai 2026, commit `23b9252`)
- ✅ **Module Bibliothèque TMA** (livré 25 mai 2026, commit `641c2f7` — catalogue global + overrides programme) + hotfix `11cfac5`
- 🚧 **Switch programme depuis breadcrumb** (livré 25 mai 2026, commit `4284193` — **BUG Ziggy en prod, fix en cours**)
- ✅ **Refonte Factures en sous-onglet de Suivi Chantier** (livré 25 mai 2026, commit `6981623`)
- ✅ **Tooling Meta — replay + health check** (livré 26 mai 2026, commits `61d1b0e`, `2d753de`, `cb14a70`+) — 27 leads rejoués sur l'incident 23-26/05
- 🚧 **Refonte comptabilisation Intacct** (livré 26 mai 2026 — `ExtBillCreate` + SDK custom + endpoint debug — **bug résiduel `UPDATE_REFERENCE_NUMBER`**)
- ✅ **Logique métier factures DÉFINITIVE** (validée 29 mai 2026 — reconstitution TTC brut sans pénalités, voir section dédiée)
- ✅ **Multi-fournisseur sur factures** (livré 29 mai 2026 — N envois APBILL séparés)
- ✅ **Sous-traitants par marché** (livré 29 mai 2026 — table `marche_sous_traitants` + onglet fiche marché)
- ✅ **Retenues Finitions / OPC** (livré 29 mai 2026 — comptes `401400` / `401500`)
- ✅ **Module Marketing META** (livré 29 mai 2026 — dashboard + KPIs + permissions + groupe `GS_COMMUNICATION`)
- ✅ **Picto Permissions feu tricolore** (livré 29 mai 2026 — debug visuel pour `GS_DSI` / `GS_ADMIN_ERP`)
- ✅ **Page /admin/categories-frais-generaux** (livré 29 mai 2026 — accessible `GS_COMPTABILITE`)
- 🚧 **Module Appels de Fonds VEFA** (Sprints 1-6 déployés en prod 23/05/2026 — pas encore testés en prod, logo/pattern PNG à uploader)

### Modules à venir (roadmap restante)
- 🔲 Module CIE (cahier des charges prêt)
- 🔲 Module Juridique/Contentieux
- 🔲 Plan trésorerie
- 🔲 ARINVOICE Intacct (lié appels de fonds)
- 🔲 Prescripteurs + commissions
- 🔲 Portail acquéreur extranet (branding AVRA/MIRA/ALYA/NOOS)
- 🔲 Multi-tenant Meta leads (4 sociétés HECTARE/Envol/Gemme/Les Balcons de la Cité)

---

## Module Capture Leads Meta Ads (livré mai 2026)

### Architecture
```
Meta (formulaire Lead Ads)
  → Webhook POST /api/webhooks/meta/leadgen
  → MetaWebhookController (vérif HMAC avec META_APP_SECRET)
  → Job ProcessMetaLead (idempotence sur meta_lead_id)
  → MetaGraphService::getLead() + getCampaign() via Marketing API
  → MetaLeadIngestionService (résolution programme + commercial)
    1. Lookup meta_campaign_id dans meta_campaign_codes (clé principale)
    2. Fallback regex code 3 chiffres /^(\d{3})\s*-/
    3. Si commercial null → LEADS_FALLBACK_COMMERCIAL_USER_ID
  → Persistance leads_marketing (BDD)
  → Job CreateLeadSharePointItem (3 retries, backoff 60/300/900s)
  → Job NotifyCommercialTeams (email HTML enrichi via Graph Mail.Send applicatif)
```

### Configuration Meta côté Hectare (app GESTION_PROSPECT)
- App ID : `2827608434238325`
- Page abonnée : Hectare-Aménageur (`570810992938193`)
- Webhook v22, événement `leadgen`
- Permissions accordées : ads_read, ads_management, leads_retrieval, pages_show_list, pages_read_engagement, pages_manage_metadata, pages_manage_ads, business_management
- App publiée (mode dev → prod), URL confidentialité renseignée

### Variables .env clés
```
META_APP_ID=2827608434238325
META_APP_SECRET=...
META_PAGE_ACCESS_TOKEN=...    # Token Page Hectare-Aménageur
META_PAGE_ID=570810992938193
META_AD_ACCOUNT_ID=act_1193405381139978
META_WEBHOOK_VERIFY_TOKEN=...
META_GRAPH_API_VERSION=v22.0
META_VERIFY_SSL=true           # false en local MAMP
LEADS_FALLBACK_COMMERCIAL_USER_ID=1
LEADS_FALLBACK_EMAIL=aurelie.portales@hectare.fr
LEADS_SHAREPOINT_SITE_ID=b8893f4d-eec8-4f58-8ee1-3a10b6295ac3
LEADS_SHAREPOINT_LIST_ID=5a66d8c2-e4d6-419c-ae2a-38deaf25b34f
AZURE_MAIL_EXPEDITEUR=hectarion@hectaregroupe.fr
```

### SharePoint liste PROSPECTS-FACEBOOK
Site CENTRE-DE-RESSOURCES. Colonnes créées par import CSV → noms internes génériques `field_1` à `field_11`. Mapping centralisé dans `config/leads_marketing.php` :

| Field | Type SP | Contenu |
|---|---|---|
| Title | Texte | "Prénom Nom" |
| field_1 | Texte | Email |
| field_2 | Texte | Téléphone |
| field_3 | Texte | Programme |
| field_4 | Texte | Code campagne |
| field_5 | Texte | Nom campagne |
| field_6 | Texte | Commercial Référent ("Nom (email)") |
| field_7 | **Date/Heure** | Date de réception (format ISO Europe/Paris) |
| field_8 | **Choix** | Statut (Nouveau/Contacté/Qualifié/RDV/Réservation/Perdu) |
| field_9 | **Plusieurs lignes** | Champs supplémentaires Meta (texte multiligne) |
| field_10 | Texte | URL ERP |
| field_11 | Texte | ID Lead Meta |

**Pièges identifiés et résolus** :
- field_9 doit être "Plusieurs lignes de texte" pour accepter retours ligne (sinon 400 Invalid request)
- field_6 utilise parenthèses `Nom (email)` pas chevrons `<>` (sinon SP interprète comme HTML)
- field_10 reste texte simple (pas Hyperlink — pose problème)
- Clés JSON dans field_9 normalisées : `\W → _`, minuscules, max 80 char
- Valeurs sanitizées : `strip_tags()` + retrait caractères de contrôle
- Apostrophes typographiques `’` causent souci sur champ Texte 1 ligne → solution : multiligne

### UI Admin livrée
- `/admin/leads-marketing` — Liste filtres recherche (clic ligne → détail)
- `/admin/leads-marketing/{id}` — Détail, statut éditable inline, commercial éditable inline, bouton "Relancer SharePoint" et "Renvoyer notification" TOUJOURS visibles
- `/admin/marketing/codes-campagnes` — CRUD inline commercial, import Meta depuis campagnes actives
- `/admin/marketing/codes-campagnes/import-meta` — Tableau campagnes actives Meta, code optionnel, commercial optionnel, bulk create
- `/admin/sp/test-fields` — Debug GS_DSI : séquence cumulative avec payload réel du lead 2
- Cartes admin : "Prospects marketing" et "Codes campagnes marketing"
- **Lien "Prospects" retiré du menu nav principal** (accès via /admin uniquement)

### M365CommercialUserResolver
Provisioning automatique d'utilisateurs M365 dans l'ERP même s'ils ne se sont jamais connectés :
- Recherche par `azure_id` ou email
- Si absent : création `users` avec `name` (displayName Graph), `email`, `azure_id`, mot de passe aléatoire inutilisable, `email_verified_at` + `preapproved_at`
- Utilisé pour codes campagnes ET édition inline commercial fiche lead
- Migration `users.preapproved_at` à exécuter

### Notification email enrichie
- **TO** : commercial du lead (commercial_user.email)
- **CC fixe** : aurelie.portales@hectare.fr + hectarion@hectaregroupe.fr
- **Fallback** : si commercial null → TO = aurelie.portales@hectare.fr
- **Sujet** : "🔥 Nouveau prospect à contacter — {campagne} — {prenom} {nom}"
- **Contenu HTML** : ouverture incitative ("contacter dans les 5 minutes"), tableau coordonnées, infos formulaire Meta humanisées, CTA bouton "Voir tous les prospects sur SharePoint", footer sobre
- **Canal** : Microsoft Graph Mail.Send Application (HTTP 200/202/204 acceptés)
- **Mention** : le mail ne va JAMAIS au client, uniquement au commercial Hectare

### Configuration Azure Mail.Send
- Mail.Send doit être en **Application** (pas Déléguée) avec consentement admin accordé
- L'app peut alors envoyer depuis n'importe quelle boîte du tenant (hectarion, robin...) si pas d'Application Access Policy restrictive

### Helper emailsEnabled()
`app/Helpers/SettingHelper.php` définit `emailsEnabled()` qui lit le setting `emails_enabled`. Doit être listé dans `composer.json` → `autoload.files` puis `php composer.phar dump-autoload -o` pour être chargé. Sinon : `Call to undefined function emailsEnabled()`.

---

## Audit emails (état mai 2026)

### Politique : 2 envois ACTIFS, le reste DÉSACTIVÉ

**Actifs :**
1. **Leads marketing (Meta)** — NotifyCommercialTeams
   - TO : commercial_user.email | Fallback : `config('leads_marketing.fallback_email')` → `aurelie.portales@hectare.fr`
   - CC : aurelie + hectarion
   - Canal : Graph applicatif
2. **Demandes fournisseurs** — DemandeFournisseurController::envoyerEmailComptabilite
   - TO : `config('notifications.fournisseurs_email')` → `comptabilite@hectare.fr` (fixe)
   - Canal : Graph applicatif (nouveau)
   - Notification cloche GS_COMPTABILITE conservée

**Désactivés** (`Log::info('Email désactivé pour [...]')`) :
- Validations facture NV1/NV2/compta — `NotificationFactureService` (assignation validateur_id conservée)
- Relances factures — `NotificationFactureService::relancerValidateur` (plus d'incrément relances_count)
- Réservations acquéreur 4 alertes ODF/accord/SRU/documents — `AlerteService` (alertes cloche conservées)
- Devis TMA client — `TMAController` (statut "Envoyé" conservé)
- Alertes SharePoint AO/devis — Jobs `UploadDevisToSharePoint` / `UploadAoDocumentToSharePoint`

Documenté dans `docs/audit-emails.md`.

---

## Module Appels de Fonds VEFA (déployé prod 23/05/2026, en rodage)

### Spécifications validées avec DSI

**Stades d'avancement** :
- Modèle par défaut : 25% Fondations / 50% Élévation / 75% Hors d'eau / 95% Cloisons / 100% Livraison
- Paramétrables par programme (libellés et % modifiables)

**Génération** :
- Bouton manuel par stade depuis fiche programme
- Déclenche traitement batch tous acquéreurs ACTÉS du programme
- Pas d'envoi automatique au client en phase 1 (bouton manuel "Envoyer à l'acquéreur" — rodage)

**Document** :
- PDF généré + dépôt SharePoint dossier acquéreur
- Format = appel de fonds = facture client (un seul document pour les 2 usages)
- Template charte Envol : pattern hexagonal haut, logo, filet rose séparateur, bandeau rose bas, couleurs `#ebbab8` + `#3c3c3b`

**Intacct** :
- Création AR Invoice (facture CLIENT) via Sage API existante
- Acquéreurs créés dans Intacct au moment de la 1ère AdF (lazy, sans doublon)
- Code analytique programme PRG-{dpt}-{code} reporté
- Référence externe (n° AdF) pour rapprochement automatique
- TVA 20% (bâtiment neuf libre)

**Numérotation** : `AF-{code_societe}-{annee}-{seq}` (ex: `AF-023-2026-014`)
- Code société = 3 chiffres défini par programme (ex: E023 → 023)

**Échéance** : point de départ = date de génération de l'appel de fonds

**Suivi paiements / relances** :
- Dashboard "AdF en retard" sans automatisation
- Bouton manuel "Relancer" génère email
- Pas d'automation pour éviter relances erronées (Intacct peut être en retard sur les encaissements)

**TMA** : facture SÉPARÉE, PAS dans appels de fonds

### Coordonnées société par programme (à saisir)
Ajoutées à la table `programmes` :
- raison_sociale (ex: ENVOL SAS)
- adresse complète
- siret, rcs, capital_social, tva_intracom
- iban, bic, banque, référence compte

### Logo Envol officiel (charte 2019)
- Couleurs : Rose Envol `#ebbab8` + Gris foncé `#3c3c3b`
- Typo : ITC Avant Garde Gothic Extra Light (titres) + Book (corps) — fallback web Montserrat
- Logo officiel : `/mnt/user-data/uploads/LOGO_ENVOL.pdf` (à intégrer en PNG/PDF dans les générations)
- Pattern hexagonal : `/mnt/user-data/uploads/A4___Pattern_Envol.pdf`
- Adresse société Envol : 1729 Avenue de la Pompignane, 34000 MONTPELLIER, 04 67 79 84 83, www.envol.fr

### Mockup validé
Design HTML validé en session — éléments visuels :
- Pattern hexagonal en haut (opacité 50%)
- Logo centré (placeholder — Cursor utilisera vrai PDF)
- Filet rose séparateur
- 2 colonnes : Programme (nom commercial ERP) | Acquéreur
- Titre "APPEL DE FONDS" en typo fine + n° AF-023-2026-014 + date
- 3 encarts rose pâle : Lot / Référence dossier / Échéance
- Barre d'avancement 5 stades (% atteints en rose, à venir en rose pâle)
- Mention légale R261-14 CCH
- Tableau financier (prix acte / cumul appelé / présent appel / cumul après / reste)
- Encart rose IBAN/BIC/Référence
- Pied de page sobre + bandeau rose `#ebbab8` en bas

### Sprints 1-6 — déployés en prod le 23 mai 2026

> Module déployé en production, **pas encore testé en conditions réelles**. Logo + pattern PNG à uploader pour la génération propre des PDF.

**Sprint 1 — Coordonnées société & bancaires**
- Migration `programmes` enrichie : `code_societe` (3 chiffres), `raison_sociale`, `adresse_societe`, `siret`, `rcs`, `capital_social`, `tva_intracom`, `iban`, `bic`
- UI : onglet "Coordonnées société & bancaires" dans la fiche programme
- Droits : `GS_ADMIN_ERP` + `GS_DIRECTION_GENERALE`
- Validation IBAN : 24-34 caractères alphanumériques

**Sprint 2 — Stades d'avancement VEFA**
- Table `stades_avancement` (par programme)
- Seeder `StadesAvancementDefaultSeeder` : 25 / 50 / 75 / 95 / 100
- UI `/programmes/{id}/stades-avancement` : drag & drop, % modifiables
- Validation somme = 100%
- Droits : `GS_ADMIN_ERP` ou `responsable_programme`

**Sprint 3 — Génération AdF**
- Table `appels_de_fonds` (distincte de l'ancienne `appels_fonds` liée à la chronologie)
- Service `GenerationAppelFondService::genererBatch()`
- `NumeroAppelFondGenerator` avec verrou DB — format `AF-{code}-{annee}-{seq}`
- Échéance configurable : `APPELS_FONDS_NB_JOURS_ECHEANCE` (défaut 30)
- Idempotence sur le couple lot / stade

**Sprint 4 — PDF charte Envol**
- Service `GenerationAppelFondPDFService` + template `appel-de-fonds.blade.php`
- Stockage : `storage/app/appels-de-fonds/{programme_id}/{numero}.pdf`
- Logo + pattern à uploader dans `resources/assets/`

**Sprint 5 — Intacct (workflow révisé)**
- Numérotation à deux niveaux :
  - ERP génère `numero_interne` (`AF-XXX`)
  - Intacct attribue `numero_facture` (`FAC-XXX`)
- Workflow asynchrone : création AdF en `brouillon` → Job `CreateIntacctClientInvoice` → `en_attente_intacct` → `emis` (avec `FAC-XXX`)
- Lookup / création client Intacct par email + nom (lazy, sans doublon)
- Compte produit configurable : `APPELS_FONDS_COMPTE_PRODUIT` (défaut 706000)
- `CLASSID` = `code_analytique` du programme
- 3 retries 60 / 300 / 900s, retour à `brouillon` si échec
- Dashboard `/admin/appels-de-fonds/erreurs-intacct`

**Sprint 6 — UI + désactivation envoi acquéreur**
- UI `/programmes/{id}/appels-de-fonds` : génération batch + suivi
- Polling 10s tant que des AdF sont en `brouillon` ou `en_attente_intacct`
- Modale de confirmation de génération avec preview des lots concernés
- Bouton "Envoyer à l'acquéreur" **désactivé** via config `APPELS_FONDS_ENVOI_ACQUEREUR=false` en phase rodage
- Backend protégé : retour 403 si appelé en API avec config à `false`

### Décisions complémentaires AdF
- **PDF** : génération uniquement après obtention du `numero_facture` Intacct
- **Statuts AdF révisés** : `brouillon`, `en_attente_intacct`, `emis`, `envoye_acquereur`, `paye`, `annule`
- **Acompte à l'acte** : à valider avec compta (montant 1500€ ? déduction du 1er AdF ?) — fonctionnalité à ajouter ultérieurement

### Reste à faire post-déploiement
- Tests en prod du module Appels de Fonds (Sprints 1-6)
- Upload du logo + pattern PNG dans `resources/assets/`
- Validation acompte à l'acte avec compta

---

## Décisions techniques additionnelles (mai 2026)

- ✅ Module Meta leads : architecture webhook + jobs + idempotence sur meta_lead_id
- ✅ Notification email leads : Graph Application avec CC fixe + contenu HTML enrichi
- ✅ Politique emails : 2 cas actifs (leads + fournisseurs), le reste désactivé
- ✅ Provisioning auto users M365 (preapproved) — pas besoin de login préalable
- ✅ Édition inline commercial sur fiche lead et codes campagnes
- ✅ SharePoint date/heure : ISO Europe/Paris (pas UTC, sinon décalage -2h sur SP)
- ✅ Appels de fonds : appel = facture client (un seul document pour Intacct + acquéreur)
- ✅ Numérotation AdF : AF-{code_societe}-{annee}-{seq} (code 3 chiffres défini par programme)
- ✅ Stades VEFA : 25/50/75/95/100 par défaut, paramétrable par programme
- ✅ Numérotation AdF à deux niveaux : `numero_interne` ERP (AF-XXX) + `numero_facture` Intacct (FAC-XXX)
- ✅ Workflow AdF asynchrone : brouillon → en_attente_intacct → emis → envoye_acquereur → paye
- ✅ PDF AdF généré uniquement après obtention du numéro Intacct
- ✅ Envoi acquéreur désactivé en phase rodage (config `APPELS_FONDS_ENVOI_ACQUEREUR=false`)
- ✅ Acompte à l'acte : reporté à plus tard (à valider avec compta)
- ✅ Système de droits par fonctionnalité : couche fine au-dessus des groupes M365 (pas de refacto immédiate du code existant, migration progressive)
- ✅ Dashboard : layout personnalisable par utilisateur, widgets filtrés par permissions, cache file 5 min par widget
- ✅ Service client : page conteneur regroupant TMA + AdF, sous-onglets avec deep-link `#tma` / `#appels-fonds`, redirection 301 pour les anciennes URLs
- ✅ Stades VEFA ↔ Chronologie : FK informationnelle uniquement, la génération AdF reste pilotée par `stade_avancement_id`
- ✅ Bibliothèque TMA : catalogue global + overrides par programme (% signés avant/après coulage) + lignes snapshotées par TMA
- ✅ Coefficient prix client TMA par défaut : ×1.8 (= 1.2 × 1.5), modifiable manuellement
- ✅ `tmas.marche_id` devient nullable et non utilisé pour le pricing (multi-entreprises par ligne reporté)
- ✅ Workflow facturation TMA : même pattern asynchrone que les AdF VEFA (TMA-XXX ERP + FAC-XXX Sage)
- ✅ `public/build` **retiré du `.gitignore`** : le build front est désormais committé (cohérent avec `vendor/` committé), évite les déploiements incohérents
- ✅ **Ziggy non installé** : URLs directes obligatoires côté Vue, jamais `route()` (voir Conventions front en début de fichier)
- ✅ Switch programme : substitution regex du segment `/programmes/{id}/`, conserve query + hash, fallback dashboard programme
- ✅ Lots éligibles TMA : statuts `reserve` + `acte`
- ✅ Sous-onglets Suivi Chantier : navigation par hash (`#appels-offres`, `#marches`, `#factures`, `#cie`), compat ascendante `?tab=` via `history.replaceState`
- ✅ **Claude Code** adopté en complément de Cursor pour les sessions debug intensives (repo + shell direct, moins d'allers-retours)
- ✅ **Pattern artisan ↔ endpoint admin** : toute commande artisan qui appelle une API externe doit avoir un endpoint admin équivalent, car OVH bloque les sorties HTTPS depuis CLI/SSH
- ✅ Intacct : passage de `BillCreate` à **`ExtBillCreate`** (SDK natif), retrait des `setBaseCurrency` / `setTransactionCurrency` qui déclenchaient des erreurs
- ✅ Intacct : **SDK custom restauré dans `vendor/intacct`** (cohérent avec `vendor/` committé)
- ✅ Lecture APBILL : **fallback `query` + `readByQuery`** plus robuste que la lecture directe par ID
- ✅ Endpoint debug `/admin/intacct/test-bill` : envoi APBILL configurable champ par champ pour isoler les erreurs Intacct
- ✅ Health check Meta horaire + widget dashboard + bannière globale (détection précoce des coupures webhook)
- ✅ **Logique métier factures DÉFINITIVE (29/05)** : pénalités hors reconstitution TTC brut, déduites uniquement du `401000` ; voir section dédiée
- ✅ **Multi-fournisseur sur factures (29/05)** : N envois APBILL séparés vers Intacct, retenues sur principal uniquement, autoliquidation TVA sous-traitants
- ✅ **Sous-traitants par marché (29/05)** : table `marche_sous_traitants` + onglet fiche marché
- ✅ Retenues Finitions (`401400`) et OPC (`401500`) configurables dans Admin > Comptabilité
- ✅ **Picto Permissions feu tricolore (29/05)** pour `GS_DSI` / `GS_ADMIN_ERP` (debug visuel des droits)
- ✅ `PermissionService` : `GS_DSI` wildcard total / `GS_ADMIN_ERP` tout sauf `admin.systeme` + `admin.maintenance`
- ✅ Page `/admin/categories-frais-generaux` accessible à `GS_COMPTABILITE`
- ✅ Nouveau groupe M365 : **`GS_COMMUNICATION`** (module Marketing META)
- ✅ **Stack effective** confirmée : Laravel 13, PHP 8.3, MySQL OVH cluster113 mutualisé (pas Public Cloud / PostgreSQL)

---

## Système de droits par fonctionnalité (livré 24 mai 2026)

> Couche fine de permissions au-dessus des groupes M365. Les groupes Entra ID restent la source d'identité ; les permissions ciblent la fonctionnalité (lecture / écriture par module).

### Architecture
- Tables : `features_permissions`, `feature_permission_groups`, `audit_log_permissions`
- Catalogue : ~80 permissions (lecture / écriture par fonctionnalité)
- Mapping groupes M365 ↔ permissions (table de jointure)
- `PermissionService` avec cache 5 min (résolution par user)
- Helper global `userCan('feature.action')` côté Blade
- Composable `useUserCan` côté Vue
- Middleware `feature.permission` pour protection de routes
- Audit log de toutes les modifications de permissions

### Catégories du catalogue
- Suivi chantier
- Factures
- Bilan
- Programmes
- Commercial
- Marketing
- Appels d'offres
- Documents
- Annuaire
- Intacct
- Administration

### UI Admin `/admin/droits`
- Onglets par catégorie
- Multi-select des groupes M365 par permission
- Recherche dans le catalogue
- Filtre par groupe M365
- Audit trail des modifications

### Mapping par défaut
- `GS_DSI` : tout
- `GS_ADMIN_ERP` : quasi-tout
- `GS_DIRECTION_GENERALE`, `GS_COMPTABILITE`, `GS_ENVOL` : périmètres ciblés selon métier

### Stratégie de migration
- **Pas de refacto immédiate du code existant** : les contrôles `hasM365Group()` historiques continuent de fonctionner
- Migration progressive : les nouvelles fonctionnalités utilisent `userCan()` / `useUserCan` ; l'ancien code sera converti au fil de l'eau

---

## Dashboard widgets personnalisables (livré 24 mai 2026)

### Architecture
- Routes : `/` et `/dashboard` (même page)
- 10 widgets disponibles, filtrés à l'affichage selon les permissions de l'utilisateur
- Drag & drop via `grid-layout-plus`
- Mode édition + bouton "Ajouter un widget"
- Configuration persistée en BDD : table `user_dashboard_widgets`
- Layout par défaut selon profil M365 dominant (seeder `DashboardSeeder`)
- Cache file 5 min par widget (réduit la charge sur les requêtes coûteuses)

### Widgets disponibles
- `appels_de_fonds_en_retard`
- `factures_a_valider`
- `leads_marketing_recents`
- `synthese_chantier`
- `tresorerie_consolidee`
- `synthese_commerciale`
- `demandes_fournisseurs`
- `synchros_intacct`
- `calendrier`
- `documents_recents`

### Bug fix notable
- **Boucle infinie résolue** via comparaison `layoutSignature` (avant : le watcher se déclenchait sur chaque mutation interne du layout par `grid-layout-plus`, provoquant un re-render infini)

---

## Refonte page /admin (livré 24 mai 2026)

### Structure par onglets (visibilité selon groupes M365)
| Onglet | Groupes M365 autorisés |
|---|---|
| Utilisateurs & Droits | `GS_DSI`, `GS_ADMIN_ERP` |
| Marketing | `GS_DSI`, `GS_ADMIN_ERP`, `GS_DIRECTION_GENERALE` |
| Comptabilité & Intacct | `GS_COMPTABILITE`, `GS_DSI`, `GS_ADMIN_ERP` |
| SharePoint & Documents | `GS_DSI`, `GS_ADMIN_ERP` |
| Système | `GS_DSI` uniquement |

### UI
- Cartes compactes (max 200px), hover discret
- Header avec toggle `emails_enabled` + indicateur dernière synchro Intacct

### Bug fix
- `AdminController` : import `use App\Http\Controllers\Admin\AdminController` manquant — corrigé

---

## Refonte UX "Service client" (livré 25 mai 2026, commit `44dde03`)

> L'ancienne page TMA devient une page conteneur **"Service client"** regroupant deux sous-onglets : **TMA** et **Appels de fonds**.

### Structure
- Page conteneur "Service client" avec deux sous-onglets internes
- Hash deep-link : `#tma` (défaut) et `#appels-fonds`
- Mode "un seul onglet" si l'utilisateur n'a qu'une des deux permissions

### Routes & redirections
- `/programmes/{id}/tmas` : page conteneur Service client
- `/programmes/{id}/appels-de-fonds` : **redirection 301** vers `/programmes/{id}/tmas#appels-fonds`
- L'onglet "Appels de fonds" est **retiré** de `/programmes/{id}/edit` (la page riche AdF migre dans le sous-onglet)

### Refacto Vue
- Composants extraits : `ProgrammeTmasPanel.vue` + `ProgrammeAppelsFondsPanel.vue` (remplacent les anciens panels par la version riche)
- `AppelsDeFonds/Index.vue` devenu un wrapper minimal du panel

### Permissions
- `tma.*` et `programmes.appels_de_fonds.*` re-catégorisées dans la catégorie **"Service client"**
- Middleware `EnsureFeaturePermission` étendu pour gérer le OR (accès si l'une des deux suffit)

### Libellés
- `AppShell`, `ProgrammeNav`, Dashboard programme : libellés de navigation mis à jour

---

## Liaison Stades VEFA ↔ Chronologie programme (livré 25 mai 2026, commit `23b9252`)

### Modèle
- FK `programme_etape_id` sur `stades_avancement`, **nullable + nullOnDelete**
- Plusieurs stades peuvent pointer vers la même étape de chronologie (cardinalité N → 1)
- Warning UI **non bloquant** en cas de doublon de mapping

### Compatibilité
- Stades existants restent non rattachés ; le mapping se fait manuellement par l'utilisateur
- Validation "somme des % = 100%" conservée

### Portée
- FK **purement informationnelle** : la génération AdF reste basée sur `stade_avancement_id`, pas sur l'étape de chronologie

---

## Module Bibliothèque TMA (livré 25 mai 2026, commit `641c2f7`)

### Modèle de données

| Table | Rôle |
|---|---|
| `prestations_tma` | Catalogue global : libellé, unité, prix entreprise HT avant/après coulage, code, actif, ordre |
| `prestations_tma_programme_overrides` | `pourcentage_ecart_avant_coulage` et `pourcentage_ecart_apres_coulage` **signés**, par programme/prestation (unique) |
| `tma_lignes` | N lignes par TMA avec snapshots (`libelle`, `unite`, `prix_unitaire_entreprise_ht`), quantité, totaux entreprise HT et client HT |

### Modifications sur `tmas`
- Nouvelle colonne `moment_coulage` (`avant` / `apres`)
- `facture_ent_ht` et `facture_client_ht` deviennent **calculés** depuis les lignes
- `marche_id` devient **nullable** et **n'est plus utilisé** pour le pricing (multi-entreprises par ligne reporté à un prochain sprint)

### Pricing
- Coefficient prix client par défaut : **×1.8** (= 1.2 × 1.5)
- Préremplit le formulaire, modifiable manuellement ligne par ligne

### UI
- Bouton **"Bibliothèque TMA"** dans l'onglet TMA → `/tma/bibliotheque` (page globale)
- Bouton **"Overrides programme"** dans l'onglet TMA → `/programmes/{programme}/tma/overrides`
- Refonte du formulaire TMA : sélection moment coulage + ajout de lignes via catalogue + calcul auto des totaux + marge affichée

### Permissions (catégorie "Service client")
| Permission | Lecture | Écriture |
|---|---|---|
| `tma.bibliotheque` | `GS_DIRECTION_GENERALE` | `GS_DSI`, `GS_ADMIN_ERP`, `GS_ENVOL` |
| `tma.overrides_programme` | — | `GS_DSI`, `GS_ADMIN_ERP`, `GS_ENVOL`, `GS_DIRECTION_GENERALE` |

- Seeder **idempotent** (`updateOrCreate` / `firstOrCreate`)

### Hotfix création TMA (commit `11cfac5`, 25/05/2026)
- **Lots éligibles** : règle métier confirmée — statuts `reserve` + `acte`
- **Empty state** lots : « Aucun logement réservé ou acté sur ce programme »
- **Select Marché** : format `référence — fournisseur.nom — poste_bilan_libelle`

---

## Switch programme depuis breadcrumb (livré 25 mai 2026, commit `4284193`)

> Sélecteur de programme directement dans le breadcrumb avec recherche, pour basculer rapidement entre programmes sans repasser par le dashboard.

### Endpoint
- `/api/dashboard/programmes-switcher`
- Service `ProgrammeAccessService` :
  - `switcherOptionsForUser()`
  - `accessibleProgrammeIds()`
  - `accessibleProgrammesForUser()`
- Cache user 5 min

### Comportement navigation
- Substitution **regex** du segment `/programmes/{old_id}/` par `/programmes/{new_id}/` dans `page.url`
- **Conserve** query string + hash (ex : reste sur `#factures` après switch)
- **Fallback** dashboard programme si l'URL courante ne contient pas `/programmes/{id}/`

### 🐛 Bug en cours (Ziggy)
- `AppShell.vue` utilise `route('api.dashboard.programmes-switcher')` mais **Ziggy n'est pas installé**
- `route()` retourne `undefined` → `axios.get(undefined)` → erreur « Impossible de charger les programmes »
- **Fix prévu** : remplacer par l'URL directe `/api/dashboard/programmes-switcher`
- **Audit à faire** : vérifier qu'aucun autre `route()` Ziggy n'a été introduit ailleurs dans la session

---

## Refonte Factures en sous-onglet de Suivi Chantier (livré 25 mai 2026, commit `6981623`)

### Structure
- Onglet **"Factures"** retiré de la nav programme principale (`AppShell`)
- Devient **sous-onglet de "Suivi Chantier"**
- Ordre final des sous-onglets : `Appels d'offres | Marchés | Factures | CIE`

### Navigation
- Sous-onglets convertis de `?tab=...` vers **hash** (`#appels-offres`, `#marches`, `#factures`, `#cie`)
- **Compat ascendante** : `?tab=xxx` → `#xxx` via `history.replaceState` au chargement
- Route `/programmes/{programme}/factures` **conservée** (wrapper minimal)

### Permissions
- Middleware `feature.permission:factures.lecture` appliqué
- Filtre d'affichage du sous-onglet selon permission

### Refacto Vue
- Composant extrait : `ProgrammeFacturesPanel.vue`

---

## Tooling Meta — replay + health check (livré 26 mai 2026)

> Outillage de résilience pour le module Capture Leads Meta Ads, suite à un incident webhook (token expiré + désabonnement page).

### Commande de rattrapage
- Artisan : `php artisan meta:replay-leads` (commit `61d1b0e`)
- Endpoint admin équivalent : `POST /admin/meta/replay-leads` (commit `2d753de`)
- **Endpoint obligatoire** car le CLI OVH ne peut pas sortir en HTTPS vers Graph (voir Contraintes infra OVH)
- Usage réel : **27 leads manqués rejoués** sur l'incident 23-26/05/2026

### Health check Meta
- Job horaire qui vérifie : validité du token + état de l'abonnement webhook sur la page (id `570810992938193`)
- Page admin `/admin/meta/health` : état détaillé + bouton de réabonnement
- **Widget dashboard** dédié (état OK / KO + dernier check)
- **Bannière globale** affichée en cas de KO pour détection précoce
- Commits : `cb14a70` + suivants

---

## Refonte comptabilisation Intacct (livré 26 mai 2026, sprint debug)

> Refonte en profondeur du workflow `APBILL` pour fiabiliser la création de factures fournisseur dans Sage Intacct.

### Changements majeurs

| Avant | Après | Commit |
|---|---|---|
| Endpoint `BillCreate` (legacy) | `ExtBillCreate` (SDK natif) | `3176e6a`, `4224852` |
| `setBaseCurrency` / `setTransactionCurrency` envoyés | Retrait (déclenchait des erreurs) | `129c884` |
| Lecture APBILL par ID direct | Fallback `query` + `readByQuery` | `49d49eb` |
| SDK Intacct depuis Composer | SDK custom **restauré dans `vendor/intacct`** | (lié à `cb14a70`) |

### Endpoint debug `/admin/intacct/test-bill` (commits `c799dde`, `70e7bb4`, `30a452d`)
- Page d'investigation **pas-à-pas** pour les envois APBILL
- Champs configurables un à un : vendor, dates, montants, pièces jointes, TVA
- Bug HTTP corrigé en route : `->post` → `->send` (commit `30a452d`)
- Utilisé pour isoler les champs déclencheurs d'erreurs côté Intacct

### 🐛 Bug résiduel en cours d'investigation
- **Smart event tenant** `UPDATE_REFERENCE_NUMBER` plante systématiquement **après** un `CREATE APBILL` réussi.
- Côté code ERP : `ExtBillCreate` + custom field `NUMROTATION_INTERNE` sont alignés sur le script de référence qui fonctionne en prod chez Hectare.
- **Prochaine piste** : ajouter `taxentries` (`BillLineTaxEntriesCreate`) par ligne avec `detailid` `TR2575FRMTPB2BFRMTGDSGLSTDRT` (TVA 20%) dans le test-bill pour valider le format complet.

---

## Référentiel TVA Sage Intacct

> Codes `detailid` à utiliser dans les `taxentries` lors des `ExtBillCreate` / `BillLineTaxEntriesCreate`.

**Solution de taxe** : `TVA française - SYS`

### Comptes 604xxx (frais BS — TVA déductibles principales)

| Taux | `detailid` |
|---|---|
| 20% | `TR2575FRMTPB2BFRMTGDSGLSTDRT` |
| 10% | `TR2576FRMTPB2BFRMTGDSGLREDRT1` |
| 5,5% | `TR2577FRMTPB2BFRMTGDSGLREDRT2` |
| Exonéré 0% | `TR2578FRMTPB2BFRMTGDSGLEXMPT` |

### Comptes 2xxx (immobilisations)
- Utiliser les variants **"FR -Immos..."** : `TR2488xxx`, etc.

---

## Logique métier factures — DÉFINITIVE (validée 29/05/2026, mise à jour 01/06/2026)

> Règles de reconstitution comptable des factures fournisseurs et d'envoi APBILL vers Intacct. **Cette section fait foi**, toute évolution doit la mettre à jour.

### Reconstitution TTC brut comptable

L'utilisateur saisit le **HT à régler** (montant du PDF fournisseur).
- `TTC_à_régler = HT_à_régler × 1,20`

Si des retenues % sont cochées (RG, CP, Finitions, OPC) :
```
TTC_brut_comptable = TTC_à_régler / (1 - RG% - CP% - Finitions% - OPC%)
HT_brut = TTC_brut_comptable / 1,20
TVA_brute = HT_brut × TVA%
```

⚠️ **TVA notionnelle dans la base de retenue** : un sous-traitant en AUTOLIQUIDATION compte pour sa **TVA notionnelle 20%** dans la base de retenue (taux nominal, JAMAIS 0), même si son taux TVA réel est 0. L'autoliquidation ne modifie QUE les écritures de TVA, PAS la base de retenue. Règle appliquée de façon identique côté affichage (JS) et côté APBILL (PHP) via le helper `tauxTvaNotionnelPourBaseRetenue()`.

### Écritures comptables — bloc principal

**DÉBIT** :
- `604000` (ou `333000` selon prestation) — Charge : `HT_brut` (inclut la part HT des pénalités)
- `445660` — TVA déductible 20% : `TVA_brute` (inclut la TVA des pénalités + la TVA notionnelle reportée des ST autoliq)

**CRÉDIT** :
| Compte | Libellé | Montant |
|---|---|---|
| `401000` | `{NomFournisseur}` — Net à payer | Total à régler TTC (− part déjà portée par les ST) |
| `401700` | Retenue de garantie X% | `base_globale × RG%` |
| `401800` | Compte prorata X% | `base_globale × CP%` |
| `401400` | Finitions X% | `base_globale × Finitions%` |
| `401400` | Pénalités | pénalité TTC (même compte que Finitions) |
| `401500` | OPC X% | `base_globale × OPC%` |
| `401600` | CIE | montant fixe (si applicable) |

**Équilibre** : `Total DÉBIT = Total CRÉDIT`.
Seul le `401000` porte le **nom du fournisseur** ; les lignes de retenue ont un libellé générique.

### Pénalités (RÈGLE CORRIGÉE le 01/06/2026 — annule la règle du 29/05)

Les pénalités sont décidées par le MOA après coup et ne figurent pas sur le PDF.
Désormais : la pénalité (TTC) est **éclatée HT + TVA dans la charge `604`/`333` et la TVA `445660`**, et porte une **ligne de crédit dédiée `401400`** (pénalité TTC). Elles n'entrent PAS dans la base de retenue (RG/CP calculées hors pénalités).

### Sous-traitants / autoliquidation (multi-fournisseur)

- Chaque fournisseur (principal + ST) = **un APBILL séparé** dans Sage, numéroté `{num_facture}-V1`, `-V2`, …
- Les **retenues (RG/CP/etc.) sont portées par le PRINCIPAL uniquement**.
- ST en **autoliquidation** : son bloc APBILL n'a PAS de ligne TVA (`604` D = HT / `401000` C = HT), mais sa **TVA notionnelle 20%** est (a) reportée sur le `445660` du principal et (b) comptée dans la base de retenue globale.

### Moteur de calcul centralisé (source unique)

- PHP : `app/Services/DepotFacture/EcrituresComptablesCalculator.php`
- Miroir JS : `resources/js/lib/depotFactureEcrituresCalculator.js`
- Tests anti-régression : `tests/Unit/DepotFactureEcrituresComptablesCalculatorTest.php`
- **L'APBILL lit les lignes comptables PERSISTÉES** (`ocr_raw.saisie.lignes_comptables`), il ne recalcule plus les retenues de son côté → garantit `APBILL == écriture affichée/validée`. Si les lignes comptables sont absentes, la comptabilisation est refusée.

### Validation obligatoire

- Si une ligne comptable a un **compte vide** → bouton "Comptabiliser" **désactivé** + ligne en rouge.
- Comptes GL APBILL configurables dans **Admin > Comptabilité**.
- Comptes hors sync Intacct : **badge orange** + persistance.

### Cas de référence validé en prod (dépôt 33, programme ENV01-001)

PRO CARRELAGE (principal) + 0KREPI (ST autoliq 666 HT), pénalité 1 641,60 :
- **Principal** : `604000` D 26 695,80 / `445660` D 5 472,36 / `401700` RG **1 641,71** / `401800` CP **492,51** / `401400` pénalité 1 641,60
- **ST autoliq** : `604` D 666 / `401000` C 666 (pas de TVA sur le bloc ST)
- **2 APBILL** créés dans Sage avec les montants ci-dessus.

---

## Comptabilisation Intacct — correctifs validés (01/06/2026)

> Chaîne de comptabilisation multi-fournisseur **fonctionnelle de bout en bout**. Correctifs de la session :

- **Solution de taxe obligatoire** : le tenant a la solution `TVA française - SYS` active. CHAQUE ligne d'APBILL doit porter une TAXENTRY. Les lignes de charge portent leur TVA (`FR -BS TVA Déductible 20%`, etc.) ; les **lignes de retenue `401`** portent une TAXENTRY **exonérée 0%** : `IntacctService::TAX_DETAIL_EXONERE_FR = 'FR -BS TVA Déductible Exonéré 0%'` avec `TRX_TAX=0` (sinon erreur `BL01001973`). Une seule TAXENTRY par ligne.
- **CLASSID programme** : la fiche programme enregistre la classe dans `programmes.intacct_class_analytics_id` (relation `intacctClassAnalytics`), PAS dans `intacct_class_id`. La compta lit désormais : `intacctClassAnalytics?->classid` → repli `intacctClass?->classid` (legacy) → `classIdFromAnalytics`.
- **ENUM statuts** : `comptabilisee` a été ajouté aux ENUM `depot_factures.statut` ET `factures.statut` (deux migrations distinctes — les deux tables sont mises à jour à la comptabilisation). `facture_ventilations.statut_intacct` accepte déjà `en_attente` / `envoye` / `erreur`.
- **Persistance `bill_id` avant statut (anti-perte)** : l'`intacct_bill_id` est enregistré dès le retour Intacct (sur le dépôt ET les ventilations), AVANT l'update de statut, pour ne jamais perdre la trace d'un bill créé même si l'update échoue.
- **⚠️ Doublons NON bloqués par Sage** : test prouvé le 01/06 — Intacct accepte plusieurs APBILL avec le même numéro de facture (`027126-V1` créé 2×). La protection anti-doublon doit donc être **côté ERP** (vérifier `intacct_bill_id` en base et/ou interroger Sage par `VENDORID+RECORDID` avant envoi). **Chantier ouvert (voir "En cours" → garde-fou anti-doublon).**

---

## Multi-fournisseur sur factures (validé en prod 01/06/2026)

> Une facture ERP peut être ventilée sur N fournisseurs (principal + sous-traitants), avec un envoi APBILL séparé par fournisseur. **Validé de bout en bout sur Sage le 01/06/2026.**

- N envois APBILL Intacct séparés (`{num}-V1`, `-V2`, …), découpage visuel en N blocs.
- Retenues + reconstitution sur le PRINCIPAL ; autoliquidation TVA gérée pour les ST.
- Détail des règles comptables : voir "Logique métier factures — DÉFINITIVE".

---

## Comptabilisation & workflow Intacct — session 02/06/2026

> Suite directe des correctifs du 01/06. Plusieurs chantiers ouverts ont été **fermés et déployés en prod**. HEAD prod en fin de session = `a9bf8a0` (endpoint probe temporaire), dernier commit fonctionnel majeur `2dce68f`.

### ✅ Garde-fou anti-doublon APBILL (commits `d29c749`, `111baf2`)
- Sage **n'empêche pas** les doublons (re-confirmé en prod). Protection **côté ERP**.
- `findExistingApBillRecordNo(entity, vendor, recordId)` = **fonction unique** de détection, utilisée en **pré-check AVANT** l'envoi (en tête de `createApbillWithOptionalAppymt`, mono + multi) ET en fallback post-erreur. Pas de logique dupliquée.
- **Clé de dédup confirmée = `entity_location_id` + `VENDORID` + `RECORDID`** (= numéro de facture). PAS le `DOCNUMBER` technique. PAS de critère date, PAS de règle "2 sur 3" (rejetée car faux positifs). En cas de doublon → `ApBillAlreadyExistsException`.
- Options : `force_apbill_create` (bypass admin) + `INTACCT_APBILL_PRECHECK_IGNORE_STATES` (filtre STATE).
- `/admin/intacct/test-bill` : toggle **"Vérifier doublon (#020)"** qui fait passer le test par le **vrai chemin prod** (`createApbillWithOptionalAppymt`). **Validé en prod** : 2e envoi identique bloqué avec message + RECORDNO.

### ✅ Synchro workflow à la comptabilisation (commit `7c83a3a`)
- Bug : la compta mettait `factures.statut=comptabilisee` mais ne touchait jamais `workflow_validations` → bandeau bloqué à "À comptabiliser" malgré badge "Comptabilisée".
- Fix : `app/Services/FactureWorkflowService.php` (idempotent) appelé dans `mettreAJourFactureLieeApresComptaIntacct`, **même transaction** que l'update statut.
  - Cas **comptabilisée** : `mise_paiement` → `valide` + crée `paiement_effectif` en `en_attente`.
  - Cas **payée** : les deux niveaux `valide`.
- `workflow_validations` : niveaux `nv1`/`nv2`/`mise_paiement`/`paiement_effectif` ; statuts `en_attente`/`valide`/`rejete`/`relance`.

### ✅ Compte de charge via poste budgétaire du marché (commit `86c1f2c`)
- Remplace le `604000` **en dur**. ⚠️ **La dimension `prestations` a été SUPPRIMÉE** (migration `2026_05_20_180000`). Chaîne réelle : **marché → `poste_budgetaire_type` → `compte_comptable_id` → `comptes_comptables.accountno`**.
- Granularité : **1 compte de charge par facture** (pas de ventilation N prestations).
- Priorité de résolution du poste : `depot_factures.poste_budgetaire_type_id` → `ocr_raw.saisie.poste_budgetaire_type_id` → `marche.poste_budgetaire_type_id` (le dépôt/saisie surcharge le marché).
- **Décision = blocage strict** : si le poste n'a pas de compte comptable → compta refusée avec message clair (PAS de fallback `604000`). Bornée à `destination === PROGRAMMES`.
- Audit prod : **23/42 postes sans compte**, mais seulement **3 marchés impactés** (postes = honoraires/frais/provisions). À renseigner au besoin dans l'admin nomenclature.

### ✅ Cohérence dépôt↔facture frais généraux + routage d'ouverture (commit `2dce68f`)
- **Règle métier confirmée** : facture liée à un **PROGRAMME** ⇒ rattachée à un **MARCHÉ**. Facture **FRAIS GÉNÉRAUX** ⇒ rattachée à un **DEVIS** (lien devis **à créer**, pas encore implémenté) ⇒ `programme_id` ET `marche_id` doivent être **NULL** (dépôt ET facture).
- Bug : un dépôt FG avait hérité de `marche_id`/`programme_id` (upload depuis fiche marché qui les posait sans condition de destination) → désynchro dépôt (≠ NULL) vs facture (NULL) → 404 à l'ouverture (`/programmes/1/factures/20`) + 422. Audit : **1 seul** dépôt désynchronisé en prod (le 34).
- Fix : (a) à la validation, le **dépôt s'aligne sur la facture** (`valider` + `validerHistorique`), garde-fou destination≠PROGRAMMES force NULL ; (b) routage front basé sur le **vrai `programme_id` de la facture** (payloads enrichis dans `FactureGlobaleController`, `MarcheController`, `ValidationController`) ; sinon `/depot-factures/{uuid}`.
- ⚠️ Les factures FG sont dans l'onglet **"Frais généraux"** (filtre `whereNull(programme_id)->whereNotNull(entite_id)`), pas "Programmes". Ne pas confondre.

### 🔜 Mise en paiement Intacct (APPYMT) — diagnostic en cours, page dédiée à venir
- Structure XML APPYMT **déjà codée** (`IntacctService`) mais **désactivée** (`$payment = null` en prod). Champs : `FINANCIALENTITY`, `PAYMENTMETHOD`, `VENDORID`, `PAYMENTDATE`, `APPYMTDETAIL` (RECORDKEY + TRX_PAYMENTAMOUNT).
- **Décisions métier** : le paiement règle le **net** (ligne `401000` = TTC moins pénalités) ; les retenues RG/CP restent dues (réglées séparément). 1 APPYMT par bill (V1+V2). Workflow `paiement_effectif → valide` via `FactureWorkflowService`. Pré-check anti double-paiement (miroir du #020). Page dédiée comme la comptabilisation.
- **Référentiel comptes bancaires/cartes Intacct identifié** (via endpoint probe temporaire `/admin/intacct/probe-financial-entities`, GS_DSI, kill-switch `INTACCT_FINANCIAL_ENTITIES_PROBE_ENABLED`) :
  - Objet **`CHECKINGACCOUNT`** (Compte courant) — 267 au total. Champs clés : **`BANKACCOUNTID`** (= le FINANCIALENTITY de l'APPYMT), `BANKACCOUNTNO`, `BANKNAME`, `GLACCOUNTNO`, `CURRENCY`, `STATUS`, `LOCATIONID`, `USEBANKFILE`, `BANKFILEFORMAT`.
  - Objet **`CREDITCARD`** (Carte de crédit) — 6. Champs clés : **`CARDID`** (= le FINANCIALENTITY pour un paiement carte), `DESCRIPTION`, `CARDTYPE`, `STATUS`, `VENDORID`, **`FINANCIALDATA.CHECKINGACCOUNTID`** (= compte courant associé à la carte).
  - `SAVINGSACCOUNT` = 0 (aucun). `FINANCIALENTITY`/`CREDITCARDACCOUNT` = n'existent pas (FINANCIALENTITY est un champ, pas un objet).
  - ⚠️ Le **réseau sortant Intacct est bloqué depuis le shell SSH** (`cURL error 7`) ; les probes doivent passer par le **contexte web/PHP-FPM** (route admin), comme la compta.
- **6 modes de paiement** à gérer : Chèque, Carte de crédit, Transfert électronique (= virement), Comptant, Fichier bancaire SEPA, + vide. Règles : tous saisissent la **banque** SAUF carte de crédit (saisir la **carte**, son compte courant est dans `FINANCIALDATA.CHECKINGACCOUNTID`). **SEPA exige le RIB sur le fournisseur** sinon échec. Mapping `mapModeReglementDirectToIntacctPaymentMethod` à compléter (manque "Fichier bancaire SEPA" + gestion du vide).
- **Plan** : Étape 1 synchro référentiel (table unique `intacct_financial_entities`, comptes ACTIFS + cartes) → Étape 2 règles par mode → Étape 3 test APPYMT sur page de test.

---

## 💳 Mise en paiement Intacct (APPYMT) & chantier paiement — session 03/06/2026

> **Énorme session.** Réalisation complète de ce qui était annoncé "🔜 à venir" dans la session 02/06. Tout le moteur de paiement APPYMT a été **validé en prod** (virement EFT + carte Credit Card, paiements réels créés dans Sage), une **page dédiée de mise en paiement** a été construite (B1/B2/B3), la **comptabilisation et le paiement ont été séparés** (2 boutons), et de nombreuses commandes de maintenance + correctifs ont été livrés. HEAD prod en fin de session = `f5c511c`.
>
> Méthode : Claude rédige des prompts "SUIVI #NNN" pour Cursor (code/build/commit/push), Robin déploie sur OVH et colle les retours. Numérotation #037→#084 sur cette session.

### ✅ Référentiel comptes bancaires / cartes Intacct (#037→#040, #047, commits `e76cb6b`, `3048d0f`, `330120a`)
- Table **`intacct_financial_entities`** : `intacct_object`, `type` (bank_account / credit_card), `intacct_id` (= **BANKACCOUNTID** pour les comptes, **CARDID** pour les cartes), `name`, `gl_account_no`, `currency`, `status`, `location_id`, `card_type`, `linked_checking_account_id`, `vendor_id`, `use_bank_file`, `raw_payload`.
- Modèle `IntacctFinancialEntity`, Job `SyncFinancialEntitiesDepuisIntacct` (queue `intacct`), endpoint `POST /admin/intacct/sync-financial-entities`, bouton Admin/Intacct.vue, scheduler 03:30.
- **🐛 Bug filtre STATUS (#038-040)** : `readByQuery` legacy ne matche pas la clause `STATUS='active'` → ramenait 0. **Fix = query vide + filtrage PHP** `strtolower(trim(STATUS))==='active'`. Résultat validé : **272 entités (266 comptes + 6 cartes)**.
- **🐛 Bug location cartes (#047, `330120a`)** : `getCreditCards` lisait `LOCATIONID` (inexistant sur CREDITCARD) ; **le bon champ = `LOCATION`**. Après resync : cartes correctement rattachées (CB ROBIN/ANNE/GUILHEM→E-04, CB LAURY→E-13, CBGP/CBMP→E-05).

### ✅ Moteur APPYMT — VALIDÉ EN PROD (#041→#045, commits `2628566`, `6f5dc07`)
- **Objets Intacct confirmés** : `CHECKINGACCOUNT` (champ entité = `LOCATIONID`, id = `BANKACCOUNTID`), `CREDITCARD` (champ entité = **`LOCATION`** pas LOCATIONID, id = `CARDID`, `FINANCIALDATA.CHECKINGACCOUNTID` souvent NULL = normal CB classique). STATUS réel = "active".
- **Mapping mode FR→EN validé** (PAYMENTMETHOD attend les valeurs API EN, pas les libellés FR) : Carte de crédit→`Credit Card` (FINANCIALENTITY=CARDID) ; Transfert électronique→`EFT` ; Chèque→`Printed Check` ; Comptant→`Cash` ; Fichier bancaire SEPA→`EFT` ; Prélèvement→`EFT` (#076). Mode vide → refus.
- **COHÉRENCE ENTITÉ↔COMPTE OBLIGATOIRE** : le compte/carte (FINANCIALENTITY) doit appartenir à l'entité du paiement (`location_id == entité`) sinon Sage rejette "bank account invalid". HECTARE=E-04, ENVOL=E-13.
- `PAYMENTDATE` format **MM/DD/YYYY**. 1 APPYMT par bill/ventilation.
- **⚠️ RÈGLE MÉTIER CLÉ** : l'APPYMT **solde le 401 dans Sage mais NE déclenche PAS de décaissement réel**, SAUF mode "Fichier bancaire SEPA" (vrai paiement par le service compta). Donc payer une facture déjà réglée (CB/virement) = juste l'enregistrement comptable du règlement. Pas de risque de double décaissement pour CB/virement/chèque/comptant.
- **Tests réels validés en prod** : virement EFT (paiement RECORDNO 31140) + carte Credit Card (RECORDNO 31145) acceptés par Sage sur E-04, via `/admin/intacct/test-bill` (toggle "Créer aussi le paiement" + DRY-RUN coché par défaut qui montre le XML sans envoyer).
- **Annulation d'un paiement Sage** : annuler le PAIEMENT d'abord, puis le bill (Sage bloque l'extourne d'un bill payé).

### ✅ Prérequis programme — compte bancaire de paiement (#048-049, commit `6f29b19`)
- Colonne **`programmes.financial_entity_id`** (FK→`intacct_financial_entities`) = compte bancaire de **PAIEMENT**, distinct de `compte_bancaire_intacct_id` (GL512, qui reste pour la **compta**).
- Migration + backfill (1 matché via gl_account_no, 3 sans GL). Select admin "Compte bancaire de paiement (Intacct)" filtré par entité dans Programmes/Edit.vue.

### ✅ Page dédiée Mise en paiement — B1/B2/B3 (#051→#058)
- Route `/factures/mise-en-paiement`, page `Factures/Paiement/Index.vue`, `PaiementController`, services `PaiementLignesPayablesService` / `PaiementAppymtBatchService` / `Net401DepuisLignesComptables`. Accès GS_DSI + GS_COMPTABILITE + GS_ADMIN_ERP + GS_DIRECTION_GENERALE (feature_key `factures.paiement`). Menu : Comptabilité → Paiement.
- **B1 (#051-052)** : liste read-only des factures `paiement_effectif=en_attente`. **1 ligne par ventilation** (multi-fournisseur = N lignes, ne pas recalculer). Colonnes + colonne **TOTAL FACTURE** + badge **MULTI** + indentation ↳ + fond alterné (lisibilité multi-fournisseur).
- **B2 (#053-057)** : sélection (cases), **édition inline "click-to-edit"** (composant `PaiementEditableCell.vue`, popover Teleport body `position:fixed`) pour entité/compte/mode/date/montant, surlignage **orange** si modifié vs initial, mode Carte→select bascule sur cartes, cohérence entité↔compte signalée, PDF popup. 🐛 Bugs résolus : `overflow:hidden` coupait le dropdown (#056), popover hors écran sans `position:fixed` (#057).
- **B3 (#058, commit `f0a2c32`)** : bouton **"Payer la sélection"** : boucle 1 APPYMT/bill, **dry-run coché par défaut** (aperçu XML, zéro envoi), contrôles (compte programme, entité↔location, SEPA IBAN/BIC, anti double-paiement local + `billHasAppymtPayment` Sage), reprise partielle (rapport par ligne), statut payée + workflow `paiement_effectif=valide`.

### ✅ Affichage page paiement (#067, #068, #073, #084)
- **#067 (`9848857`)** : classe/compte pour les **FG** (sans programme) → classe lue depuis `ocr_raw.saisie.analytics_id` ; compte de paiement par défaut par entité (**E-04→512900, E-13→512901**) ; **bouton popup "écritures comptabilisées"** (lignes_comptables : compte de charge, TVA, classe, montants — source de vérité de l'APBILL).
- **#068 (`a8dfa00`)** : mode + date par défaut **repris de la COMPTA** (`PaiementComptaReglementResolver` : mode depuis `saisie.mode_reglement`, date depuis `depot.date_reglement`, carte depuis `saisie.compte_bancaire_id`) au lieu de Virement/aujourd'hui. Colonne "DATE RGL." visible/éditable.
- **#073 (`3eeda89`)** : colonne montant fusionnée (net à payer en **gros vert** emerald-700 + total facture en petit gris dessous, dernière colonne à droite).
- **#084 (`38c9a53`)** : fix colgroup 100% (Montant 12%→29%) — suppression du trou vide entre FOURN. et MONTANT.

### ✅ Séparation COMPTA / PAIEMENT — 2 boutons (#060-061, commit `c338fef`)
- **Décision métier** : retrait du paiement automatique implicite des factures "déjà réglées". CHOIX EXPLICITE du comptable :
  - **"Comptabiliser"** : APBILL seul → `paiement_effectif=en_attente` → la facture (même déjà réglée) apparaît en file de Mise en paiement.
  - **"Comptabiliser et payer"** : APBILL → **popup récapitulatif modifiable** (entité/mode/compte/carte/montant/date) → **dry-run par défaut** → APPYMT → payée. N'apparaît PAS en mise en paiement. SEPA autorisé.
- Services `PaiementAppymtExecutionService`, `ComptabilisationPaiementApercuService`, dialog `ComptabilisationPaiementDialog.vue`. Service partagé validation/post-traitement entre les 2 chemins.
- **#063 (`d22eac2`)** : les factures "déjà réglée" comptabilisées (bouton Comptabiliser seul) doivent apparaître en mise en paiement → retrait du filtre `deja_reglee=false` de `PaiementLignesPayablesService` + `FactureWorkflowService` crée `paiement_effectif=en_attente` même sans étape `mise_paiement`.
- **#076 (`e280fc7`)** : mode **"Prélèvement" → mappé vers Transfert électronique / EFT** (n'existe pas dans Sage). `PaiementModeReglementMapper` centralisé (compta + paiement + resolver + normalisation JS).
- **#077 (`4307ed8`) + #079 (`4c0509e`) + #080 (`0b65878`)** : à la **comptabilisation**, mode CB → le select propose les **cartes** (pas un compte 512). ⚠️ **Filtre des cartes par SOCIÉTÉ (E-04), pas par établissement** : la société est `entites.intacct_id`, l'établissement (`etablissements.location_id`, ex. MTP00) est une sous-localisation rattachée via `etablissements.parent_id`. Les cartes ont `location_id` = LOCATION Sage. Commande `erp:sync-comptes-cartes-comptables` (backfill `comptes_comptables.accountno = CARDID`) + diag `erp:diag-cartes-compta --societe=E-XX` + endpoint `GET /admin/intacct/diag-cartes-compta`.

### ✅ Commandes de maintenance créées (toutes dry-run par défaut, `--apply` après validation)
- **`factures:rattrapage-file-paiement --facture=FAC-XXX [--apply]`** (#063-064, `67c1dbe`) : remet en file une facture marquée payée à tort SANS APPYMT réel (filtre "sans trace paiements_intacct"). Affichage par référence.
- **`factures:decomptabiliser --facture=FAC-XXX [--apply]`** (#064-065, `67c1dbe`/`071b093`) : repasse en "à comptabiliser" côté ERP (statut, dépôt, ventilations `intacct_bill_id` vidé + `statut_intacct='en_attente'` [🐛 NULL interdit corrigé #065], workflow supprimé), transactionnel. **AVERTIT d'extourner manuellement les bills Sage** (anti-doublon #020).
- **`factures:annuler-paiement --facture=FAC-XXX [--apply]`** (#069, `152fecb`) : miroir du paiement (DELETE `paiements_intacct`, statut→comptabilisee, workflow pe→en_attente, rouvre la file). Ne touche pas Sage. Service `FactureAnnulerPaiementService`.
- **`erp:sync-comptes-cartes-comptables`** + **`erp:diag-cartes-compta`** (#080) : sync/backfill des comptes carte + diagnostic résolution société.

### ✅ Boutons UI d'annulation / rejet
- **"Annuler la comptabilisation"** (#071, `de549e3`) : ERP only + avertissement Sage RECORDNO à extourner manuellement. Endpoints GET/POST `/factures/{id}/annuler-comptabilisation`, refus si paiement attaché. Dialog `AnnulerComptabilisationDialog.vue`.
- **"Rejeter une facture" à la comptabilisation** (#082, `f698fa7`) : statut **`rejetee`** final (sort des files), **motif obligatoire** (colonnes `rejet_motif`/`rejet_par_user_id`/`rejet_at`), **notification au validateur** (cascade : dernier nv2 validé → nv1 → validateur désigné au dépôt) via `AlerteService::factureRejeteeParComptabilisation` + `retirerFactureAComptabiliser`. Services `FactureComptabilisationRejetService`, `FactureValidateurNotificationResolver`.

### ✅ Anti-doublon APBILL #020 — ignore les bills EXTOURNÉS (#072, commit `fd381a4`)
- 🐛 Cas : facture dé-comptabilisée puis APBILL **extourné** dans Sage → le bill original reste (state **`Reversed`**, UI "Extourné") + une écriture d'extourne (state **`Reversal`**, UI "Extourne") → l'anti-doublon les retrouvait et bloquait la re-comptabilisation (faux positif).
- **Fix** : `findExistingApBillRecordNo` ignore désormais les états `Reversed`/`Reversal`/`Extourné`/`Extourne` (config `INTACCT_APBILL_PRECHECK_IGNORE_STATES`, valeur par défaut renseignée). Les bills **actifs** restent bloqués (#020 intact). Probe `GET /admin/intacct/probe-apbill-states?entity=E-XX&recordno=...`.

### ✅ Détection doublon AU DÉPÔT resserrée (#078, commit `99ad336`)
- ⚠️ **≠ anti-doublon APBILL #020**. C'est la détection au DÉPÔT/upload (`DepotFactureDuplicateDetector`, hook OCR `DepotFactureClaudeAnalysisService`).
- 🐛 Faux positif : 2 factures Amazon **différentes** (numéros/dates/montants différents) vues comme doublon car critère trop large (siret seul).
- **Fix** : doublon SEULEMENT si **même fournisseur + même numéro de facture**. Fallback fournisseur+date+montant exact si numéro OCR absent.

### ✅ Nom de fichier lisible (#074-075, commit `4f2ae65`)
- **Principe clé : le nom de fichier NE sert PAS d'identifiant.** Identité = uuid + chemin `{entite}/{année}/{uuid}.pdf` (stable). Nom lisible = **cosmétique** (affichage + Content-Disposition au téléchargement), peut évoluer quand l'OCR se complète, **sans renommer le fichier physique**.
- Colonne `depot_factures.nom_affichage` (nullable), recalculée au dépôt + à chaque maj OCR (idempotent). Format `FOURNISSEUR_NUMERO_DATE.pdf` (slug propre, infos manquantes → nom partiel). Service `DepotFactureNomLisibleService`. UI listes : `nom_affichage || nom_fichier_original`.

### ✅ Bouton œil liste Comptabilité (#083, commit `f5c511c`)
- 🐛 Régression introduite par `2dce68f` : l'œil ouvrait le **dépôt** au lieu de la **fiche facture** (workflow). Fix `Global.vue` `depotHref` + `FactureGlobaleController` (repli `programme_id` du dépôt).

### ✅ Fiche facture Frais Généraux + suite de session (#080-popup → #091)
> Suite directe (même journée 03/06). Construction de la fiche facture pour les FG, correctifs de navigation/validation, comptes bancaires depuis le référentiel, et **résolution d'une crise 500 qui bloquait toute la comptabilisation**. HEAD prod final = `1968281`.

- **#080-popup (`c32aa57`)** : "Ouvrir le doublon" au dépôt → **modal** (comparaison infos + PDF + multi-doublons) au lieu d'écraser la page de saisie. Composant `DepotFactureDoublonModal.vue`, `findAllMatches` + `DepotFactureDoublonPayload`.
- **#085 (`6858253`)** : bouton **œil** sur l'onglet **Frais Généraux** → fiche facture avec workflow. Avant, aucune fiche workflow n'existait pour les FG (Show.vue était liée au programme). Nouvelle route **`GET /factures/fiche/{facture}`** → `Factures/Show.vue` (context=frais_generaux). Route programme `/programmes/{pid}/factures/{fid}` inchangée.
- **#086 (`4bab410`)** : **validation d'étape workflow** sur la fiche FG. Bug : `submitValider()` faisait un return early si `isFraisGeneraux` → aucun POST. Routes `POST /factures/fiche/{fid}/valider|rejeter`, logique back `processValider/processRejeter` réutilisée.
- **#087 (`676b0cf`)** : 🐛 le POST validation FG renvoyait **404** (cache de routes : la route #085 était en GET seul). Fix : groupe de routes `factures/fiche` + middleware `factures.lecture`, plus de `abort 404` sur programme_id null. **⚠️ Post-déploiement : `php artisan route:clear` requis.**
- **#088 (`05b200b`)** : en **comptabilisation**, le champ "Compte bancaire" (mode non-CB) liste désormais les **comptes bancaires du référentiel Intacct** (`intacct_financial_entities` type bank_account, BANKACCOUNTID) filtrés par société, au lieu des comptes GL bruts. Mode CB → cartes (inchangé). `ComptabilisationBankCompteOptions` + commande **`erp:sync-comptes-banques-comptables`** (backfill `accountno=BANKACCOUNTID`, **266 comptes** synchronisés). Défauts E-04→512900 / E-13→512901 pointant vers l'entité du référentiel.
- **🚨 #090 (`79c61d7`) — CRISE 500 RÉSOLUE** : au clic "Comptabiliser", **erreur 500 bloquant TOUTE la compta en prod**. Cause = `DepotFactureController` utilisait `FactureHistoriqueSaisie` **sans `use`** → PHP la cherchait dans `App\Http\Controllers\` (introuvable). La vraie classe est **`App\Support\FactureHistoriqueSaisie`** (usages : L1152 `snapshotFromDepot`, L2575 diff `logHistoriqueIfSaisieChanged`). Fix = ajout du `use`. **Diagnostic via `grep "production.ERROR" storage/logs/laravel.log`** (le 500 navigateur ne montre rien). ⚠️ Leçon : ce bug est passé car les **tests sqlite de Cursor sont "skipped"** (pas de pdo_sqlite) → un `use` manquant ne déclenche aucune alerte. Penser à vérifier le chargement des classes / un test réel après les gros commits.
- **#091 (`1968281`)** : détection doublon au dépôt corrigée → **numéro de facture + montant TTC STRICTEMENT** (fournisseur IGNORÉ car pas toujours dispo à l'OCR). Fallback date+montant si numéro absent, sinon pas de détection (éviter les faux positifs). + **Validateur par défaut = le déposant** (auth user) au dépôt, pré-rempli si vide (pas écrasé en édition).

### ✅ Test bout-en-bout Amazon — VALIDÉ (03/06)
- Cycle complet validé sur une vraie facture (FAC-2026-023) : dépôt → comptabilisation → mise en paiement → **APPYMT réel** avec mode CB + bonne date (17/04/2026). Le souci de CB côté Sage a été résolu par Robin. Toute la chaîne fonctionne sur un cas réel.


### ✅ Session 04/06/2026 — Compta facture de marché, pré-remplissage fournisseur, blocage NV1/NV2, frise workflow
> Suite de session (04/06). Pré-remplissage des réglages fournisseur à l'OCR, déblocage du workflow de dépôt marché, **résolution complète de la comptabilisation des factures de marché** (compte de charge via le poste budgétaire du marché + écriture HT/TVA équilibrée), statuts informatifs "Bloqué NV1/NV2", affichage du validateur NV1 dans la frise, colonne/filtre Société, RIB fournisseur en paiement, personnalisation de la page Mise en paiement (colonnes/filtres/modif groupée), annulation compta par ligne, workflow FG à un seul niveau (NV1), nettoyage de la fiche facture (en-tête, breadcrumb→Retour), colonne règlement + règle SEPA, bouton Comptabiliser bleu, et regroupement des boutons workflow dans la barre d'actions (Modifier bleu). HEAD prod = `b1f557e`.

- **#094 (`2b78309`)** : 🔧 FIX WORKFLOW (Option 1 du diag #093). Le contrôle `assertProgrammeChargeAccountConfiguredForSaisie` ("les lignes comptables ne contiennent pas la charge du poste budgétaire") s'exécutait AU DÉPÔT (valider/validerHistorique/update) alors que la génération des écritures n'est possible qu'à la COMPTABILISATION → utilisateur bloqué (on lui demandait de "régénérer les écritures" là où le bouton n'existe pas). RETIRÉ du dépôt (154 lignes supprimées de `DepotFactureController`), CONSERVÉ à la compta. + faux positif "ligne vide" corrigé via `LignesComptablesLigneHelper` (ignore les lignes sans débit/crédit significatif). NOTE diag #093 : la table `prestations` a été SUPPRIMÉE en prod (migration 2026_05_20) → le compte de charge vient désormais de `postes_budgetaires_types.compte_comptable_id`, pas d'une prestation.
- **#095 (`47abb6a`)** : PRÉ-REMPLISSAGE RÉGLAGES FOURNISSEUR. Au dépôt, depuis la dernière facture du même fournisseur, pré-remplir UNIQUEMENT les champs simples : `categorie_id`, `analytics_id`, `mode_reglement`, `etablissement_id`, validateur. **LIGNES COMPTABLES / COMPTE DE CHARGE EXCLUS** (pour ne pas recréer le souci #093/#094). Critère "même fournisseur" : `fournisseur_id` > `siret` 14 (nom OCR jamais seul). Service `DepotFactureReglagesFournisseurResolver::resolveForDepot()` (`findLastDepot` hors rejete/archive/historique, priorité comptabilisee/paye/valide/a_comptabiliser). Priorité : OCR courant > brouillon utilisateur > historique > défauts. Validateur : historique fournisseur > déposant (#091) > vide. Endpoint `GET /depot-factures/{uuid}/reglages-fournisseur?fournisseur_id=`.
- **#096 (`452fb95`)** : NOUVEAUX STATUTS "BLOQUÉ NV1 / BLOQUÉ NV2" (informatif). Sur une facture en attente NV1/NV2, bouton "Bloquer" (à côté Valider/Rejeter) avec MOTIF OBLIGATOIRE, pour signifier "vue mais ne pas payer pour l'instant". PUREMENT INFORMATIF : la facture reste au même niveau workflow (statut reste en_attente). Champs `bloque*` sur `workflow_validations` (migration), `WorkflowValidationBloquePresenter`, composant `WorkflowBloqueBadge`. Badge visible fiche + listes (Global/Programme/Validations), motif + qui/quand. Valider depuis l'état bloqué = possible avec ALERTE DE CONFIRMATION rappelant le blocage (+ motif) ; la validation lève le blocage. Marche pour FG ET programme. ⚠️ Post-déploiement : `php artisan migrate --force` + `route:clear`.
- **#097 (`2f6e852`)** : 🔧 COMPTA FACTURE DE MARCHÉ — compte de charge via le poste du marché + TVA. RÈGLE MÉTIER : à la création d'un marché, on choisit le POSTE BUDGÉTAIRE DU BILAN auquel il est associé ; ce poste est relié à un COMPTE COMPTABLE. Le compte de charge des écritures = le compte du poste budgétaire DU MARCHÉ (chaîne marché → poste → compte, ex. 331453). Problème 1 : "poste budgétaire du marché introuvable" malgré marché/poste/compte OK → cause = résolution du poste AVANT le marché, `marche_id` facture ignoré → nouveau service `ProgrammeChargeAccountResolver` (priorité marché → `poste_budgetaire_type_id` → accountno). Problème 2 : écriture déséquilibrée (331453 débit vide, 401 crédit seul, pas de TVA) → cause = HT=0 avec TTC renseigné → `inferHtDepuisTtcSiManquant` + calculateur HT/TVA depuis ventilations (sans retenues) + garde-fou watch. Résultat : écriture ÉQUILIBRÉE (débit charge HT + débit TVA déductible + crédit 401 TTC). NE PRÉSUME PAS la classe du compte.
- **#098 (`df6d2a6`)** : FRISE WORKFLOW — NV1 affiche validateur + date. Cause : la validation AUTOMATIQUE de NV1 (quand déposant = validateur NV1) ne créait PAS de ligne NV1 en base → rien à afficher (contrairement à NV2). Fix stockage : au dépôt, si NV1 skip auto → créer la ligne `workflow_validations` nv1 valide (validateur_id = déposant, date_validation, commentaire auto) + nv2 en_attente. Fix affichage : `WorkflowValidationStepPresenter` + `actor_automatique` → mention "(automatique)" dans la frise. + validateur_id renseigné à chaque validation NV1/NV2 manuelle. ⚠️ Pas de migration rétroactive : les factures déposées AVANT ce fix peuvent ne pas avoir de ligne NV1 (frise NV1 vide pour l'historique).


- **#099 (`62809f6`)** : LISTE FACTURES — colonne + filtre **Société**. Colonne "Société" (format "E-XX — nom", ex. E-23 ARBOREA / E-04 HECTARE) triable + filtre Société dans le bloc Filtres, sur les onglets Programmes ET Frais Généraux (et Autres). Service `SocieteEntiteResolver` : l'entité d'une facture programme vient de `programme.entite_id`, celle d'une FG de `depot/facture.entite_id`. Pas de migration.
- **#100 (`7ae0e24`) puis #101 (`816a39a`)** : MISE EN PAIEMENT — colonne Fournisseur "FXXXX — NOM" (ID Intacct à gauche du nom, une ligne) + **RIB/IBAN** dessous. Le RIB n'était PAS stocké → ajout colonnes `iban/bic` sur `fournisseurs` (migration), synchro depuis Intacct (`VENDORBANKFILEDETAIL`) via **endpoint web admin** `POST /admin/intacct/sync-fournisseurs` (⚠️ jamais en CLI : sortie HTTPS bloquée en SSH OVH). `FournisseurPaiementPresenter` (libellé + RIB), "RIB non renseigné" si absent. 🚨 #101 = FIX 500 : le #100 avait **redéclaré** `IntacctService::readByQueryPaginated()` (méthode déjà existante) → erreur fatale PHP au chargement de IntacctService (donc 500 sur la synchro ET tout appel Intacct). Diagnostic via `grep ERROR storage/logs/laravel.log`. Fix = suppression du doublon + `getVendorBankDetailsIndexed()` recâblé sur la signature d'origine. ⚠️ 2e bug de ce type en 2 jours (cf. #090 `FactureHistoriqueSaisie`) → les tests sqlite skipped ne détectent pas les redéclarations : faire `php -l` + `ReflectionClass` sur les fichiers PHP modifiés avant push.
- **#102 (`4c54f65`)** : MISE EN PAIEMENT — 3 améliorations. (1) **Colonnes redimensionnables** (poignées sur les `<th>`, largeurs en %, ordre inchangé, pas de masquage — `usePaiementTableColumns.js` ; table HTML custom, pas un DataTable PrimeVue). (2) **Filtres** (entité/société, statut, mode, fournisseur, dates de règlement) façon Global.vue, filtre serveur avant pagination — `PaiementLignesFiltre`. (3) **Modification groupée** : sélectionner plusieurs factures (cases `rowEdits[].selected` existantes) et appliquer une valeur commune sur compte bancaire / mode de règlement / date de règlement UNIQUEMENT, avec **aperçu + confirmation obligatoires** (ancien → nouveau) avant application. Compte en masse = même société + référentiel Intacct selon le mode. Dry-run APPYMT intact, écritures/statut workflow non touchés.

- **#103 (`9ad89e1` inclus dans le commit triangle)** : MISE EN PAIEMENT — bloc Filtres COMPACTÉ sur une seule ligne (champs + Appliquer/Réinitialiser horizontaux, plus petit), au lieu de champs empilés en pleine hauteur.
- **#104 (`9ad89e1`)** : DÉPÔT — (1) le TRIANGLE D'ALERTE (`row.sans_validateur`) restait affiché à tort alors que le valideur était pré-rempli (#091/#095) : `depotSansValidateur()` ne lisait que `ocr_raw.saisie`, sans le valideur en mémoire Vue → décalage. Fix `DepotFactureValidateurEffectif` (saisie + réglages #095 + déposant #091) + resynchro de la liste après prefill. (2) Renommage UI **"Validateur" → "Valideur"** PARTOUT (labels/tooltips/alertes/toasts uniquement, PAS les variables `validateur_id`/`validateur_email`).
- **#105 (`ded708c`)** : MISE EN PAIEMENT — action **"Annuler la comptabilisation" PAR LIGNE**, réutilisant le MÊME endpoint que la fiche (`FactureAnnulerComptabilisationController` show/store + `FactureDecomptabilisationService` : statut → a_comptabiliser, purge bill Intacct, workflows paiement) et le MÊME dialog d'avertissement (`AnnulerComptabilisationDialog.vue`). Permissions GS_COMPTABILITE/GS_ADMIN_ERP/GS_DSI. Pas de 2e chemin divergent.
- **#106 (`6a55a8b`)** : MENU Comptabilité réordonné → "Toutes les factures" en 1er (puis Boîte de dépôt, Validation fournisseur, Comptabilisation, Paiement).
- **#107 (`f38e33c`)** : 🐛 FIX #105 — le dialog "Annuler la comptabilisation" était VIDE sur la page paiement (le `watch` n'avait pas `immediate: true` → le GET show n'était jamais appelé). Fix : `watch([visible, factureId], { immediate: true })` + reset aperçu. Affiche maintenant l'avertissement complet + RECORDNO/bill + référence facture, comme la fiche.
- **#108 (`0c4d12d`)** : FRISE WORKFLOW — étape "Comptabilisée" affiche désormais date + qui (format "JJ/MM/AAAA par Nom", comme NV2/NV1 #098) ; AU SURVOL → tooltip avec les écritures comptables (compte/débit/crédit) + RECORDNO/bill Intacct. Migration (ENUM `workflow_validations` niveau `comptabilisation`), `FactureWorkflowService::enregistrerComptabilisationValidee()`, `FactureWorkflowComptabilisationPresenter`, `WorkflowComptaEcrituresHover.vue`. ⚠️ `migrate --force`. Date/auteur stockés à partir de maintenant (pas rétroactif, repli mise_paiement legacy).
- **#109 (`421ff83`)** : FICHE FACTURE — nettoyage en-tête. Titre = **vrai numéro fournisseur** (ex. FR6II0MABEI) au lieu du n° interne FAC-2026-XXX ; **un seul** bouton "Annuler la comptabilisation" (barre d'actions) au lieu de 3 ; bouton "Voir dans Sage Intacct" SUPPRIMÉ ; "N° fournisseur" retiré ; AJOUT des 3 dates (facture / échéance / paiement prévisionnel) ; **Entité avant Fournisseur** ; fournisseur NON cliquable.
- **#110 / #112 (`dd17850`)** : WORKFLOW FRAIS GÉNÉRAUX À UN SEUL NIVEAU (NV1). Nouvelle règle : FG (`programme_id IS NULL`) = NV1 uniquement → après NV1 validé, passe DIRECTEMENT à `a_comptabiliser` (pas de NV2). Programme/marché = NV1 + NV2, INCHANGÉ. Helper `FactureEstFraisGeneraux` + service `FactureWorkflowApresNv1Service`, branché aux 4 points : DepotFactureController, FactureController::processValider, **ValidationController (2e chemin /validations aligné)**, buildWorkflowForDisplay (frise FG = Analyse → NV1 → Comptabilisée → Mise en paiement → Payée), + notifs NV2 skip FG. Nouvelles FG seulement (FG déjà en NV2 inchangées, pas de rétro). #098 auto NV1 FG → directement a_comptabiliser.
- **#111 (`192c893`)** : FICHE FACTURE — les DEUX fils d'ariane ("Suivi Chantier / Factures / ..." du bandeau via AppShell.vue + celui de la page) SUPPRIMÉS, remplacés par un bouton **"Retour aux factures"** → onglet correspondant (FG → Frais généraux, programme → Programmes). Le breadcrumb "Suivi Chantier" reste intact sur les autres pages (Marchés, Programmes).
- **#113 (`02750a9`)** : MISE EN PAIEMENT — colonne **"Règlement"** (badges "Déjà réglée" / "À régler", source `depot_factures.deja_reglee` + fallback `ocr_raw.saisie.deja_reglee`) + règle métier : si facture **déjà réglée**, le mode **SEPA est grisé/indisponible** dans le select de ligne ET exclu de la modification groupée #102 (aperçu + raison). `PAIEMENT_MODE_SEPA`, reset SEPA à l'init si déjà réglée.
- **#114 (`b096e69`)** : FICHE FACTURE — bouton "Comptabiliser" passé en BLEU et déplacé dans la barre d'actions (à côté Modifier/Supprimer), suppression du bouton vert isolé en haut (et de la variante header mobile). Logique compta/FG/programme inchangée.
- **#115 (`b1f557e`)** : FICHE FACTURE — boutons workflow **Valider (vert) / Bloquer (orange) / Rejeter (rouge)** regroupés dans la BARRE D'ACTIONS (à côté Modifier/Supprimer), suppression des boutons isolés en haut à droite. **Modifier passé en BLEU** sur toutes les pages workflow (FG + programme). Barre selon l'état : en validation → Valider/Bloquer/Rejeter/Modifier/Supprimer ; à comptabiliser → Comptabiliser(bleu)/Modifier/Supprimer ; comptabilisée → Annuler compta/Modifier/Supprimer. Logique inchangée. Bundle `Show-CLwiOkE2.js`.

---

## 🏷️ Multi-enseigne + bugs paiement + module super-admin factures — session 05-08/06/2026

> Grosse session : (1) socle **multi-enseigne** (SUIVI A) ; (2) série de **bugs paiement/fournisseur** (#138→#146) tous liés au même nœud — plusieurs sources de fournisseur/RIB non alignées ; (3) **module super-admin factures** (couteau suisse de rattrapage ERP↔Sage, #148 A→D) ; (4) **fix race condition** comptabilisation (#149) + filtre paiement (#150). HEAD prod fin de session = `f7bb3be`.

### 🏷️ Multi-enseigne — SUIVI A (socle, déployé)
> Permettre à l'ERP de gérer plusieurs enseignes (ENVOL, HECTARE) avec nomenclature/timeline/modules/branding/GED propres, sans casser le moteur comptable.

- **Deux axes INDÉPENDANTS sur `programmes`** (cardinal — ne JAMAIS dériver l'un de l'autre) :
  - **`enseigne_id`** — axe **applicatif**, stable, pilote nomenclature / timeline / modules / branding / GED. JAMAIS dérivé de l'entité.
  - **`entite_id`** — entité comptable **Sage Intacct**, mutable (un programme démarre sur l'entité mère puis migre vers une SCCV dédiée plus tard).
- **Règle cardinale** : le **moteur comptable ne lit JAMAIS `enseigne_id`** (il ne lit que `entite_id` / classes / postes). `enseigne_id` = présentation/organisation, pas compta.
- **Table `enseignes`** seedée : **ENVOL** (entité par défaut E-13, tous modules actifs) et **HECTARE** (entité par défaut E-04, modules TMA / AdF VEFA / Stades VEFA désactivés).
- 5 migrations déployées, dont **fix de l'erreur MySQL 1830** (`enseigne_id cannot be NOT NULL needed in a foreign key constraint SET NULL`) → la FK `SET NULL` impose une colonne nullable ; backfill Envol puis contrainte. Commit socle `d0532b0`.
- Création de programme : **sélecteur d'enseigne** qui préfixe les codes (**ENV** vs **HEC**). Page lecture seule **`/admin/enseignes`**.
- **Reste à faire multi-enseigne (ordre imposé A→B→C→D)** :
  - **SUIVI B** : filtre enseigne partout (bilan, nomenclature, sélecteurs marché/dépôt) + unicité composite `(enseigne_id, code)` sur `rubriques_bilan` + `NomenclatureController`. Vérifier qu'Envol reste identique avant de passer à C.
  - **SUIVI C** : `HectareNomenclatureSeeder` (11 rubriques A→K, codes `H_`).
  - **SUIVI D** : frises chronologiques par enseigne (totalement indépendantes, pas de catalogue partagé) — DIAG-FRISE d'abord.
  - Chantiers séparés (chacun avec DIAGNOSTIC) : **GED Hectare**, **commercialisation terrain** (nature_lots), **recettes terrain dans le bilan**.
  - **Angle mort connu** : changement d'entité d'un programme après coup → les snapshots facture/dépôt sont figés (à traiter le moment venu).
- ⚠️ **Gap prod-vs-code connu** : la prod a **8 rubriques bilan** (Foncier, Taxes, Concessionnaires, Travaux, Honoraires techniques, MOA, Honoraires commerciaux, Financiers juridiques — préfixes `T_`, `V_`, `C_`, `HT_`, `CD_`, `FG_`) qui NE correspondent PAS au seeder à 5 rubriques du code. Confirmé par Robin.

### 💸 Série bugs paiement / fournisseur (tous liés : sources fournisseur/RIB désalignées)
- **#138 (`5ef9cb6`)** : 🐛 WORKFLOW RE-VALIDATION. Sur la page dépôt, une facture déjà engagée (`depot.facture_id != null`) pouvait être réinjectée dans le workflow → `valider()` recréait une `Facture::create` → facture orpheline + incohérence Intacct. Fix : seuil "workflow engagé" = `depot.facture_id != null` ; le bouton "Envoyer en validation" devient "**Enregistrer**" une fois engagé (route PATCH `/factures/comptabilisation/{uuid}` = `ComptabilisationController@update`, préserve le statut) ; backend `valider()` renvoie **422** si `facture_id != null`. Rejetée = re-dépôt à zéro. Modifs post-engagement tracées via `FactureHistoriqueSaisie::diff()`.
- **#139 (`e25795e`)** : 🐛 ÉDITION FOURNISSEUR MARCHÉ. Sur `Marches/Show.vue` (AutoComplete inline), erreur "fournisseur id required" : `@blur` committait trop tôt + extraction d'id boguée (`42?.id` → undefined → null envoyé). Fix : commit sur **`@item-select` uniquement** (plus `@blur`/`@keyup.enter`), `resolveFournisseurIdForPayload` accepte un id numérique, label "{intacct_id} — {nom} — {siret}", recherche multi-champs. `factures.fournisseur_id` reste indépendant du marché (changer le marché ne propage pas aux factures).
- **#141 (`a11a67e`)** : MASQUER Modifier+Supprimer sur fiche facture **comptabilisée/en_paiement/payee** (boutons absents pour tous, garde-fou backend `can_edit_depot=false`). Page dépôt + bouton "Enregistrer" #138 inchangés.
- **#142 (`093b7f7`)** : 🐛 FOURNISSEUR FIGÉ EN MISE EN PAIEMENT. Après changement de fournisseur sur un marché, la mise en paiement gardait l'ANCIEN fournisseur (`factures.fournisseur_id` figé) alors que compta/Sage lisent `ocr_raw.saisie.fournisseur_id`. Fix Option A : nouveau `PaiementFournisseurResolver` (priorité `depot.fournisseur_id` → saisie → facture), **resync `factures.fournisseur_id` à la compta**, **relecture du VENDORID du Bill Sage avant APPYMT** (`ApBillVendorIdResolver`, flux WEB). Commande de rattrapage `factures:resync-fournisseur` (dry-run/--apply). Rattrapage exécuté (FAC-2026-040 : F1582→F1581 ; FAC-2026-020 : F1719→F3067).
- **#143 (`317843d` + fix `8ec5000`)** : 🐛 RIB FOURNISSEURS NE REMONTE PAS. La synchro lit bien `VENDORBANKFILEDETAIL` mais (1) **écrasait iban/bic à NULL** quand pas de données (destructeur — chaque synchro ratée effaçait les RIB existants), (2) rejetait `STATUS="T"` (liste blanche `{active,a}`). Fix : synchro **non destructive** (ne nullifie plus jamais ; préserve l'existant si fetch vide ; `bank_fetch_failed` + warning) + **observabilité** dans la réponse JSON (`bank_records_indexed`, `vendors_with_rib`) + **probe web** `GET /admin/intacct/probe-vendor-bank?VENDORID=X` (DSI). **#143-FIX (`8ec5000`)** : la probe a révélé que F1581 IDEM renvoie `STATUS="T"` (valide, IBAN/BIC présents) ignoré → nouveau `VendorBankDetailRowResolver` : ligne exploitable dès qu'IBAN/BIC présents, exclusion seulement des statuts explicitement inactifs (`inactive`/`i`/`false`/`f`/`0`/`no`/`n`), "T" accepté. Résultat : F1581 IDEM a iban `FR7616607003240812170774216` / bic `CCBPFRPPPPG`. CLÔTURÉ.
- **#145 (`baefa84`)** : 🐛 SEPA "aucun fournisseur ERP lié à ce VENDORID". `assertSepaVendorRib()` cherchait le RIB dans `demandes_fournisseurs` UNIQUEMENT, alors que #143 l'a mis dans `fournisseurs`. La grille affiche le RIB (lu de `fournisseurs`), mais le SEPA lit l'autre table → vide → échec (seul le SEPA exige une résolution RIB côté ERP, EFT/CB non). Fix Option A : nouveau `FournisseurPaiementPresenter::ribDepuisVendorId()` (chaîne partagée grille+SEPA : `fournisseurs.intacct_id` en priorité, fallback `demandes_fournisseurs`, échec explicite sinon). Dry-run FAC-2026-040 OK. CLÔTURÉ.
- **#146 (`a59a3b3`)** : 🐛 SEPA → ACH. Le mode "Fichier bancaire SEPA" envoyait `PAYMENTMETHOD=EFT` (constante `APPYMT_PAYMENT_METHOD_SEPA='EFT'`). Or **EFT = "Record Transfer"** (simple enregistrement), **ACH = paiement par fichier bancaire**. Doc Sage confirmée (PAYMENTMETHOD accepte Printed Check/Joint Check/Cash/EFT/Credit Card/ACH/Check Delivery/WF Check/WF Domestic ACH/WF USD Wire). Fix Option A minimal : constante **EFT→ACH** (`PaiementModeReglementMapper`, autres modes inchangés). Dry-run FAC-2026-042 confirme `<PAYMENTMETHOD>ACH</PAYMENTMETHOD>`. **SEPA testé OK en prod (frise NV2→Comptabilisée→Mise en paiement→Payée).** ⚠️ [DOC] La **génération du fichier de paiement bancaire reste une étape côté Sage** (Payment Manager / Bank Files), hors périmètre ERP : l'ERP "met en paiement" en ACH, Sage produit le fichier. Si Sage rejette ACH sur le tenant → #146-B (config tenant). CLÔTURÉ côté ERP.
- **#147 (`d2030fd`)** : MASQUER le bouton "Comptabiliser et payer" sur la page Comptabilisation. Flag `SHOW_COMPTABILISER_ET_PAYER=false` + prop `showComptabiliserEtPayer` (défaut true) sur `DepotFactureSaisieForm` (les autres écrans gardent le bouton). Réactivation = flag true + rebuild. Handler conservé.
- **#140** (auto-validation NV1, déposant=valideur NV1 → saut NV2) : **RÉSOLU et testé en prod** (géré par Robin, pas de synthèse vue par Claude). CLÔTURÉ.

### 🛠️ Module SUPER-ADMIN FACTURES (#148 A→D) — couteau suisse de rattrapage ERP↔Sage
> Réservé **GS_DSI + GS_ADMIN_ERP**. **PRINCIPE CARDINAL : l'outil n'écrit QUE dans l'ERP, JAMAIS dans Sage** (sauf probe lecture seule). Répare l'ERP pour refléter la réalité Sage déjà existante (ne remplace jamais une extourne / re-comptabilisation Sage).

- **Diag #148** : lier un Bill Sage proprement nécessite une écriture **COORDONNÉE** (pas juste `intacct_bill_id`) : `facture.intacct_bill_id` + `facture.intacct_record_no` (identiques) + `depot.intacct_bill_id` + statuts `comptabilisee` (facture+dépôt) + synchro via `FactureWorkflowService::synchroniserApresComptabilisationIntacct`. Sinon facture "comptabilisée" fantôme invisible en paiement. Découpage en 5 lots.
- **#148 Lot 1 (`924df53` + fix `00501f7`)** : page **`/admin/factures-superadmin`** (Index liste filtrable + Show fiche diagnostic état complet), action **"Lier un Bill Sage"** mono-fournisseur, **transaction atomique** (rollback total), **zéro écriture Sage**, audit via `facture_historique` action `correction_superadmin`, réutilise `FactureWorkflowService`. Probe optionnelle `probe-bill`. Refus : RECORDNO doublon, multi-fournisseur, paiements_intacct. Nouveaux : `FactureSuperAdminCorrectionService`, `FactureSuperAdminController`, `Admin/FacturesSuperadmin/Index.vue` + `Show.vue`. 🚨 **Fix 500 (`00501f7`)** : "Column 'facture_id' ambiguous" — le raccourci `with('depotFacture:facture_id,...')` sur la relation `latestOfMany` (INNER JOIN) rendait les colonnes ambiguës → colonnes qualifiées `depot_factures.*`.
- **#148-B (`6d0d90b`)** : GARDE-FOUS VALIDATION 2 niveaux. **Niveau 1 AVERTISSEMENT** (override confirmé + tracé) : `statut_desaligne`, `fournisseur_desaligne`, `montant_desaligne`, `bill_desaligne`, `bill_partiel`, `comptabilisee_sans_bill`, `statut_cible_inhabituel`, `workflow_validation_incomplete`, `workflow_compta_sans_paiement`. **Niveau 2 BLOCAGE DUR** (jamais d'override) : `depot_absent`, `multi_fournisseur`, `paiements_intacct` (initialement, voir #148-C), `fk_*` (fournisseur/entité/programme/marché inexistant), `recordno_format`, `recordno_doublon`. Override tracé dans l'audit (`avertissements_outrepasses`).
- **#148-C (`5b22b5b`)** : CHANGEMENT D'ÉTAPE WORKFLOW LIBRE + SUPPRESSION SOFT-DELETE. Capacité 1 = forcer n'importe quelle étape → n'importe quelle (`apercuChangementEtape`/`changerEtapeWorkflow`/`validerChangementEtape`, transaction atomique, champs requis remplis ex. `date_paiement` pour `payee`, synchro `FactureWorkflowService`, audit `changement_etape_superadmin`). Capacité 2 = suppression **soft-delete tracée** (réutilise `FactureSuppressionService::executerSuppressionCoupleeTransaction` #130, trace Sage active = avertissement N1 "Sage non touché", motif obligatoire, audit `suppression_superadmin`). **[DÉCISION] `paiements_intacct` rétrogradé en AVERTISSEMENT N1 partout** (liaison + changement d'étape + suppression) — cohérent avec "avertir mais permettre". Seul mur restant = intégrité FK + formats.
- **#148-D (`48bcefc`)** : AFFICHAGE "FORCÉ PAR SUPER-ADMIN" sur la fiche facture. Nouveau `FactureSuperAdminAuditPresenter` (lit `facture_historique`, filtre actions super-admin + avertissements outrepassés, mapping statut→étape frise). `FactureController::renderShowPage()` expose `superadmin_audit` + `workflow_view` enrichi (`superadmin_forced` par step). Front : `FactureSuperAdminAuditBanner.vue` (bandeau ambre + détail qui/quand/action/motif/avertissements) + `Show.vue` (étapes forcées = cercle ambre + bouclier + badge "Forcé" + tooltip). Factures normales inchangées.

### 🛡️ #149 (`b343b45`) — Race condition switch facture (compta) + garde-fou anti mauvaise-compta
> Bug INTERMITTENT : en switchant de facture dans la file `/factures/comptabilisation`, les données se mélangeaient ("c'est les données" → risque de mauvaise compta Sage).
- **Front** (`Index.vue`) : token de requête + **AbortController** par sélection ; garde UUID avant toute affectation du payload ; `:key` composite `${selectedUuid}-${payloadGeneration}` (force la recréation du composant) ; props `selectedComptaUuid` + `comptaPayloadLoading`.
- **Front** (`DepotFactureSaisieForm.vue`) : `comptaIdentiteAlignee` → bouton Comptabiliser **grisé** si `depot.uuid !== selectedComptaUuid` ou payload en chargement ; early-return dans `comptabiliserSeul()`/`comptabiliserEtPayer()`.
- **Back** (`ComptabilisationIdentiteGuard`) : **empreinte métier** (entite_id, fournisseur_id, programme_id, marche_id, montant_ttc tolérance 0,01 €) comparée à la facture/dépôt en base ; garde sur PATCH (update) et `envoyer-intacct-compta` → **422 `errors.identite` si croisement, RIEN envoyé à Sage**. ⚠️ Cursor avait sauté la phase diagnostic mais la correction couvre bien les 2 causes (race + state résiduel) + le garde-fou backend.

### 🔎 #150 (`f7bb3be`) — Filtre "À régler / Déjà réglée" sur Mise en paiement
- L'ancien filtre "Statut" filtrait des cas TECHNIQUES (`compte_manquant`, `multi_fournisseur`) sans lien avec le badge RÈGLEMENT → "valeurs bizarres". Remplacé par **Tous / À régler / Déjà réglée** sur le champ `deja_reglee` (le même que le badge). Backend `PaiementLignesFiltre` (`a_regler` → `deja_reglee=false`, `deja_reglee` → `true`). Autres filtres inchangés. ⚠️ **À VÉRIFIER au déploiement** : Cursor dit "pas de .vue modifié" — si le menu déroulant affiche encore les anciennes options, il manque le fix front (faire #150-FIX).

### 🔜 Suivis ouverts en fin de session 08/06
- **#144 — Synchro dimension Sage "Projets" (PAS commencé, diag fait, par lots)** : objet Sage `PROJECT`, table `intacct_projects` (calquée sur `intacct_classes`), synchro web `POST /admin/intacct/sync-projets`, page `/admin/projets`, FK `programmes.intacct_project_id`, injection `PROJECTID` au niveau **ligne APBILL** (déjà sérialisé par `ExtBillCreate::writeLineXml()`, jamais alimenté), APPYMT inchangé, `PROJECTID` **optionnel** (ne casse pas l'existant). Découpage 3 lots (1 référentiel+synchro+admin+probe / 2 association programme / 3 injection compta). **Reprendre par le Lot 1.**
- **#150** : vérifier que le menu déroulant affiche bien les nouvelles options (sinon #150-FIX front).
- **PROJECT.md** : tenu à jour jusqu'à `f7bb3be` (cette session).

### 🧠 Apprentissages / principes consolidés cette session
- **`enseigne_id` (applicatif) et `entite_id` (Sage) sont des axes INDÉPENDANTS** — ne jamais dériver l'un de l'autre ; le moteur comptable ne lit jamais `enseigne_id`.
- **Une seule source de vérité par donnée** : la série #142→#145 venait de RIB/fournisseur dispersés sur plusieurs tables (`fournisseurs`, `demandes_fournisseurs`, `ocr_raw.saisie`, Bill Sage). Toujours faire converger grille + validation + envoi vers la MÊME source.
- **Une probe web lecture seule** (calquée à chaque besoin Sage) tranche les diagnostics Intacct en direct (ex. #143 : "STATUS=T ignoré" révélé en une requête). À systématiser.
- **EFT ≠ ACH dans Sage** : EFT = enregistrement ("Record Transfer"), ACH = paiement par fichier bancaire. La génération du fichier SEPA reste côté Sage.
- **Super-admin = ERP-only** : réparer l'ERP pour refléter Sage, jamais écrire dans Sage. Écriture coordonnée + transaction atomique + audit + 2 niveaux de garde-fou (avertissement override / blocage dur FK).
- **Bug intermittent au switch = race condition async** (réponses dans le désordre) : la réinitialisation du state seule ne suffit pas, il faut AbortController/token + `:key` + garde-fou backend (empreinte métier) sur les sujets compta.
- ⚠️ Rappel OVH renforcé : Cursor propose régulièrement `config:cache`/`route:cache` dans ses séquences de déploiement → **NE JAMAIS les exécuter** (casse cluster113). Routine = `optimize:clear` + `view:clear` (+ `route:clear` si routes, + `migrate --force` si migration).

---

### 🔜 Reste à faire / TODO ouverts en fin de session 03/06
- **Synchro référentiel : désactiver les entités SUPPRIMÉES dans Sage** (#081 préparé, **PAS encore fait** — décision : le faire proprement via l'app, pas en one-shot). Cas concret : carte Robin supprimée+recréée dans Sage → **doublon fantôme** en base ("8613 - CB ROBIN" compte 2887 vs "CB 8613 - ROBIN" compte 2888). La synchro fait un upsert sans retirer les disparues. Fix prévu : marquer `inactive` les entités absentes du retour Sage COMPLET (garde-fou : ne rien désactiver si retour partiel/vide).
- **Alertes "facture à comptabiliser"** : doivent cibler **GS_COMPTABILITE** (pas tous les détenteurs du droit dont le DSI), avec logique **tâche partagée** (si l'un comptabilise, l'alerte disparaît pour tous → idéalement notification basée sur l'état métier de la facture, pas une copie par utilisateur).
- **Mémorisation des réglages par fournisseur à l'OCR** (nouveau chantier) : pré-remplir fournisseur/catégorie/classe/compte/mode/établissement à partir des saisies précédentes du même fournisseur (Amazon, SOCOTEC…). Gain de temps sur les récurrents. Identifier sur SIRET/TVA/nom OCR. Tout reste modifiable.
- **Nettoyer dans Sage les bills/paiements de test** (31083/31084 extournés, et divers bills/paiements de test des sessions).
- **Retirer l'endpoint probe temporaire** `/admin/intacct/probe-financial-entities` (+ probes diag/apbill-states) après validation.

### Notes techniques permanentes ajoutées cette session
- **`statut_intacct` sur `facture_ventilations` est un ENUM NOT NULL** (`en_attente`/`envoye`/`erreur`, défaut `en_attente`) — ne JAMAIS le passer à NULL (cause crash, cf. #065). `intacct_bill_id` est nullable.
- **`paiements_intacct`** : `intacct_payment_id`, `intacct_bill_id`, `montant`, `date`, `mode` — trace ERP du paiement.
- **Deux notions de "compte" à ne pas confondre** : compte de **charge** (604/615…, comptabilisation, dans `lignes_comptables`) vs compte **bancaire de paiement** (512…/CARDID, le FINANCIALENTITY de l'APPYMT). Le compte de paiement n'est PAS dans l'APBILL.
- **Société vs établissement Intacct** : société = entité (E-04/E-13), établissement = location (MTP00…) rattachée via `etablissements.parent_id`. Les moyens de paiement (cartes/comptes) se filtrent par **société**.
- **⚠️ Cursor a re-suggéré `npm run build` sur OVH plusieurs fois cette session** : à IGNORER systématiquement (assets commités, build fait avant push). Travail en parallèle (#082/#083/#084) → push rejetés/rebasés : toujours vérifier `git log --oneline origin/main` avant de déployer.

---

## Sous-traitants par marché (livré 29 mai 2026)

### Modèle
- Table `marche_sous_traitants`

### UI
- Nouvel onglet **SOUS-TRAITANTS** dans la fiche marché
- Champs : sous-traitant (FK fournisseurs Intacct), montant HT sous-traité, autoliquidation TVA (oui/non)

### Intégration factures
- Sous-traitants exploités par le module **Multi-fournisseur sur factures** (cf. section ci-dessus)

---

## Retenues Finitions et OPC (livré 29 mai 2026)

- Nouvelles cases à cocher sur la facture : **Finitions** et **OPC**
- Compte par défaut **Finitions** : `401400`
- Compte par défaut **OPC** : `401500`
- Settings configurables dans **Admin > Comptabilité**
- Intègrent la formule de reconstitution TTC brut (cf. Logique métier factures)

---

## Module Marketing META (livré 29 mai 2026)

> Dashboard de pilotage des campagnes Meta Ads, exposé aux équipes commerciales et communication.

### Navigation
- Menu principal **MARKETING** (entre ENVOL et SUIVI DEV) avec sous-menu **META**

### Dashboard META
- Sélecteur de période : mois / semaine / trimestre / année / personnalisée
- **KPIs** : campagnes actives, total prospects, taux de conversion
- **Top 3** campagnes par leads générés
- **Courbe d'évolution** des leads (bucket jour / semaine selon période)
- **Derniers leads** : 5 à 10 dernières entrées

### Permissions (catégorie Marketing)
- `marketing.lecture`
- `marketing.gestion`
- Nouveau groupe M365 : **`GS_COMMUNICATION`**

---

## Picto Permissions feu tricolore (livré 29 mai 2026)

- **Feu tricolore** affiché en haut à droite de l'application
- Visible **uniquement** pour `GS_DSI` et `GS_ADMIN_ERP`
- Tooltip détaillé : clé de permission courante + groupes M365 autorisés par niveau
- Outil de debug visuel pour identifier rapidement les blocages de droits en prod

### Règles de résolution `PermissionService`
- **`GS_DSI`** : wildcard total (accès à tout, y compris `admin.systeme` et `admin.maintenance`)
- **`GS_ADMIN_ERP`** : tout **sauf** `admin.systeme` et `admin.maintenance`

---

## Catégories frais généraux (livré 29 mai 2026)

- Page **`/admin/categories-frais-generaux`** : gestion des catégories de frais généraux
- Permissions : `admin.categories_frais_generaux.lecture` / `admin.categories_frais_generaux.ecriture`
- Accès ouvert à `GS_COMPTABILITE` (en plus de `GS_DSI` et `GS_ADMIN_ERP`)

---

## Décisions métier prises — chantiers à venir (mai 2026)

> Décisions actées avec Robin, à implémenter dans les sprints suivants.

### Dérogation PMR (TMA)
- Toggle **"Dérogation PMR"** sur la fiche TMA
- Si activé : génération d'un PDF de dérogation destiné à l'acquéreur (signature électronique prévue en phase 2) + bureau de contrôle
- Bureau de contrôle = **FK fournisseurs Intacct**, sélectionnable dans les paramètres du programme
- Nouvelle bibliothèque **"Réversibilité"** (libellé UI), table technique `prestations_reversibilite_pmr` : `{libellé, texte paragraphe PDF}`
- Accès via bouton dans l'onglet TMA de Service client
- Permission : `GS_ENVOL`
- Stockage du PDF : SharePoint, dossier acquéreur
- **Bloqueur** : attente d'un exemple PDF fourni par Robin pour caler la structure de base commune

### Facturation TMA
- Toggle `facturation_tma_via_envol` sur `programmes` (défaut **`true`**)
  - `true` : RIB Envol fixe (table `parametres` globale, modifiable via `/admin`)
  - `false` : RIB du programme
- Numérotation **TMA** : `TMA-{code_societe}-{annee}-{seq}` côté ERP + `FAC-XXX` Sage
- **Même pattern asynchrone** que les AdF VEFA (numero_interne ERP + numero_facture Intacct)
- `CLASSID` Intacct : code analytique du programme (`PRG-{dpt}-{code}`)
- Compte produit Intacct TMA : paramétrable via `/admin` (défaut **`707201`** — nomenclature Flora)

### Trésorerie (refonte complète, à reprendre)
- Repartir de la **structure du bilan** (postes par rubrique)
- Colonnes : `Poste | Étude fi | Reste à payer | M1 | M2 | ...` (à partir du mois de la 1ère facture rentrée)
- `Reste à payer = (marchés + avenants signés) − factures validées`
- **TTC partout**
- 2 filtres : masquer postes à 0 + masquer postes 100% payés
- **Recettes commerciales** :
  - Lot **acté** : chronologie réelle + % stades VEFA + 30j d'encaissement
  - Lot **réservé** : date prévisionnelle d'acte + chronologie prévisionnelle + 30j
  - Lot **disponible** : date objectif d'acte + idem
- **Inconnues à trancher** :
  - périmètre "déjà dépensé" (validé vs payé)
  - étalement des dépenses futures
  - date de référence
  - TVA poste par poste
  - source "date objectif acte"
  - supprimer ou garder l'existant

---

## Manuel utilisateur (en cours)

> Documentation utilisateur de l'ERP, à destination des équipes Hectare.

### Phase 1 — Rédaction Markdown
- Rédaction structurée par modules métier
- Géré sur un onglet Claude dédié

### Phase 2 — Intégration dans l'ERP (via Cursor)
- Route `/aide` accessible depuis l'ERP
- Moteur de recherche full-text dans la doc
- Chatbot IA pour répondre aux questions utilisateurs en s'appuyant sur le manuel

---

## Organisation des sessions Claude

- **3 onglets Claude en parallèle** :
  1. **Dev** — chef d'orchestre des sessions de développement (Cursor en local)
  2. **Manuel** — rédaction du manuel utilisateur
  3. **PROJECT.md** — maintenance de ce fichier de contexte
- **Claude Code** adopté en complément de Cursor depuis le 26/05/2026 pour les sessions de debug intensives (lecture/écriture directe du repo, exécution shell, moins d'allers-retours sur les chantiers complexes type Intacct)
- Licence **Claude MAX** active : marge confortable sur la longueur des conversations

---

## En cours

> _(Mettre à jour à chaque session)_

- [x] **🏷️ Multi-enseigne SUIVI A — socle déployé (05-08/06/2026, `d0532b0`)** : table `enseignes` (ENVOL/HECTARE), deux axes indépendants `enseigne_id` (applicatif) / `entite_id` (Sage), backfill Envol, fix MySQL 1830, sélecteur enseigne à la création (préfixe ENV/HEC), `/admin/enseignes` lecture seule. Moteur compta ne lit jamais `enseigne_id`.
- [ ] **🏷️ Multi-enseigne SUIVI B/C/D** : B = filtre enseigne partout + unicité composite rubriques ; C = `HectareNomenclatureSeeder` (11 rubriques A→K, codes `H_`) ; D = frises par enseigne. Ordre imposé A→B→C→D. Chantiers séparés : GED Hectare, commercialisation terrain, recettes terrain bilan.
- [x] **💸 Série bugs paiement/fournisseur (08/06/2026)** : #138 workflow re-validation, #139 édition fournisseur marché, #142 fournisseur figé en paiement, #143(+FIX) RIB synchro non destructive + STATUS=T, #145 SEPA résolution RIB, #146 SEPA→ACH. SEPA testé OK en prod.
- [x] **🛠️ Module super-admin factures (#148 A→D, 08/06/2026, `48bcefc`)** : `/admin/factures-superadmin`, lier un Bill Sage manuellement, garde-fous 2 niveaux (avertissement/override + blocage dur FK), changement d'étape libre, suppression soft-delete, affichage "forcé par super-admin". ZÉRO écriture Sage.
- [x] **🛡️ Race condition switch facture compta (#149, 08/06/2026, `b343b45`)** : AbortController + `:key` + garde-fou backend `ComptabilisationIdentiteGuard` (422 si empreinte croisée, rien envoyé à Sage).
- [x] **🔎 Filtre À régler/Déjà réglée sur Mise en paiement (#150, `f7bb3be`)** — ⚠️ vérifier que le menu déroulant affiche bien les nouvelles options (sinon #150-FIX front).
- [ ] **🗂️ #144 — Synchro dimension Sage "Projets" (diag fait, à faire par lots)** : `intacct_projects` + synchro web + `/admin/projets` + FK `programmes.intacct_project_id` + injection `PROJECTID` ligne APBILL (déjà sérialisé, jamais alimenté) + probe. `PROJECTID` optionnel. Reprendre par le Lot 1.
- [x] Module Capture Leads Meta Ads en production
- [x] Refonte politique emails (2 actifs / reste désactivé)
- [x] Édition inline commercial (fiche lead + codes campagnes)
- [x] Provisioning auto users M365 préapprouvés
- [x] Spec module Appels de Fonds validée + mockup PDF validé
- [x] **Sprints 1-6 module Appels de Fonds VEFA déployés en prod (23/05/2026)**
  - Sprint 1 : coordonnées société & bancaires (programmes)
  - Sprint 2 : stades d'avancement VEFA (table + UI drag & drop)
  - Sprint 3 : génération AdF + numérotation AF-{code}-{annee}-{seq}
  - Sprint 4 : PDF charte Envol (template Blade)
  - Sprint 5 : workflow Intacct asynchrone (numero_interne + numero_facture, retries, dashboard erreurs)
  - Sprint 6 : UI génération batch + polling + envoi acquéreur désactivé en rodage
- [x] Bug chronologie programme — correctif finalisé et déployé en prod le 23/05/2026 (commit `7e56d2b`) : layout tableau inline pleine largeur + barre fixe Enregistrer + classe `chronologie-tab-bleed` pour bypass `max-w-6xl` — fichier `resources/js/Components/ProgrammeChronologieEtapesPanel.vue`
- [x] **Système de droits par fonctionnalité livré (24/05/2026)** — features_permissions + UI /admin/droits + helper userCan() + middleware feature.permission + cache 5 min
- [x] **Dashboard widgets personnalisables livré (24/05/2026)** — 10 widgets, drag & drop, layout par user, cache 5 min, bug boucle infinie résolu (layoutSignature)
- [x] **Refonte page /admin livrée (24/05/2026)** — onglets par catégorie + header (toggle emails + synchro Intacct)
- [x] Bug AdminController résolu (import `use App\Http\Controllers\Admin\AdminController` manquant)
- [x] **Refonte UX Service client (25/05/2026, commit `44dde03`)** — TMA + AdF regroupés, sous-onglets avec deep-link, redirection 301
- [x] **Liaison Stades VEFA ↔ Chronologie (25/05/2026, commit `23b9252`)** — FK informationnelle nullable
- [x] **Module Bibliothèque TMA (25/05/2026, commit `641c2f7`)** — catalogue global, overrides programme, lignes snapshotées, coefficient ×1.8
- [x] **Hotfix création TMA (25/05/2026, commit `11cfac5`)** — empty state lots (`reserve` + `acte`) + format select Marché
- [x] **Switch programme depuis breadcrumb (25/05/2026, commit `4284193`)** — endpoint + service + cache 5 min — ⚠️ **BUG Ziggy en prod, fix en cours**
- [x] **Refonte Factures en sous-onglet Suivi Chantier (25/05/2026, commit `6981623`)** — hash deep-link + compat `?tab=`
- [x] **Module Meta : webhook restauré (sprint 23-26/05/2026)** — token renouvelé + abonnement page `570810992938193` rétabli, **27 leads manqués rejoués** via `/admin/meta/replay-leads`
- [x] **Refonte comptabilisation Intacct (26/05/2026)** — `BillCreate` → `ExtBillCreate` (SDK natif), retrait `setBaseCurrency` / `setTransactionCurrency`, fallback `query` + `readByQuery`, SDK custom restauré dans `vendor/intacct`
- [x] **Health check Meta poussé (26/05/2026, commit `cb14a70` + suivants)** — job horaire + widget dashboard + bannière globale + page `/admin/meta/health`
- [x] **Logique métier factures DÉFINITIVE validée (29/05/2026)** — reconstitution TTC brut sans pénalités, écritures détaillées par compte
- [x] **Multi-fournisseur sur factures (29/05/2026)** — N envois APBILL séparés, retenues sur principal, autoliquidation TVA sous-traitants
- [x] **Sous-traitants par marché (29/05/2026)** — table `marche_sous_traitants` + onglet fiche marché
- [x] **Retenues Finitions / OPC (29/05/2026)** — comptes `401400` / `401500` configurables Admin > Comptabilité
- [x] **Module Marketing META (29/05/2026)** — menu + dashboard + permissions + groupe `GS_COMMUNICATION`
- [x] **Picto Permissions feu tricolore (29/05/2026)** — visible `GS_DSI` / `GS_ADMIN_ERP`
- [x] **Page /admin/categories-frais-generaux (29/05/2026)** — accessible `GS_COMPTABILITE`
- [x] Bugs résolus 29/05 :
  - Dropdown comptes GL APBILL vide après synchro Intacct
  - Dropdown Programmes incomplet sur formulaire facture
  - Compte GL hors sync Intacct → badge orange + persistance
- [x] **Pénalités factures — règle CORRIGÉE et validée en prod (01/06/2026)** : pénalité éclatée HT+TVA dans charge/`445660` + ligne crédit `401400` dédiée ; hors base de retenue (voir Logique métier factures)
- [x] **Comptabilisation Intacct multi-fournisseur — VALIDÉE DE BOUT EN BOUT en prod (01/06/2026)** : taxe (TAXENTRY exonérée sur `401`), CLASSID (`intacct_class_analytics_id`), ENUM statuts (`depot_factures` + `factures`), RG/CP corrects (base globale + ST autoliq notionnel 20%), persistance `bill_id` avant statut
- [x] **🛡️ Garde-fou anti-doublon APBILL — livré et validé prod (02/06/2026, commits `d29c749`+`111baf2`)** : pré-check `findExistingApBillRecordNo` (clé `entity+VENDORID+RECORDID`) avant chaque envoi, fonction unique pré-check + fallback, testable via toggle sur `/admin/intacct/test-bill`. Bypass `force_apbill_create`.
- [x] **🔀 Séparation Compta / Paiement — 2 boutons livrés prod (03/06/2026, commit `c338fef`)** : "Comptabiliser" (APBILL seul → file paiement) vs "Comptabiliser et payer" (popup + dry-run → APPYMT). Retrait du paiement implicite des "déjà réglées".
- [x] **🧾 Rejeter une facture à la comptabilisation (03/06/2026, commit `f698fa7`)** : statut `rejetee`, motif obligatoire, notif au validateur (cascade nv2→nv1→dépôt), sort des files.
- [x] **🛠️ Commandes de maintenance factures (03/06/2026)** : `factures:rattrapage-file-paiement`, `factures:decomptabiliser`, `factures:annuler-paiement`, `erp:sync-comptes-cartes-comptables`, `erp:diag-cartes-compta` (toutes dry-run + `--apply`). Bouton UI "Annuler la comptabilisation" (`de549e3`).
- [x] **🛡️ Anti-doublon APBILL ignore les bills EXTOURNÉS (03/06/2026, commit `fd381a4`)** : états `Reversed`/`Reversal` ignorés (débloque la re-compta d'une facture extournée), bills actifs toujours bloqués.
- [x] **🔁 Détection doublon AU DÉPÔT resserrée (03/06/2026, commit `99ad336`)** : fournisseur + numéro de facture (≠ anti-doublon APBILL).
- [x] **📄 Nom de fichier lisible (03/06/2026, commit `4f2ae65`)** : `nom_affichage` cosmétique (`FOURNISSEUR_NUMERO_DATE.pdf`), zéro renommage physique, le nom ne sert PAS d'identifiant (uuid stable).
- [x] **👁️ Fix bouton œil liste Comptabilité → fiche facture (03/06/2026, commit `f5c511c`)** : corrige la régression de `2dce68f`.
- [x] **📄 Fiche facture Frais Généraux + workflow (03/06/2026, `6858253`/`4bab410`/`676b0cf`)** : route `/factures/fiche/{facture}`, œil FG → fiche, validation d'étape FG (POST `/factures/fiche/{id}/valider|rejeter`). ⚠️ `route:clear` requis au déploiement.
- [x] **🏦 Compte bancaire compta depuis référentiel Intacct (03/06/2026, `05b200b`)** : mode non-CB → comptes `intacct_financial_entities` (BANKACCOUNTID) filtrés société ; commande `erp:sync-comptes-banques-comptables` (266 comptes).
- [x] **🪟 Popup "Ouvrir le doublon" en modal (03/06/2026, `c32aa57`)** : comparaison sans quitter la saisie.
- [x] **🚨 Fix crise 500 compta — `FactureHistoriqueSaisie` sans `use` (03/06/2026, `79c61d7`)** : `App\Support\FactureHistoriqueSaisie` non importé dans `DepotFactureController` → 500 bloquant. Diagnostic via logs Laravel. ⚠️ Tests sqlite skipped n'ont rien détecté.
- [x] **🔁 Doublon dépôt v2 : numéro + montant TTC strict (03/06/2026, `1968281`)** : fournisseur ignoré (pas toujours dispo à l'OCR). + Validateur par défaut = déposant.
- [x] **🔧 Contrôle charge poste hors dépôt (04/06/2026, `2b78309`)** : contrôle "charge du poste budgétaire" retiré du dépôt, conservé à la compta (là où "Générer les écritures" existe). Faux positif "ligne vide" corrigé (`LignesComptablesLigneHelper`).
- [x] **🧠 Pré-remplissage réglages fournisseur à l'OCR (04/06/2026, `47abb6a`)** : catégorie/classe/mode/établissement/validateur repris de la dernière facture du fournisseur. Lignes comptables EXCLUES. Critère fournisseur_id > siret.
- [x] **🚫 Statuts "Bloqué NV1/NV2" informatifs (04/06/2026, `452fb95`)** : bouton Bloquer + motif obligatoire, badge visible, valider depuis bloqué avec confirmation. La facture reste au même niveau workflow.
- [x] **🔧 Compta facture de marché : compte via poste + TVA (04/06/2026, `2f6e852`)** : `ProgrammeChargeAccountResolver` (marché → poste budgétaire → compte 331453) + écriture HT/TVA déductible/401 équilibrée. Résout "poste introuvable" + écriture déséquilibrée.
- [x] **👤 Frise workflow : validateur + date sur NV1 (04/06/2026, `df6d2a6`)** : la validation auto NV1 (déposant = validateur) crée la ligne NV1 en base + mention "(automatique)" dans la frise.
- [x] **🏢 Colonne + filtre Société sur la liste factures (04/06/2026, `62809f6`)** : "E-XX — nom" triable + filtre, onglets Programmes + FG (`SocieteEntiteResolver`).
- [x] **🏦 RIB fournisseur en Mise en paiement (04/06/2026, `7ae0e24` + fix `816a39a`)** : "FXXXX — NOM" + RIB/IBAN dessous ; colonnes iban/bic + synchro Intacct via endpoint web. #101 corrige un 500 (redéclaration `IntacctService::readByQueryPaginated`).
- [x] **🎛️ Mise en paiement : colonnes redimensionnables + filtres + modif groupée (04/06/2026, `4c54f65`)** : largeurs ajustables, bloc filtres, modification groupée (compte/mode/date) avec aperçu + confirmation.
- [x] **🔧 Bloc filtres paiement compacté + triangle valideur + renommage "Valideur" (04/06/2026, `9ad89e1`)** : #103 filtres sur une ligne ; #104 triangle d'alerte corrigé (valideur effectif) + "Validateur"→"Valideur".
- [x] **↩️ Annuler la comptabilisation par ligne en paiement (04/06/2026, `ded708c` + fix `f38e33c`)** : même endpoint + même avertissement (RECORDNO) que la fiche. #107 corrige le dialog vide.
- [x] **📋 Menu Comptabilité : "Toutes les factures" en 1er (04/06/2026, `6a55a8b`)**.
- [x] **🧾 Frise "Comptabilisée" : date + qui + écritures au survol (04/06/2026, `0c4d12d`)** : date/auteur de compta + tooltip écritures (compte/débit/crédit) + RECORDNO. Migration ENUM workflow_validations.
- [x] **🧹 Fiche facture : en-tête nettoyé + Retour aux factures (04/06/2026, `421ff83` + `192c893`)** : #109 titre = n° fournisseur, 1 seul Annuler, dates, entité avant fournisseur ; #111 fils d'ariane → bouton "Retour aux factures".
- [x] **🔀 Workflow Frais Généraux à un seul niveau NV1 (04/06/2026, `dd17850`)** : FG (programme_id null) → NV1 puis directement a_comptabiliser, pas de NV2 ; programme inchangé. Nouvelles FG seulement.
- [x] **💶 Mise en paiement : colonne règlement + SEPA bloqué si déjà réglée (04/06/2026, `02750a9`)** : badges Déjà réglée/À régler + SEPA grisé (ligne + modif groupée).
- [x] **🔵 Bouton "Comptabiliser" bleu dans la barre d'actions (04/06/2026, `b096e69`)** : déplacé à côté Modifier/Supprimer, bouton vert isolé supprimé.
- [x] **🎨 Boutons workflow regroupés dans la barre + Modifier bleu (04/06/2026, `b1f557e`)** : Valider/Bloquer/Rejeter alignés avec Modifier(bleu)/Supprimer ; couleurs métier conservées.
- [x] **✅ Test bout-en-bout Amazon (FAC-2026-023) VALIDÉ (03/06/2026)** : dépôt → compta → paiement APPYMT réel (mode CB, date 17/04/2026). Chaîne complète OK sur cas réel.
- [ ] **💳🗑️ Synchro référentiel : désactiver les entités SUPPRIMÉES dans Sage** (#081 préparé, PAS fait — à faire proprement via l'app) : cas carte Robin fantôme (doublon "8613 - CB ROBIN" vs "CB 8613 - ROBIN"). Marquer `inactive` les absentes du retour Sage COMPLET (garde-fou si retour partiel).
- [ ] **🔔 Alertes "facture à comptabiliser"** : cibler GS_COMPTABILITE (pas le DSI) + logique tâche partagée (si l'un comptabilise → disparaît pour tous, basé sur l'état métier).
- [ ] **🧠 Mémorisation des réglages par fournisseur à l'OCR** : pré-remplir fournisseur/catégorie/classe/compte/mode/établissement depuis les saisies précédentes du même fournisseur (récurrents Amazon/SOCOTEC). Identifier sur SIRET/TVA/nom OCR.
- [ ] **🧹 Nettoyer les bills/paiements de test dans Sage** (31083/31084 extournés + divers tests des sessions).
- [ ] **Comptabilisation Intacct des AVOIRS (APBILL négatif)** : saisie + moteur + affichage OK (montants miroir négatifs), mais l'envoi d'un APBILL entièrement négatif vers Intacct n'est pas branché. POC à faire via `/admin/intacct/test-bill`.
- [x] **💳 Mise en paiement Intacct (APPYMT) — LIVRÉE & VALIDÉE EN PROD (03/06/2026)** : moteur APPYMT validé (virement EFT + carte Credit Card, paiements réels Sage), table `intacct_financial_entities` (272 entités), page dédiée `/factures/mise-en-paiement` (B1/B2/B3, édition inline, dry-run par défaut), défauts repris de la compta (mode+date+carte), classe/compte FG, popup écritures, colonne montant fusionnée. HEAD `f5c511c`. ⚠️ Règle clé : APPYMT solde le 401 sans décaissement réel SAUF SEPA. (voir section "💳 Mise en paiement Intacct — session 03/06/2026")
- [ ] **🔗 Lien facture FRAIS GÉNÉRAUX → DEVIS** : une FG n'a ni marché ni programme ; elle doit être reliée à un devis (modèle/table à créer).
- [ ] **📋 Inventaire des retenues/cautions** : audit métier repéré sur PDF de situation (RG, retenue sur montant non garanti, CP, finitions, OPC, CIE, pénalités, cautions bancaires 01/02, montant cautionné/non cautionné). Certains non câblés. Trame tableau (type / compte / base / taux / câblé / payé) à remplir.
- [ ] **Renseigner les comptes des 23 postes budgétaires sans compte** (admin nomenclature) : sinon les 3 marchés concernés se bloquent à la compta (#028). Comptes "évidents" : honoraires→622x, frais financiers→66x, publicité→623x, mécénat→6238, géomètre→6226.
- [ ] **Retirer l'endpoint probe temporaire** `/admin/intacct/probe-financial-entities` après validation (ou `INTACCT_FINANCIAL_ENTITIES_PROBE_ENABLED=false`).
- [ ] **Supprimer les fichiers parasites `PROJEC1T.md` / `PROJECT4.md`** non suivis dans le repo.
- [x] **Compte de charge via poste budgétaire — livré prod (02/06/2026, commit `86c1f2c`)** : remplace `604000` en dur. ⚠️ dimension `prestations` SUPPRIMÉE — chaîne réelle = marché → `poste_budgetaire_type` → `compte_comptable_id` → `accountno`. Blocage strict si poste sans compte (pas de fallback), borné `destination=PROGRAMMES`. 23/42 postes sans compte mais 3 marchés impactés.
- [x] **Synchro workflow à la compta — livré prod (02/06/2026, commit `7c83a3a`)** : `FactureWorkflowService` idempotent (mise_paiement→valide + crée paiement_effectif), même transaction que l'update statut. Corrige le bandeau bloqué à "À comptabiliser".
- [x] **Cohérence dépôt↔facture FG + routage — livré prod (02/06/2026, commit `2dce68f`)** : FG ⇒ programme_id/marche_id NULL (dépôt ET facture) ; routage front basé sur le vrai programme_id de la facture ; sinon `/depot-factures/{uuid}`. Facture FG reliée à un DEVIS (lien à créer).
- [ ] **Cosmétique** : la ligne `401400` d'une pénalité s'affiche "Finitions" dans Sage (même compte `401400`, heuristique de classement par montant). Sans impact comptable.
- [ ] **🐛 Fix bug Ziggy switch programme** : remplacer `route('api.dashboard.programmes-switcher')` par URL directe dans `AppShell.vue` + audit du reste de la session
- [ ] **Bibliothèque Réversibilité + bureau de contrôle programme** (prérequis Dérogation PMR)
- [ ] **Dérogation PMR : génération PDF** (en attente exemple PDF fourni par Robin)
- [ ] **Facturation TMA + workflow Intacct asynchrone** (TMA-XXX + FAC-XXX, même pattern que AdF VEFA)
- [ ] **Trésorerie : refonte complète** (structure du bilan, colonnes mensuelles, recettes par statut de lot) — plusieurs inconnues à trancher
- [ ] **Tests en prod du module Appels de Fonds (Sprints 1-6 déployés mais toujours pas testés)**
- [ ] Logo + pattern PNG à uploader dans `resources/assets/` (bloque la génération propre des PDF AdF)
- [ ] Validation acompte à l'acte avec compta avant ajout
- [ ] Migration progressive du code existant vers le helper `userCan()` (au fil des évolutions)
- [ ] **Ticket OVH ouvert** : limite upload 128M (bloque les AO > 350 Mo)
- [ ] Manuel utilisateur (onglet dédié, phase 1 Markdown)
- [ ] Multi-tenant Meta leads (4 sociétés HECTARE/Envol/Gemme/Les Balcons de la Cité)
- [ ] Module CIE
- [ ] Module Juridique/Contentieux
- [ ] **💡 IDÉE À CADRER — Macarons / tampons de workflow sur le PDF de la facture** (note 04/06, à discuter) : apposer des tampons matérialisant le workflow de validation directement sur le PDF (ex. "Validé NV1 par X le JJ/MM", "Comptabilisé", "Bon à payer"...). Points à trancher avant tout dev : (a) **PDF incrusté physiquement** (nouveau fichier tamponné généré/stocké, lib PHP type FPDI/TCPDF) **vs overlay d'affichage** dans le viewer ERP (sans toucher le fichier) ; (b) **quand** générer (à chaque étape franchie / à la demande) ; (c) **contenu** du macaron (étape + nom + date ? QR/référence ?) ; (d) le PDF tamponné **part-il dans Sage** (pièce jointe APBILL) ou reste-t-il dans l'ERP ; (e) contraintes OVH (pas de sortie HTTPS en CLI). NON validé — idée de Robin pour traçabilité.

---

## Comment utiliser ce fichier

1. **Début de session** : coller ce fichier entier dans le chat Claude
2. **Pendant la session** : travailler sur un module ou une fonctionnalité précise
3. **Fin de session** : demander à Claude de mettre à jour ce fichier avec les avancées
4. **IDE recommandé** : Cursor (lit toute la codebase, garde le contexte technique)
