---
suivi: 1065
date: 2026-07-29
sujet: SharePoint multi-site — site HECTARE + arborescence lotissement
chantier: sharepoint_ged
type: feature
statut: poussé
hash: a108e0bc
fichiers:
  - app/Support/SharePointArborescenceStructures.php
  - app/Support/SharePointHectareAccessProbe.php
  - app/Http/Controllers/Admin/SharePointHectareAccessController.php
  - app/Models/Enseigne.php
  - app/Services/SharePointService.php
  - app/Jobs/CreateSharePointArborescence.php
  - app/Http/Controllers/ProgrammeController.php
  - routes/web.php
  - database/migrations/2026_07_29_180000_add_sharepoint_columns_to_enseignes.php
  - tools/diag/verif_sharepoint_hectare_1065.php
  - docs/suivi/SUIVI_1065_sharepoint_site_hectare.md
---

## PROMPT ENVOYÉ

SUIVI #1065 — Phase 2. Deux sites SharePoint (ENVOL `/sites/envol2026`, HECTARE
`/sites/HECTARE`), deux arborescences. Config site/drive sur `enseignes` (pas
`modules_actifs`). Structure HECTARE = FONCIER, COMMERCIAL, MONTAGE, TRAVAUX (sans
sous-dossiers). Recréer EQUILIBRE sans supprimer l’ancien dossier ENVOL. Phase 0
vérif accès via WEB. Fix `creerDossierMarche` → `resolveDriveIdForProgramme`.
Logs error sur le chemin création. Non-régression ENVOL.

## SYNTHÈSE

### Phase 0 — vérification accès
- Script : `tools/diag/verif_sharepoint_hectare_1065.php` (en CLI OVH : affiche la
  procédure et s’arrête ; pas d’appel Graph par défaut).
- Route WEB : `GET /admin/sharepoint/verif-hectare-1065` (GS_DSI / GS_ADMIN_ERP /
  GS_DIRECTION_GENERALE) → texte brut explicite `ok` / `error_code`
  (`permission_denied`, `site_not_found`, `drive_not_found`, `token_missing`…).
- Logique : `SharePointHectareAccessProbe` (lecture seule, zéro création).

### Configuration
- Colonnes `enseignes.sharepoint_site_id`, `enseignes.sharepoint_drive_name` (nullable).
- **Justification base vs .env** : config métier par enseigne, administrable, évite une
  variable d’env par enseigne. ENVOL reste NULL → fallback `SHAREPOINT_SITE_ID` /
  `SHAREPOINT_DRIVE_NAME` (aucun changement .env requis pour ENVOL).
- HECTARE seed migration : `hectaresa.sharepoint.com:/sites/HECTARE` + `PROGRAMMES`
  (forme Graph hostname:/sites/path — pas un secret).
- Pas de touche à `modules_actifs`.

### Structures déclaratives
- `App\Support\SharePointArborescenceStructures`
  - `structureVefa()` : 8 racines historiques + enfants comités / suivi / AO (bit-pour-bit
    noms de la structure ENVOL pré-#1065).
  - `structureLotissement()` : FONCIER, COMMERCIAL, MONTAGE, TRAVAUX uniquement.
- Sélection : `forEnseigneCode()` → HECTARE = lotissement, sinon VEFA.

### Fix marchés
- `creerDossierMarche()` utilise `resolveDriveIdForProgramme()`.
- Fallback resolve : drive stocké programme, sinon site/drive enseigne, sinon config globale.
- `getDriveId()` global restant : `listerContenuRacine`, `listerContenuParItemId`,
  `getDriveRootItem` (fallback), et fallback ultime de `resolveDriveIdForProgramme`.

### EQUILIBRE
- Bouton / action DSI existante `POST programmes/{id}/sharepoint-sync` →
  `creerArborescenceProgramme` sur le site enseigne HECTARE, met à jour les 3 colonnes SP.
- Idempotent via `ensureFolder` (réutilise si existe). **Aucune suppression** SharePoint.

### Logs
- Chemin création arborescence + job + sync DSI + début marché → `Log::error` + trace.
- Autres `Log::warning` SharePoint : listés dans le livrable (lot ultérieur).

### Journal (#412)
- **Pas d’entrée** : changement d’emplacement GED transparent pour l’usage ERP (navigateur
  GED) ; surface admin DSI uniquement ; pas de nouvelle UI métier visible grand public.

## DÉPLOIEMENT-TEST

```bash
git pull origin main
find app/ -name "*.php" -exec touch {} +
php artisan migrate --force
php artisan route:clear
php artisan optimize:clear
# PAS de journal:sync
```

Phase 0 (avant écriture / recréation EQUILIBRE) :
ouvrir `https://<host>/admin/sharepoint/verif-hectare-1065` en GS_DSI.
Si `permission_denied` → grant Entra sur `/sites/HECTARE`, puis retester.
Puis recréer EQUILIBRE : édition programme → sync SharePoint DSI.

## LEÇON
Une configuration globale unique (un site, un drive) tient jusqu’au jour où une seconde
entité arrive ; le fait que `sharepoint_drive_id` ait été stocké PAR PROGRAMME dès
l’origine a rendu la lecture multi-site possible sans migration — la création, elle,
était restée globale.

Complément (#1067) : Graph accepte path (`host:/sites/nom`) et composite
(`host,guid,guid`), mais `rawurlencode()` casse le path. Stocker en base la forme
composite que le code consomme pour `/drives` — le path a produit un faux « drive
introuvable » alors que l’accès Entra était déjà OK.
