# PROJECT.md — ERP Immobilier Interne (Hectare Groupe)

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

---

## Contexte général

- **Type** : ERP métier interne (pas un produit à vendre)
- **Secteur** : Promotion immobilière (Envol uniquement)
- **Objectif** : Remplacer Pegao (2 400€/mois)
- **Équipe dev** : DSI seul, ancien développeur web (HTML, PHP, JS)
- **URL production** : https://envol.hectare.fr
- **Repo** : HECTAREG/erp-immo (GitHub privé)

---

## Stack technique

| Couche | Technologie |
|---|---|
| Backend | Laravel 13 (PHP 8.3) |
| Base de données | MySQL (OVH mutualisé) |
| 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é (France) |
| IA intégrée | API Claude (Anthropic) — claude-sonnet-4-20250514 |
| Versioning | GitHub (repo privé : HECTAREG/erp-immo) |
| IDE | Cursor (Auto-run = Run Everything) |
| SDK Intacct | intacct/intacct-sdk-php (dev-master) |
| Graphiques | ApexCharts (vue3-apexcharts, enregistré globalement dans app.js) |

---

## ⚠️ PROCÉDURE DE DÉPLOIEMENT — ABSOLUE

**npm et composer NE SONT PAS disponibles sur OVH mutualisé.**
**vendor/ est committé dans le repo.**

**Local (Windows PowerShell)**
```powershell
cd C:\erp-immo
npm run build
git add -f public/build
git add .
git commit -m "feat/fix: description"
git push origin main
```

**SSH OVH**
```bash
ssh -o StrictHostKeyChecking=no hectare-envol@ssh.cluster113.hosting.ovh.net
cd /home/hectare-envol/envol.hectare.fr
git pull origin main
php artisan migrate --force
php artisan config:clear
php artisan cache:clear
```

**Si migration bloquée (Nothing to migrate mais Pending) :**
```bash
php artisan migrate --path=database/migrations/NOM_MIGRATION.php --force
```

---

## Gestion des droits — Groupes M365

```
GS_ADMIN_ERP          → accès total (Gate::before = true)
GS_COMPTABILITE       → comptables + validation fournisseurs
GS_DIRECTION_GENERALE → lecture totale + modification factures
GS_DSI                → administration + comptabilisation
GS_ENVOL              → utilisateurs standard
```

- Colonne BDD : **`m365_groups`** (pas `groupes_m365`)
- `isAdminERP()` sur modèle User → court-circuite tous les checks
- `Gate::before()` dans AppServiceProvider
- Toggle emails depuis `/admin` → `settings.emails_enabled`
- **emailsEnabled()** : ne jamais appeler depuis middleware → inline DB::table('settings')

---

## Intégration Sage Intacct ✅

- APBILL via SDK `intacct/intacct-sdk-php`
- Logs : `storage/logs/intacct.log` (ne jamais logger le XML brut — credentials en clair)

### Pièges critiques
- TAXSOLUTIONID = `'TVA française - SYS'` obligatoire
- taxentries sur chaque ligne HT (pas de ligne 445xxx)
- setVendorId() sur chaque BillLineCreate
- Pas de RECORDID
- SUPDOC ID max 20 chars
- SSL local : `$clientConfig->setSslVerify(false)` si `!app()->isProduction()`
- Mapping PAYMENTMETHOD : "Carte de crédit" → "Credit Card", "Virement" → "EFT"

### Comptes comptables (configurables /admin)
```
401600 → CIE | 401700 → RG | 401800 → CP | 419000 → Acomptes clients
```

### Sync paiements Intacct
- Par classe analytique (défaut) ou par entité (`intacct_sync_par_entite = true` sur programme)

### Jobs sync
| Job | Fréquence |
|---|---|
| SyncFournisseursDepuisIntacct | nuit 02:00 |
| SyncEntitesDepuisIntacct | nuit 01:30 |
| SyncClassesDepuisIntacct | nuit 02:30 |
| RecupererPaiementsIntacct | 2x/jour 10h et 16h |

---

## Navigation

**Menu principal** : ACCUEIL | COMPTABILITÉ ▾ | ANNUAIRE | ENVOL ▾

**Menu COMPTABILITÉ** :
- Boîte de dépôt
- Toutes les factures
- Validation fournisseur (GS_COMPTABILITE, GS_ADMIN_ERP, GS_DSI)
- Comptabilisation (GS_COMPTABILITE, GS_ADMIN_ERP, GS_DSI)

**Sous-menu programme** :
```
Tableau de bord → Marchés → Factures → TMA → Commercialisation → Trésorerie → Bilan → Documents → ⚙️
```
⚙️ = paramètres programme → liens vers **Financement** et **Associés**

---

## Workflow factures — ARCHITECTURE CRITIQUE

### 3 types (type_source)
```
classique    → NV1 → NV2 → a_comptabiliser → valide → paye
deja_reglee  → bypass NV1/NV2 → a_comptabiliser directement
historique   → bypass tout → paye directement
```

### Règle NV1/NV2
- Si valideur = attributaire → saute NV1, va NV2 directement
- Si valideur ≠ attributaire → passe par NV1

### Statuts complets
```
depose → analyse → attribue → en_cours_saisie
→ en_validation_nv1 → en_validation_nv2
→ a_comptabiliser → valide → paye (+ rejete, archive)
```

### Boîte de dépôt — affiche uniquement
```
depose | analyse | attribue | en_cours_saisie | rejete
```

### Colonnes directes sur depot_factures ✅ (migration 2026_05_18_140000)
```
programme_id, marche_id, fournisseur_id, poste_budgetaire_type_id, type_source
```
ocr_raw = données OCR brutes uniquement (plus de FK dedans)

---

## Marchés — POINTS CRITIQUES

- **⚠️ PAS DE SUPPRESSION** — destroy supprimé définitivement. Utiliser statut "Résilié"
- **poste_budgetaire_type_id** sur la table `marches` — migration `2026_05_19_140000` (peut nécessiter `--path` si bloquée)
- **Édition inline** : clic direct sur une valeur → éditable, blur/entrée = sauvegarde, échap = annule
- **Plus de modale** "Modifier le marché" — tout est inline sur la fiche
- **Plus de prestation liée** — poste_budgetaire_type_id direct, prestation_id = null
- Poste affiché : `{rubrique.libelle} › {poste.libelle}`
- Caution : montant + RG complémentaire si dépassement
- RG calculée sur marché + avenants (montant_total_ht)
- KPIs depuis depot_factures colonnes directes

### Select poste — implémentation correcte
```vue
<Select
    v-model="form.poste_budgetaire_type_id"
    :options="postesOptions"
    option-label="label"
    option-value="id"
    filter
    placeholder="Rechercher un poste..."
/>
// postesOptions = postes.map(p => ({ id: p.id, label: `${p.rubrique_bilan?.libelle} › ${p.libelle}` }))
// Sauvegarde sur @update:model-value (pas @change ni @hide)
```

---

## Nomenclature bilan — SOURCE DE VÉRITÉ

La rubrique est portée par **`rubrique_bilan_id`** uniquement.
Le `code_interne` ne détermine PAS la rubrique.

### Rubriques OVH (prod)
| id | libelle | code |
|---|---|---|
| 1 | 1 — FONCIER | terrain |
| 2 | 3 — CONCESSIONNAIRES | vrd |
| 3 | 4 — TRAVAUX | construction |
| 4 | 5 — HONORAIRES TECHNIQUES | honoraires_techniques |
| 5 | 6 — MOA | charges_directes |
| 7 | 2 — TAXES | t_taxes |
| 8 | 7 — HONORAIRES COMMERCIAUX | honocom |
| 9 | 8 — FINANCIERS JURIDIQUES | financiersjuridiques |

### code_interne hardcodés dans le code
```
C_TRAVAUX_VRD  → fallback resolvePosteBudgetaireTypeId (MarcheController)
FG_HONO_VENTE  → BilanFinancierService — sur poste id=45 "HONORAIRES DE VENTE GLOBAL"
```

### Générateur code_interne
iconv + preg_replace (corrigé — plus de D_E_M_O_L_I_T_I_O_N)

---

## Bilan financier

- Colonnes : Étude fi / Marchés signés (montant_total_ht) / Facturé validé / En attente / Bilan réel / Δ
- **Marchés signés = montant_total_ht** (marché + avenants) — pas montant_marche_ht seul
- **Section Recettes** : CA théorique / prévisionnel / réel / TMA
- **Section Marges** : théorique / prévisionnelle / réelle + taux (>15% vert, 10-15% orange, <10% rouge)
- Factures historiques (statut paye) incluses dans "Facturé validé"

---

## Commercialisation

- Prix grille modifiable quel que soit le statut du lot
- Import Pegao enrichi : lots + statuts + réservations + contacts + co-acquéreurs + dates
- Import actes Pegao séparé (bouton dédié)
- KPI "Remises accordées" = SUM(max(0, prix_grille_ttc - prix_vente_ttc)) lots réservés+actés
- Suppression tous les lots (GS_DSI) avec confirmation "CONFIRMER"
- ENUM statut lots : `en_stock | en_vente | reserve | acte | annule` (bloque non implémenté)

---

## Tables clés

```
programmes                → + intacct_sync_par_entite, honoraires_*, provision_imprevus_pct
lots_commerciaux          → + regime_tva
offres_commerciales       → $table forcé explicitement
reservations              → + date_annulation, motif_annulation, date_acte
marches                   → + poste_budgetaire_type_id (migration 2026_05_19_140000)
                             prestation_id = toujours null désormais
depot_factures            → + programme_id, marche_id, fournisseur_id, type_source (2026_05_18_140000)
postes_budgetaires_types  → code_interne = unicité uniquement
rubriques_bilan           → source de vérité rubrique
lignes_financement        → financement programme
programme_associes        → associés + appels fonds + distributions
objectifs_commerciaux
paiements_intacct
lahouari_scores
```

---

## ⚠️ Points techniques critiques

- **Destroy marché** : SUPPRIMÉ — ne jamais réimplémenter
- **poste_budgetaire_type_id sur marches** : migration `2026_05_19_140000` — si bloquée sur OVH utiliser `--path`
- Tous les marchés OVH ont actuellement `poste_budgetaire_type_id = 20` (CONSTRUCTION+VRD) par défaut du backfill — à corriger manuellement via édition inline
- **ocr_raw** : plus de FK dedans, uniquement données OCR brutes
- **type_source** : synchronisé avec saisie_historique/deja_reglee via observer saving()
- **Import Pegao** : nettoyage backticks avant JSON parse
- **QUEUE_CONNECTION=sync** partout (pas de worker OVH)
- **ApexCharts** : enregistré globalement dans app.js
- **Timeout Guzzle** : 120s pour les appels Claude avec PDFs
- **`protected $table = 'offres_commerciales'`** : forcer sur le modèle

---

## Roadmap

| Module | Statut |
|---|---|
| Auth SSO M365 | ✅ |
| Boîte dépôt factures + workflow refondé | ✅ |
| Comptabilisation APBILL Intacct | ✅ |
| Marchés — édition inline, poste direct, sans prestation | ✅ |
| Commercialisation + import Pegao enrichi | ✅ |
| TMA workflow complet | ✅ |
| Bilan recettes + marges | ✅ |
| Refactoring depot_factures colonnes directes | ✅ |
| Menu Comptabilité (ex-Factures) | ✅ |
| KPI remises accordées corrigé | ✅ |
| Plan de trésorerie | 🔧 En cours de test |
| Correction postes marchés OVH (tous à id=20) | 🔧 À faire manuellement |
| Retenues provisoires multi-lignes | 🔲 |
| Flux ARINVOICE Intacct (TMA + appels de fonds) | 🔲 |
| Mise à jour statut facture depuis paiement Intacct | 🔲 |
| Prescripteurs + commissions | 🔲 |
| Développement foncier | 🔲 |
| Livraison / SAV / GPA | 🔲 |
| Yousign + AR24 + portail acquéreur | 🔲 |

---

## Prochaines priorités

1. Corriger les postes des marchés OVH (tous à id=20 par défaut) via édition inline
2. Tester import Pegao complet avec PDF Arborea
3. Tester plan de trésorerie
4. Flux ARINVOICE Intacct
5. Prescripteurs + commissions

---

## Comment utiliser ce fichier

1. **Début de session** : coller ce fichier dans le chat Claude
2. **Fin de session** : demander la mise à jour PROJECT.md téléchargeable
3. **Déploiement** : npm en local, JAMAIS sur OVH, pas de composer
4. **IDE** : Cursor (Auto-run = Run Everything)
5. **Logs Intacct** : ne jamais partager le fichier brut
6. **Destroy marché** : ne JAMAIS réimplémenter
