---
suivi: 1231
date: 2026-08-06
sujet: Plusieurs documents par type marché + normaliseur VEFA + signalement doublons path
chantier: marches-documents / sharepoint
type: feature
statut: poussé
hash: 5b9e79f8
fichiers:
  - app/Http/Controllers/MarcheController.php
  - app/Services/SharePointService.php
  - resources/js/Pages/Marches/Show.vue
  - tests/Unit/SharePointNormalizeDynamicFolderNameTest.php
  - tools/diag/liste_doublons_marche_documents_1231.php
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - public/build/
  - docs/suivi/SUIVI_1231_marche_documents_multi.md
---

# SUIVI #1231 — documents marché multi-versions + normaliseur

## PROMPT ENVOYÉ

SUIVI #1231 — plusieurs fichiers par type de document marché (motif avenants),
signalement des 3 doublons `fichier_path` existants sans fusion, convergence
`normalizeMarcheSegment` → `normalizeDynamicFolderName`, tests unitaires,
journal, build, push. Pas de migration sauf UNIQUE bloquant. Pas de renommage
SharePoint des dossiers `A+_`. Pas d’arborescence (#1230). Pas de data-fix.

## FICHES LUES

- `#1230` — écrasement `{TYPE}.ext`, normaliseur VEFA défaillant, volumes SP ;
  lot E = ce chantier. Ne pas toucher `resolveMarcheClassification` / arbo.
- `#1228` — chemins locaux relatifs `marches/{id}/…` sous `storage/app/public`.
- `#1221` — `Marches/Show.vue` zone commune ; rebase avant build.
- `#1191` (leçon) — pas de fusion silencieuse de données.
- `#1108` / dette SP — échec SharePoint visible (flash warning), pas muet.

## OBJET 1 — Multi fichiers par type

### Convention locale

| | Avant | Après |
|---|--------|--------|
| Chemin | `marches/{id}/{TYPE}.{ext}` (unlink si existe) | `marches/{id}/{uuid}_{nom_sanitizé}.{ext}` |
| Modèle | avenants déjà : `avenants/{id}/{uuid}_{nom}.ext` | aligné |

### Convention SharePoint (VEFA)

| | Avant | Après |
|---|--------|--------|
| Nom | `{TYPE}.ext` (écrasement) | `{TYPE}_{YYYYMMDD}__{nom_original}.{ext}` + `resolveUniqueFileNameInFolder` |
| Lotissement | `{prestataire}__{piece}.ext` | inchangé (+ unicité dossier) |

### UNIQUE

Migration `2026_05_04_171500_create_marche_documents_table.php` : **aucune**
contrainte UNIQUE sur `(marche_id, type_document)` ; aucune migration ultérieure
n’en ajoute. `SHOW CREATE TABLE` local impossible (MySQL down) — à confirmer OVH.

### UI

Liste par type : versions triées `created_at` desc ; `nom_fichier` + date ;
badge « récent » ; « Ajouter une version ». Bandeau manquants / N/X pièces :
présence = au moins un doc du type (`array_diff` / pluck inchangés).

## OBJET 2 — Doublons existants (aucune écriture)

- Flag `fichier_partage` si plusieurs lignes partagent le même `fichier_path`.
- Mention UI : « Fichier partagé avec un autre enregistrement… ».
- `destroyDocument` : unlink disque seulement si plus aucune autre ligne ne
  référence le path.
- Script lecture seule : `php tools/diag/liste_doublons_marche_documents_1231.php`
- Marchés cités pour test UI : **1** et **159**.
- Liste exacte des 3 groupes (ids) : **à coller depuis la sortie OVH du script**
  (MySQL local refusé connexion). Aucune ligne modifiée dans ce lot.

## OBJET 3 — Normaliseur

`normalizeMarcheSegment` délègue à `normalizeDynamicFolderName` (public + trim).

**Appelants de `normalizeMarcheSegment`** (seuls) :

1. `creerDossierMarcheVefa` — segment poste (`SANS_POSTE` si vide)
2. `creerDossierMarcheVefa` — segment fournisseur (`SANS_FOURNISSEUR`)

Aucun autre. Comportement plus strict (collapse / strip) → **nouveaux** dossiers
uniquement. Dossiers existants `A+_` **non renommés** (reprise Graph à part).

Tests : `tests/Unit/SharePointNormalizeDynamicFolderNameTest` (5 cas).

## DÉPLOIEMENT

```text
git pull origin main
php artisan view:clear
php artisan journal:sync
php tools/diag/liste_doublons_marche_documents_1231.php
```

Pas de migrate. Pas de `route:clear` sauf si besoin.

## LEÇON

- Motif avenant (`uuid_nom`) empilable = modèle pour tout upload multi-versions.
- Échec SP doit remonter en flash warning (#1108), pas seulement `Log::warning`.
- Doublons path historiques : signaler, ne jamais fusionner sans décision métier.
- Normaliseur : une seule implémentation (`normalizeDynamicFolderName`) pour VEFA
  et lotissement.
