---
suivi: 1391
date: 2026-09-01
sujet: programme_hors_erp_socle
chantier: programmes
type: feature
statut: poussé
hash: 
fichiers:
  - app/Models/Programme.php
  - app/Support/ProgrammeHorsErpGuard.php
  - app/Http/Controllers/ProgrammeController.php
  - app/Http/Controllers/ProgrammeDashboardController.php
  - app/Http/Controllers/MarcheController.php
  - app/Http/Controllers/ProgrammeBudgetController.php
  - app/Http/Controllers/BilanFinancierController.php
  - app/Http/Middleware/HandleInertiaRequests.php
  - app/Services/ProgrammeAccessService.php
  - resources/js/Pages/Programmes/Index.vue
  - resources/js/Pages/Programmes/Edit.vue
  - resources/js/Pages/Programmes/CreateHorsErp.vue
  - resources/js/Layouts/AppShell.vue
  - resources/js/Components/ProgrammeHeader.vue
  - routes/web.php
  - tests/Unit/ProgrammeHorsErp1391SourceTest.php
  - database/data/journal_mises_a_jour_post_2026_06_12.php
  - docs/suivi/SUIVI_1391_programme_hors_erp_socle.md
---

## PROMPT ENVOYÉ

SUIVI #1391 — troisième statut programme `hors_erp` (socle back + front liste/fiche). Pas de migration. Pas de dépôt facture (#1392). Garde-fous marché/budget/bilan par statut programme.

## FICHES LUES

- Diagnostic #1390 (acquis prod) : `statut` varchar(50), filtres actif/inactif inchangés, entité obligatoire pour NV2, pas de migration.

## SYNTHÈSE

### A — Statut `hors_erp`

- `Rule::in` étendu à `hors_erp` dans `updateStatut`, `store`, `update`.
- Filtres `actif` / `inactif` de `index` **non modifiés** ; branche `statut=hors_erp` → `where statut = 'hors_erp'`.

### B — Liste `/programmes`

| Libellé affiché | Valeur postée (query) | Valeur base | Libellé back |
|-----------------|----------------------|-------------|--------------|
| Actifs | `statut=actif` | `actif` ou `NULL` | actifs |
| Inactifs | `statut=inactif` | `inactif` | inactifs |
| Hors ERP | `statut=hors_erp` | `hors_erp` | hors ERP |
| Tous | `statut=tous` | (aucun filtre statut) | programmes |

- Compteur adapté à la vue (`N actifs`, `N hors ERP`, etc.).
- Pastille « Hors ERP » en liste et en tête de fiche.

### C — Création hors ERP

- Route `GET /programmes/create-hors-erp`, `POST /programmes/hors-erp`.
- Champs : libellé, entité (required), enseigne (required) + cohérence entité↔enseigne.
- `statut = hors_erp` à la création. **Pas** de dispatch SharePoint (contrairement au `store` classique).

#### Colonnes NOT NULL / unicité `programmes` (migrations)

- NOT NULL sans défaut métier création hors ERP : `libelle`, `enseigne_id` (migration 2026_06_05_110400).
- Nullable acceptées pour création minimale : `entite_id`, `code_envol`, `code_analytique`, etc.
- UNIQUE nullable : `code_envol`, `code_societe` — pas de génération obligatoire pour ce lot.

### D — Blocages serveur (422 ValidationException / 403 abort)

- `MarcheController` create (403) + store (422).
- `ProgrammeBudgetController` index (403) + store enveloppe (422).
- `BilanFinancierController` index (403) + `syncNomenclature` postes (422).
- `ProgrammeHorsErpGuard` : contrôle **statut programme**, pas droits admin/M365.

### E — Accessibilité fiche

- `ProgrammeAccessService` : filtre `actif OR NULL` → hors ERP **absent du switcher** (comportement voulu).
- `ProgrammeController::edit` : pas de filtre ACL statut → fiche **ouvrable** depuis vue Hors ERP.
- `ProgrammeDashboardController::show` : redirect vers `edit` si hors ERP.
- `AppShell` : pas d’onglets programme (nav vide) + pas de switcher sur fiche hors ERP.

### Garde-fou jobs (non modifiés)

| Job | Ramasse un programme hors ERP ? |
|-----|--------------------------------|
| `CreateSharePointArborescence` | Non si créé via `storeHorsErp` (pas de dispatch). |
| `RecupererPaiementsIntacct` | Non par défaut (filtre intacct_class / sync_par_entite ; hors ERP créé sans ces flags). |
| Autres jobs SharePoint (AO, devis, AdF) | Liés à entités métier (marché, AO…) — pas de marché sur hors ERP. |

### Tests

- `ProgrammeHorsErp1391SourceTest` : statut, filtres, garde-fous, UI, switcher.

## DÉPLOIEMENT-TEST

```bash
git pull origin main
php artisan optimize:clear
php artisan view:clear
php artisan journal:sync
```

1. Créer programme hors ERP ENVOL + HECTARE → visibles filtre Hors ERP.
2. Absents Actifs/Inactifs et compteur actifs.
3. Fiche ouvrable depuis liste Hors ERP.
4. Absent switcher navigation.
5. POST marché → 422 message « Ce programme est hors ERP : la création d'un marché n'y est pas autorisée. »
6. POST enveloppe / sync postes → 422.
7. POST sans entité → 422 validation entite_id.
8. 98 actifs + 3 inactifs inchangés.
9. Tri/recherche OK.

## LEÇON

- NV2 reste piloté par `programme → entite → enseigne` ; entité **obligatoire** sur hors ERP.
- Ne pas toucher filtres actif/inactif : exclusion naturelle de `hors_erp`.
- Garde-fous par statut programme, jamais par Gate admin (Robin doit être refusé aussi).
