---
suivi: 1241
date: 2026-08-07
sujet: Refonte arborescence SharePoint SUIVI CHANTIER ENVOL
chantier: sharepoint / suivi-chantier
type: feature
statut: poussé
hash: 67f8c2ed
fichiers:
  - app/Support/SharePointArborescenceStructures.php
  - app/Services/SharePointService.php
  - app/Services/SharePointChantierArboMigrationService.php
  - app/Services/SharePointChantierInventaireService.php
  - app/Http/Controllers/Admin/SharePointChantierArboMigrationController.php
  - app/Http/Controllers/MarcheController.php
  - app/Console/Commands/RebuildSharePointAoStructure.php
  - routes/web.php
  - resources/js/Pages/Marches/Show.vue
  - public/build/
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - tests/Unit/SharePointServiceAvenantAttachmentsTest.php
  - docs/suivi/SUIVI_1241_arborescence_chantier.md
---

# SUIVI #1241 — arborescence chantier ENVOL

## PROMPT ENVOYÉ

Refonte `05_SUIVI_CHANTIER` (racine NON renommée) : structure cible, résolution
tolérante, module AO `2_AO/{reference}`, migration web sim/apply, garde-fous
(aucune suppression de contenu, HECTARE intact, pas de rewrite `sharepoint_url`).

## FICHES LUES

- `#1230` — cible arbo + suppression `resolveMarcheClassification`
- `#1232` — inventaire contenu réel ; modèle route web batch
- `#1236` / `#1239` — liens par `item_id` (prérequis levé)
- `#1098` — structure marché lotissement (convergence signalée)
- `#1191` — sim par défaut / apply explicite

## ARBORESCENCE CIBLE

```
05_SUIVI_CHANTIER
├── 1_DCE
├── 2_AO/{reference_ao}/ (+ emplacement dossier.zip non généré)
├── 3_DOSSIER_MARCHE
├── 4_PRESTATAIRES/{POSTE}_{FOURNISSEUR}/
│   ├── 1_ADMINISTRATIF
│   ├── 2_CONTRATS_ET_AVENANTS
│   ├── 3_SUIVI_OPERATIONNEL/ (6 enfants LAZY)
│   └── 4_DOC_LIBRES
└── 5_PHOTOS
```

`00_COMITES_ENGAGEMENT` retiré de `structureVefa()` (suppression si vide en migration).

Convergence : `structureMarcheVefa()` ≈ `structureMarcheLotissement()` sans
`SOUS_TRAITANTS` et sans enfants eager de `3_SUIVI_OPERATIONNEL`. Factorisation
possible ultérieurement — non faite ici. Divergence assumée : `SOUS_TRAITANTS`
reste côté HECTARE.

## OBJET 2 — Résolution par nom (chantier)

| fichier:ligne | nom actuel (avant) | nom cible | traitement |
|---|---|---|---|
| SharePointArborescenceStructures.php:63-84 | 00_COMITES + 1_PRESTATAIRES…AO… | 5 racines 1_DCE…5_PHOTOS | déclaration mise à jour |
| SharePointArborescenceStructures.php:109-116 | — | structureMarcheVefa | nouveau |
| SharePointArborescenceStructures.php:141-153 | — | folderNameAliases | tolérance |
| SharePointService.php:36-42 | DCE,AO,DOSSIER_MARCHE,PRESTATAIRES | 1_DCE…5_PHOTOS | AO_SUIVI_SUBFOLDERS |
| SharePointService.php:1927+ | 1_/2_/3_ classif | 4_PRESTATAIRES | creerDossierMarcheVefa |
| SharePointService.php:1987+ (ancien) | 00_PIECES / 01_CONTRAT / 02_FACTURES | 1_ADMIN…4_DOC_LIBRES | structureMarcheVefa ; factures supprimé |
| SharePointService.php:~3268 | resolveMarcheClassification | — | **supprimée** |
| SharePointService.php:3582+ | ensure AO legacy | 5 racines | ensureAoStructureForProgramme |
| SharePointService.php:3833+ | AO_{Ymd} | {reference} | aoFolderName + legacy |
| SharePointService.php:3879+ | find AO | 2_AO puis AO | resolveAoParentFolder tolérant |
| SharePointService.php:3910+ | dossier AO | ref puis AO_Ymd | resolveAoFolderItemId |
| SharePointService.php:3936+ | — | findChildFolderTolerant | nouveau |
| MarcheController.php | messages 01_CONTRAT | 2_CONTRATS… | libellés ; IDs stockés inchangés |
| RebuildSharePointAoStructure.php | DCE/AO… | 5 racines via ensure | adaptée (CLI OVH inutilisable Graph) |
| AoDocumentService | labels DCE | inchangé (dépôt sous dossier AO) | OK |
| CreateSharePointAoFolder job | délègue creerDossierAppelOffre | 2_AO/{ref} | OK |
| TMA / Commercialisation | pas de noms chantier | — | hors périmètre |
| MigrationService + Controller | — | ops ensure/move/rename/delete | nouveau |

### Tolérance

`findChildFolderTolerant(preferred, aliases)` : cherche le nom cible, puis les
alias legacy (`folderNameAliases()`). Création (`ensure`) utilise **toujours** le
nom cible. **Quand retirer** : après migration de tous les programmes ENVOL
(inventaire #1232 sans `AO`, `1_PRESTATAIRES`, `DCE`, etc.).

## OBJET 3 — Module AO

- Parent : `2_AO` (repli `AO`).
- Dossier consultation : `{reference}` (repli lecture `AO_{Ymd}`).
- Migration : move dossiers datés → `2_AO/{ref}` ; zip *DCE* → `1_DCE`.
- `dossier.zip` : emplacement `2_AO` créé ; **aucune génération** par le module.
- Preuve move : `moveDriveItem` = `PATCH .../items/{id}` avec `parentReference.id`
  + `name` — déplacement métadonnée Graph, pas download/re-upload (OK 342 Mo).
- `RebuildSharePointAoStructure` : adaptée aux 5 racines ; en prod OVH préférer
  la route web (Graph bloqué en CLI).

## OBJET 4 — Migration web

- `GET /admin/sharepoint/migration-chantier/{id}/run` (sim)
- `GET /admin/sharepoint/migration-chantier/{id}/run?apply=1`
- Batch + reprise ; état `storage/app/arborescence_chantier_migration/`
- Ordre : create → move → rename `_old_` → delete_if_empty
- Échecs dans la réponse (pas seulement Log::warning)

### Retour arrière

1. Renommer `_old_1_PRESTATAIRES` → `1_PRESTATAIRES` (idem `_old_2_ENTREPRISES`).
2. Recréer `AO` si besoin et re-déplacer depuis `2_AO` (move inverse).
3. Les `item_id` ERP restent valides quel que soit le chemin.

## LEÇON

- Ne jamais supprimer un dossier non vide : `_old_` + signalement.
- Classification marché par flags poste = non fiable → un seul `4_PRESTATAIRES`.
- Graph move ≠ re-upload : PATCH parentReference.
- Migration SharePoint = route web, jamais artisan sur OVH.
