# Architecture — smartcentraldeveloppement

> Document généré par exploration automatisée du code le 2026-08-18, **rafraîchi le 2026-09-07** (intégration Massia, refonte du module `bl` en onglets, routes `devis_laravel`/`formule_modification_groupee`), **puis le 2026-09-09** (migrations de données pour `t_norme_contrainte` et `t_code_norme`, voir §4 et §8). Objectif : servir de carte de référence pour naviguer le projet sans avoir à tout relire à chaque demande de modification. À mettre à jour périodiquement (le code évolue vite, ~160 migrations en ~1 an).
>
> **Portée de l'analyse** : exploration exhaustive de la structure des dossiers (tous les fichiers listés), lecture approfondie d'un échantillon représentatif (~65 fichiers : contrôleurs les plus gros et les plus petits, modèles, migrations, config, middlewares, providers, services, traits, vues, + le module Massia ajouté le 2026-09-07). Les généralisations sur les ~140 contrôleurs et ~150 modèles s'appuient sur cet échantillon et sur la convention de nommage très régulière observée — à affiner si un module précis s'avère être une exception.

---

## 1. Vue d'ensemble

**smartcentraldeveloppement** est un ERP métier pour **centrales à béton** (production, logistique, commande, facturation, qualité/traçabilité, conformité normative béton NF EN 206). C'est une application Laravel monolithique, développée et enrichie en continu depuis plusieurs années (traces d'un existant PHP procédural progressivement porté vers Laravel).

**Stack technique :**

| Couche | Techno |
|---|---|
| Framework | Laravel **10.10** (PHP ^8.1) |
| Auth web | Laravel **Jetstream 4.3** + **Fortify** (session, 2FA activé) |
| Auth API | Laravel **Sanctum 3.3** (tokens Bearer + "abilities") |
| Front-end module métier | Blade + **jQuery/Ajax** + **DataTables** (`yajra/laravel-datatables-oracle`) |
| Front-end socle Jetstream | Blade components + **Livewire 3** + Alpine.js |
| Build assets | **Vite** + **Tailwind 3** |
| Base de données | MySQL (via `doctrine/dbal`), driver session/queue `database` |
| Génération documents | `phpoffice/phpspreadsheet`, `phpoffice/phpword`, `maatwebsite/excel`, `setasign/fpdf`+`fpdi`, `spipu/html2pdf`, `mikehaertl/php-pdftk` |
| Reporting | `koolreport/core` + `koolreport/laravel` |
| Fichiers distants | `league/flysystem-ftp`, `league/flysystem-sftp-v3`, `phpseclib/phpseclib` |
| Qualité code (dev) | `larastan/larastan` (PHPStan), `laravel/pint`, `barryvdh/laravel-ide-helper`, `laravel-debugbar` |

---

## 2. Structure des dossiers

```
app/
├── Actions/        → Fortify/Jetstream (scaffolding standard, peu touché)
├── Console/
│   ├── Commands/   → tâches planifiées (export tarifs, récupération FTP...)
│   └── Kernel.php
├── Exceptions/
├── Exports/        → exports Excel (formules, pesées) via Maatwebsite/PhpSpreadsheet
├── fonctions/       → ⚠️ helpers procéduraux "legacy" (voir §7)
├── Http/
│   ├── Controllers/ → ~140 contrôleurs, 1 par module métier
│   └── Middleware/   → middlewares standards Laravel + 3 middlewares custom (voir §5)
├── Jobs/            → envoi de mails asynchrones (queue)
├── Listeners/        → hooks sur login/logout, échec d'envoi de mail
├── Mail/             → Mailables (commande, facture, mailing, 2FA...)
├── Models/           → ~150 modèles, 1 par table `t_xxx`
├── Providers/
├── Reports/          → rapports KoolReport (ex. chiffre d'affaires par société)
├── Services/         → logique métier orientée objet (tarification, PEPPOL, mailing...)
├── Traits/            → ⚠️ traits utilisés comme "actions de contrôleur" (voir §7)
└── View/Components/  → composants Blade Jetstream (AppLayout, GuestLayout)

routes/
├── api.php    → routes API (Sanctum), petit fichier, peu de groupes
├── web.php    → routes web, fichier UNIQUE de ~2200 lignes pour tous les modules
├── console.php
└── channels.php

resources/
├── views/     → ~140 sous-dossiers (1 par module), pattern index.blade.php + form.blade.php
│   ├── fiche_annexe/    → mini-tableaux réutilisables en onglets (le seul vrai composant partagé)
│   ├── doc_utilisateur/ → documentation utilisateur intégrée, par module (bonne pratique rare)
│   └── components/       → composants Jetstream par défaut
├── js/ et css/  → très légers, l'essentiel du JS métier est inline dans les vues Blade
└── lang/         → fr/en

database/
├── migrations/  → ~160 fichiers, convention de nommage mixte (voir §6)
└── seeders/      → DatabaseSeeder par défaut uniquement

tests/
├── Feature/  → tests Jetstream par défaut + quelques modules métier (Ajout, Chauffeur,
│               Facture, Produit, Service, Zone, SocieteMailing)
└── Unit/     → quasiment vide (ExampleTest uniquement)
```

---

## 3. Cartographie des modules métier

Les contrôleurs/modèles/vues suivent presque tous la convention `<domaine>_controller.php` / `<domaine>.php` (modèle) / `views/<domaine>/`. Voici le regroupement par grand domaine fonctionnel (utile pour savoir "où chercher") :

**Commercial / tiers**
client, contact, commercial, categorie_client, famille_client, sous_famille_client, typologie_client, region_commerciale, secteur, concurrent, adresse_facturation, banque, mode_reg, echeance, assurance_credit, categorie_assurance_credit, position_compte, commentaire_stocker, devis.

**Chantiers**
chantier, planning, planning_commun, planning_prev, ouvrage, type_ouvrage, ventilation, zone.

**Formulation béton (production)**
formule, formule_variant, formule_producteur, composition, classe_consistance, classe_exposition, classe_resistance, classe_chlorure, classe_reduction_prg, designation_beton, type_beton, code_norme, norme, norme_contrainte, specificite, nature_addition, reglage_trappe, groupe_centrale_formule, famille_beton_echantillon, gwr, ajout, article_associe.

> `norme_contrainte` (table `t_norme_contrainte`) porte les valeurs limites par classe d'exposition (rapport Eeff/liant, classe de résistance mini, et surtout le rapport maximal Addition/(ciment+Addition) par type de ciment/addition) issues du tableau **NA.F.1 du PR NF EN 206** — consommée en lecture par `composition_calcul_trait::calculer_ratio_max_addition()` (nom indicatif, voir le trait). `code_norme` (table `t_code_norme`) est en réalité une **simple table de vocabulaire contrôlé** sans clé étrangère (colonnes `classe_resistance`/`classe_exposition`/`type_ciment`/`type_addition` lues indépendamment via `->distinct()` pour peupler des listes déroulantes "Code norme") — elle n'est pas liée à `norme` (`t_norme`, actuellement **0 ligne**) ni à `norme_contrainte` malgré la proximité de nom.

**Centrales & parc matériel**
centrale, chauffeur, vehicule, type_vehicule, gamme_vidange, moyen_dechargement, produit, produit_centrale, produit_transport, fournisseur, typologie_article, famille_article, entree_matiere, energie, trajet, transporteur.

**Commandes / logistique / livraison**
commande, ligne_commande, bl, ligne_bl, pesee, depart_cycle, suivi_numero_bl, reservation_pompe, info_commande, transfert_vers_production, suivi_commande, api_transfert (API). Voir aussi devis (module `devis_laravel`, routes dédiées) qui se transforme en commande/facture/BL.

> ⚠️ **`resources/views/bl/*` est actuellement non commité (untracked)** alors que `bl_controller.php` (tracké) en dépend directement (`view('bl.index')`, `view('bl.form')`...). Le module `bl` a été **récemment décomposé en onglets** (`bl_onglet_general`, `bl_onglet_divers`, `bl_onglet_heures`, `bl_onglet_incident`, `bl_onglet_demat`, `bl_onglet_adresse_facturation/livraison`, `bl_onglet_observation`) — première application concrète de la recommandation §9 étape 3.12 (factoriser les formulaires monolithiques à la manière de `fiche_annexe/`). À committer avant toute synchronisation/déploiement pour ne pas perdre ce travail.

**Facturation**
facture, ligne_facture, pre_facture, ligne_pre_facture, relever_facture, operation_facture, reglement, tva, groupe_tarif, groupe_centrale_tarif, groupe_societe_tarif, tarif, decalage_facturation, motif_avoir, export_facture_electronique (PEPPOL), export_comptabilite.

**Qualité / traçabilité**
tracabilite, tracabilite_composition, tracabilite_envoi, incident, gravite_incident, famille_incident, sous_famille_incident, nature_incident, type_attaque, type_exposition, type_finition.

**Administration / paramétrage**
parametre, profil, utilisateur, societe, civilite, pays, commune, domaine, unite, motif_action, motif_absence, motif_echec_reussite, type_action, type_demande_action, demande_action, message_demande_action, action, activite, fonction.

**Communication**
envoi_mail, envoi_sms, message_alarme_mails, mailing, mailing_template, mailing_history.

**Intégrations externes / imports / IA**
importation_externe, migration_client_centrale, migration_utilisateur, trans_externe, gpt (appel IA externe), etat_stock, cbao.

**Outils internes / dev**
test_copier_fichier_ftp, test_excel, test_impression, test_trans_externe, statistique, cocher_fiche_centrale, modele_impression, modele_qr_code.

---

## 4. Conventions de code observées

- **Contrôleurs** : `<domaine>_controller.php`, `extends BaseController`. Méthodes standard récurrentes : `index()` (vue liste), `data_table()` (endpoint JSON pour DataTables), `afficher_formulaire()` (retourne le HTML du form, chargé en Ajax), `enregistrer()` (create/update), `supprimer()`, `<domaine>_recherche()`. `BaseController` charge `$this->user` et injecte plusieurs helpers de `App\fonctions` + traits métier, mais contient peu de logique propre.
- **Modèles** : `<domaine>.php`, `extends BaseModel` (sauf `utilisateur`/`User` qui restent proches d'Eloquent standard pour Jetstream/Sanctum). Convention systématique `$table = 't_xxx'`, `$primaryKey = 'id_xxx'` (PK non standard), `$guarded = ['created_at','updated_at']` (pas de `$fillable`). Relations **toutes préfixées `lier_`** (`lier_client()`, `lier_centrale()`...) — convention maison, pas du Eloquent idiomatique. `BaseModel` ajoute un hook `saving` qui interroge `SHOW COLUMNS` pour poser des valeurs par défaut par type — mécanisme non standard.
- **Tables** : préfixe `t_` systématique (`t_client`, `t_commande`, `t_formule`...) sauf les tables du socle framework (`users`, `personal_access_tokens`).
- **Vues** : `views/<domaine>/index.blade.php` (grille DataTables serveur) + `form.blade.php` (formulaire chargé en Ajax et injecté dans une `div`, sans rechargement de page). `fiche_annexe/table_xxx_fiche_annexe.blade.php` = mini-composant générique réutilisé en onglet sur plusieurs fiches (client, chantier...) — seul vrai mécanisme de réutilisation de vue identifié.
- **Migrations** : deux formats coexistent — le format standard Laravel (`YYYY_MM_DD_HHMMSS_nom.php`) pour l'historique ancien/framework, et un format maison récent `YYYY_MM_DD_HHmm_V<version>_<INITIALES>_<description>.php` (ex. `2025_08_12_0920_V900_NB_create_t_reglage_trappe.php`) qui trace la version de release et l'auteur directement dans le nom de fichier.
- **Migrations de données** (pas seulement de schéma) : pattern récurrent observé sur plusieurs migrations (`2026_03_23_1330_V904000_BAM_t_client_assurance.php`, `2026_07_29_1410_V905000_GC_t_formule_variant_active.php`, et les deux ajoutées le 2026-09 pour `t_norme_contrainte`/`t_code_norme`) : `Schema::hasTable()`/`Schema::hasColumn()` en garde avant un `Schema::create`/`table`, puis `DB::table(...)->updateOrInsert(...)` ou `->insert()` conditionné (table vide / valeur absente) pour rendre la migration rejouable sans doublon sur un environnement où les données existent déjà. Il n'existe **aucun seeder** dans `database/seeders/` (`DatabaseSeeder` par défaut, vide) — toutes les données de référence "métier" sont poussées via ce type de migration plutôt que via des seeders Laravel classiques.

---

## 5. Authentification & sécurité

- **Web** : Jetstream + Fortify, guard `session`, 2FA activable par utilisateur (`TwoFactorAuthenticatable`), rate limiters natifs sur `login` (5/min) et `two-factor` (5/min). Authentification custom (`FortifyServiceProvider`) : recherche par `email`, `name` OU `code_auto` ; migration legacy des mots de passe SHA1 → bcrypt à la première connexion réussie (`user_ancien_mdp`).
- **API** : Laravel Sanctum, tokens personnels (`HasApiTokens` sur `User`), système d'**abilities** natif Sanctum exploité via le middleware custom `CheckTokenAbility` (alias `ability`, ex. `ability:read`).
- **Middlewares custom** :
  - `CheckUserInterne` (alias `interne`) : bloque l'accès si `user_type != 0` (distingue utilisateurs internes/externes), redirige vers `/bl` sinon.
  - `VerifierSessionUtilisateur` (groupe `web`) : recopie les attributs de l'utilisateur connecté dans `$_SESSION` PHP natif — pont vers du code legacy hors cycle de vie Laravel.
  - `CheckTokenAbility` (alias `ability`) : vérifie `tokenCan()` côté Sanctum.
- **`routes/api.php` (état actuel, mis à jour ce jour)** : `/user` et le groupe `v1/classe_consistance` sont sous `auth:sanctum` ; le groupe `/transfert` vient d'être protégé par `auth:sanctum` également (voir historique de conversation) ; `/centrale_connectee` reste **sans middleware d'authentification** — à examiner si ce point d'entrée doit rester public.
- **Points d'attention sécurité relevés pendant le scan (à traiter, voir §8) :**
  1. **Clé API OpenAI en clair dans `gpt_controller.php`** — à révoquer/rotater immédiatement et déplacer en variable d'environnement.
  2. **Mots de passe FTP/SFTP/Trakkeo en valeur littérale par défaut** dans `config/filesystems.php` (`env('FTP_PASSWORD', 'valeur réelle')`) — mêmes recommandations.
  3. CORS totalement ouvert sur `api/*` (`allowed_origins/methods/headers => ['*']`).
  4. CSRF désactivé sur plusieurs routes à effet de bord (téléchargement PDF, envoi de mail, validation de transfert production).
  5. Dans `routes/web.php`, plusieurs groupes (chantier, commande, facture) n'appliquent le middleware d'auth qu'à la route `index`, laissant les routes d'écriture/suppression reposer sur des vérifications ad hoc dans le code des contrôleurs plutôt que sur le routeur — exactement le même type de trou que celui corrigé aujourd'hui sur `/transfert` dans `api.php`.
  6. `$guarded` limité à 2 colonnes sur des modèles sensibles (`facture`, `client`, `commande`) = mass assignment quasiment ouvert.
  7. `SESSION_SECURE_COOKIE` non forcé à `true`.

---

## 6. Intégrations externes

| Intégration | Où | Nature |
|---|---|---|
| Automate de centrale (temps réel) | `app/Traits/envoyer_socket.php` | Socket TCP brut (`socket_create`/`socket_connect`), requête/réponse synchrone vers `IP_AUTOMATE:PORT_AUTOMATE` — pas de WebSocket web, protocole propriétaire point-à-point |
| Automate de centrale (fichiers) | `app/Traits/copier_fichier_sftp.php` + `Console/Commands/tache_recuperer_fichier_xml_ftp.php` | Récupération SFTP de paires `.dat`/`.flg`, suppression distante après copie. ⚠️ La commande planifiée censée orchestrer ce flux est **vide** (juste un `Log::info`) — flux probablement non opérationnel actuellement, à vérifier |
| Facturation électronique | `app/Services/generer_peppol_bis3_xml_service.php` + `generer_data_peppol_par_facture_service.php` | Génération XML conforme **PEPPOL BIS3 / UBL 2.1**, mapping tolérant aux champs manquants. Pas de validation XSD/Schematron visible dans ce service |
| IA générative | `app/Http/Controllers/gpt_controller.php` | Appel à l'API OpenAI — ⚠️ clé en dur, voir §5 |
| Mail | Mailgun / Postmark / SES / SMTP selon environnement | Config dans `config/mail.php` + `services.php` |
| Fichiers plats clients externes | `app/Services/structure_client_externe_service.php` | Parsing de fichiers à largeur fixe |
| Trakkeo (télématique véhicules ?) | disque `ftp_trakkeo` dans `config/filesystems.php` | FTP dédié |
| Broadcasting | Pusher configuré (`config/broadcasting.php`) | Présence de config, usage réel non confirmé dans l'échantillon |
| Reporting | KoolReport (`koolreport/core`, `koolreport/laravel`, `koolreport/amazing`, `koolreport/datagrid`, `koolreport/printpdf`, `koolreport/excel`) | Voir §6bis — module `statistique_laravel`, portage complet de l'ancien système `public/statistique/*` |
| MASSIA (logiciel qualité béton, éditeur Arcade) | `app/Services/Massia/`, `app/Http/Controllers/Massia/` | Voir §6ter — échange bidirectionnel BL (export) / Formule (import), **ajouté le 2026-09-07, pas encore branché** (aucune route, config ou migration) |
| Impression PDF (SumatraPDF) | `app/Services/impression_pdf_service.php`, `app/Http/Controllers/impression_pdf_controller.php` | Voir §6quater — impression du BL dématérialisé à partir d'un `bl_no_exec`, exposée en API, **ajouté le 2026-09-11** |

---

## 6bis. Module statistiques (`statistique_laravel`)

Portage Laravel complet de l'ancien système de rapports PHP procédural `public/statistique/*` (piloté historiquement par `public/statistique/index.php` + un rapport par sous-dossier). **44 statistiques migrées** (toutes celles du menu `index.php`, plus les dossiers orphelins présents dans `public/statistique/` mais non reliés au menu). L'ancien système reste en place tel quel dans `public/statistique/` (non supprimé).

**Architecture** :
- `app/Http/Controllers/statistique_laravel_controller.php` : contrôleur unique avec un **registre** `slug => classe de rapport` (méthode `registry()`) et des routes génériques :
  - `GET /statistique_laravel/` → page maître de filtres (`index()`, vue `resources/views/statistique/index.blade.php`, reprend le formulaire de l'ancien `index.php` : dates, société/centrale/client/chantier/formule/..., affichage dynamique des critères pertinents par stat via `criteriaMap` en JS).
  - `GET /statistique_laravel/chantiers_par_client` : endpoint JSON pour la liste déroulante chantier dépendante du client choisi.
  - `GET /statistique_laravel/{slug}` → `show()` : rend la statistique en HTML (widget `\koolreport\datagrid\DataTables`).
  - `GET /statistique_laravel/{slug}/pdf` → `pdf()` : export PDF.
  - `GET /statistique_laravel/{slug}/excel` → `excel()` : export Excel.
  - ⚠️ Les recherches "avancées" en popup du formulaire legacy (`public/statistique/recherche_generale/*`) n'ont **pas** été portées ; remplacées par de simples listes déroulantes.
- Chaque statistique vit dans `app/Reports/<slug>/<slug>.php` (classe `extends App\Reports\Support\BaseReport`) + `<slug>.view.php` (web) + `<slug>.pdf.view.php` + `<slug>.excel.view.php`, sur le modèle de `chiffre_affaire_par_societe`/`bl_service_par_societe_centrale` (rapports d'origine, conservés tels quels, non ré-enregistrés dans le nouveau registre pour éviter les collisions de slug).

**Classes partagées** (`app/Reports/Support/`) :
- `BaseReport` : mutualise les traits communs (source Laravel/Eloquent, thème `amazing`, export PDF, export Excel, filtres).
- `FilterableReport` : construit les clauses `WHERE` + tableau de bindings PDO nommés (`buildFilters()`, `dateRangeFilter()`, `societeOuCentraleFilter()`) — **tous les filtres utilisent des requêtes préparées**, contrairement au legacy qui interpolait les valeurs `$_GET` directement dans le SQL (injection SQL possible, corrigée dans ce portage).
- `PdfGroupedTable` : rendu HTML générique d'un tableau groupé à N niveaux pour mpdf (qui n'exécute pas le JS du widget web), avec sous-totaux par niveau.
- `ExcelGroupedTable` : construit la config `rowGroup` du widget `\koolreport\excel\Table` (regroupement + sommes natif côté Excel, pas de recalcul PHP).
- `FastPdfExportable` / `FastPdfHandler` : variante de `koolreport/printpdf` sans `autoScriptToLang`/`autoLangToFont` (gain mesuré modeste, ~9% sur un run moyenné — l'essentiel du temps de génération PDF vient de la mise en page mpdf elle-même sur des tableaux de plusieurs milliers de lignes).
- `ZoneFactureQuantite` / `LigneBlAggregation` : logique métier partagée (quantité facturée d'une ligne "zone" encodée dans `ligne_facture_liste_bl`, calcul du total TTC ligne à ligne d'un BL avec TVA par type d'article) — reprise à l'identique du legacy sur les rapports où elle apparaissait dupliquée.
- `PalmaresReport` : classement décroissant générique pour les rapports "Palmarès" (client/formule/formule M3), basé sur des tables `t_palmares_*` pré-agrégées par un autre processus (pas de filtre de date).

**Corrections apportées par rapport au legacy** (au fil de la migration, cf. commentaires dans chaque classe concernée) :
- Filtres passés en binding PDO au lieu d'être concaténés dans le SQL (faille d'injection).
- Plusieurs rapports lisaient un filtre "chantier" (ou "société") sans jamais l'appliquer à la requête (variable calculée puis oubliée) — filtre effectivement branché.
- `facture_chiffre_affaire_chantier` groupait en réalité par client (copie de `facture_chiffre_affaire_client` jamais adaptée) — corrigé pour grouper par chantier.
- `facture_chiffre_affaire_formule` n'appliquait pas le flip de signe sur avoir (incohérent avec les rapports similaires) — corrigé.
- Bug de famille d'article : plusieurs rapports vérifiaient `famille_service_id` quel que soit le type de ligne (service/ajout/adjuvant/zone/formule n'ont pourtant pas tous cette colonne) — corrigé en résolvant la bonne colonne de famille selon le type réel.
- Date de fin avec un `$` littéral en trop dans une comparaison SQL (`bl_societe_centrale_formule_normee`) — corrigé.
- `journal_comptable`/`journal_vente` lisaient `date_debut`/`date_fin` sans jamais filtrer dessus — filtre de date ajouté sur `facture_date`.

**Limites connues** : le mécanisme d'export PDF du rapport `bl_service` legacy (`koolreport/export` + Chrome/Node headless) n'a pas été repris — ni Chrome ni Node ne sont installés sur cette machine de dev. Tous les nouveaux exports PDF utilisent `koolreport/printpdf` (mpdf, 100% PHP), avec un rendu HTML recalculé côté PHP (le widget web `DataTables` étant du JS, non exécutable par mpdf). Les packages premium KoolReport (`amazing`, `datagrid`, `printpdf`, `excel`) sont installés depuis le dépôt Composer privé `https://repo.koolreport.com` (identifiants dans `auth.json`, gitignored, non commité).

---

## 6ter. Intégration MASSIA (`app/Services/Massia/`, `app/Http/Controllers/Massia/`) — en cours, non branchée

Échange bidirectionnel avec **MASSIA**, le logiciel qualité/formulation béton de l'éditeur Arcade utilisé en centrale, ajouté le 2026-09-07 et **absent de tout autre point d'entrée du code à ce jour** (pas de route déclarée dans `web.php`/`api.php`, pas de clé `services.massia` dans `config/services.php`, pas de colonne `bl_exporte` en migration bien qu'utilisée par le code). Traiter ce module comme un **chantier ouvert**, pas encore fonctionnel en l'état.

**Sens sortant — export d'un BL vers Massia** :
- `App\Services\Massia\MassiaApiClient` : client HTTP bas niveau (`Illuminate\Support\Facades\Http`), authentification par en-tête `X-API-KEY` lu dans `config('services.massia.api_key')` (config à créer), timeout 30 s.
- `App\Services\Massia\BlMassiaMapper::mapper(bl $bl)` : construit le payload JSON `BatchProtocolDto` attendu par Massia à partir d'un `bl` (chantier, client, formule, gâchées/pesées groupées par `pesee_no_gachee`). Documente explicitement dans ses docblocks les champs du schéma Massia **sans équivalent actuel** (envoyés à `null` : `MDRefMassia`, `Certification`, `Hygrometry`, etc.) — bonne pratique de traçabilité des limites connues.
- `App\Services\Massia\BlMassiaExportService::exporter(bl $bl)` : appelle le mapper puis le client, journalise succès/échec (`Log::warning`/`Log::error` + `report()`), marque `$bl->bl_exporte = 1` en cas de succès. Retourne un tableau `['success' => bool, 'message' => ..., 'payload' => ..., 'reponse' => ...]` — jamais d'exception non interceptée, comportement adapté à un déclenchement batch.
- Deux points d'entrée pour déclencher l'export : `App\Console\Commands\massia_exporter_bl_command` (`php artisan massia:exporter-bl {bl_code?}` — sans argument, exporte tous les BL avec `bl_annule = 0` et `bl_exporte = 0` ; **non planifiée dans `Console/Kernel.php`**, à lancer manuellement pour l'instant) et `App\Http\Controllers\Massia\massia_bl_export_controller::exporter()` (pensé pour `POST /v1/massia/bl/{bl}/exporter`, route à créer).

**Sens entrant — import d'une formule depuis Massia** :
- `App\Http\Controllers\Massia\massia_formule_import_controller::importer()` (pensé pour `POST /v1/massia/formule`) valide `CodeBeton`/`Centrale.Code` puis délègue à `App\Services\Massia\FormuleMassiaImportService::importer(array $payload)`.
- Le service fait un `firstOrNew`/upsert sur `formule` (+ `formule_detail` par centrale) et **remplace intégralement** la composition (`composition::where(...)->delete()` puis recréation) pour la centrale concernée, dans une transaction DB unique.
- Les règles de correspondance de codes (préfixe centrale sur le code béton, transformation `X<code>` → `<code>` pour l'exposition, concaténation classe+résistance, `Cl0<code>` pour le chlorure, suffixe `--` sur les codes fournisseurs...) sont **documentées en docblock avec leur origine** ("Interface Massia-Alfi_Formules/Produits" fournie par Arcade) et leurs limites (règle chlorure déduite d'un seul exemple) — traçabilité métier soignée, à conserver comme modèle pour documenter les futures intégrations tierces.

**À faire avant mise en service** (voir aussi §9) : déclarer les routes `v1/massia/...` (avec middleware d'auth adapté — probablement une clé API dédiée côté entrant, symétrique à celle utilisée en sortant), ajouter `config/services.php['massia']` + variables `.env`, migrer la colonne `bl_exporte` sur `t_bl`, décider si `massia:exporter-bl` doit être planifiée (`Console/Kernel.php`) ou rester déclenchée à la demande.

---

## 6quater. Impression d'un lot de fiches par no_exec (`impression_pdf_controller`)

Route API (`POST /impression_pdf/imprimer`, sans middleware d'auth, ajoutée
le 2026-09-11, adaptée le 2026-09-14 au format d'un appelant externe —
automate de centrale) qui reçoit un **tableau JSON** de fiches, chacune avec
`fichier_type` (`0` = bl, `1` = pesee), `fichier_no_exec` (→ `t_bl.bl_no_exec`),
et `fichier_id`/`centrale_code` (reçus, non exploités pour l'instant —
`centrale_code` ne filtre pas la recherche). Chaque fiche est traitée
indépendamment et le contrôleur renvoie un tableau de résultats dans le même
ordre (statut HTTP `200` global, succès/échec porté par fiche). Pour
`fichier_type=0` : retrouve le BL, régénère son PDF dématérialisé via
`App\Services\bl_demat_service::genererPdfPourBl()` (§ ci-dessus) puis
l'imprime via `App\Services\impression_pdf_service::imprimerPdf()` (appel CLI
à SumatraPDF via `Illuminate\Support\Facades\Process` — plus `exec()` brut —
borné à **30 s** avec `Process::timeout(30)`, chemin lu dans
`env('SUMATRA_PATH')`, imprimante par défaut dans `env('IMPRIMANTE')` ; si
SumatraPDF ne répond pas dans ce délai — imprimante introuvable/hors ligne,
boîte de dialogue Windows bloquante — le process est tué et un message
d'erreur dédié est retourné au lieu de bloquer la requête indéfiniment, comme
c'était le cas avant ce correctif du 2026-09-14). Pour `fichier_type=1` : résout l'`id_bl`
correspondant (`bl::where('bl_no_exec', ...)->value('id_bl')`), génère le PDF
des pesées via `App\Services\pesee_pdf_service::genererPdfPourBl()`
(enregistré dans `storage/app/public/pdf_pesee/{id_bl}.pdf`) puis l'imprime de
la même façon. `pesee_pdf_service` a été extrait de
`pesee_controller::imprimer_pdf()` (qui construisait son PDF `Html2Pdf` en
ligne pour le renvoyer en téléchargement navigateur, mode `'D'`) afin d'être
**partagé entre `pesee_controller`** (méthode `construireHtml2Pdf()`, mode
`'D'` inchangé) **et `impression_pdf_controller`** (méthode
`genererPdfPourBl()`, mode `'F'` = enregistrement disque + impression) — même
logique d'extraction que pour `bl_demat_service`, à réutiliser si un futur
type de fiche imprimable apparaît. Voir
[`docs/impression_pdf_controller.md`](docs/impression_pdf_controller.md) pour
le détail des paramètres/réponses. `bl_no_exec` n'a pas de contrainte
d'unicité en base (incrémenté globalement via `bl::max('bl_no_exec') + 1`),
point à surveiller si des doublons apparaissent un jour.

---

## 7. Patterns transverses (Services / Traits / fonctions)

Trois styles coexistent sans frontière claire :

- **`app/Services/`** : classes orientées objet, responsabilité unique, injectables — le style "cible" (`tarif_service`, `article_tarif_service`, `MailingService`, `generer_peppol_bis3_xml_service`, `structure_client_externe_service`). Le nouveau sous-namespace `App\Services\Massia\` (client HTTP / mapper / service métier séparés, injection par constructeur, docblocks qui tracent les règles métier et leurs limites) est à ce jour l'exemple le plus abouti de ce style — bon gabarit à réutiliser pour la prochaine intégration tierce plutôt que de repartir du legacy `app/fonctions/`.
- **`app/Traits/`** : utilisés ici de façon détournée comme des **actions de contrôleur entières** (`composition_calcul_trait`, `dupliquer_commande_trait`, `envoyer_socket` — ils appellent `response()->json()`, `auth()->user()` directement). Ce n'est pas l'usage classique d'un trait (comportement réutilisable indépendant du contexte HTTP), ce qui nuit à la testabilité.
- **`app/fonctions/`** (namespace `App\fonctions`, minuscule) : classes "sac de fonctions" procédurales héritées d'un style pré-Laravel, portant parfois une logique métier aussi lourde que les Services (ex. calcul de taxes sur 12-19 Ko) sans règle documentée de placement.

Le trait `champs_invisibles_api_si_exterieur_trait` (masquage de champs API interne/externe, utilisé dans `BaseModel`) est défini et exposé sur tous les modèles, mais **aucun consommateur** (Resource API, middleware) n'a été trouvé dans l'échantillon exploré — à vérifier avant de le documenter comme actif.

---

## 8. Bilan : ce qui va bien / ce qui est à surveiller

### Points forts
- **Conventions de nommage très régulières** sur 140+ modules (contrôleur/modèle/vue/table alignés) : une fois le pattern connu, on retrouve n'importe quel module en quelques secondes.
- **Modélisation métier riche et conforme au domaine** (normes béton NF EN 206 : classes de résistance, exposition, chlorure, consistance...) — un vrai savoir métier est capitalisé dans le schéma de données.
- **Documentation utilisateur intégrée** (`views/doc_utilisateur/*`) par module — pratique rare et précieuse pour l'onboarding/support, à conserver et étendre.
- **`fiche_annexe/`** : un vrai mécanisme de composant Blade réutilisable et paramétrable, îlot de bonne factorisation dans un océan de vues monolithiques.
- **Gestion asynchrone du mail correcte** : Jobs + Listeners + hook `Queue::failing()` qui repropage l'échec vers le statut métier en base — un vrai souci de fiabilité.
- **Effort de conformité réglementaire réel** (PEPPOL/UBL2.1) et **couverture de tests Feature** sur plusieurs modules métier sensibles (facturation, produit, service, zone, chauffeur) en plus des tests Jetstream par défaut.
- **Traçabilité version/auteur dans les migrations récentes** : intention louable de savoir quelle migration correspond à quelle livraison.
- **Le module `bl` vient d'être redécoupé en onglets** (`bl_onglet_*`) et **la nouvelle intégration Massia suit un vrai style Service découplé** (client HTTP / mapping / logique métier séparés, règles de correspondance documentées avec leur source) — deux signaux concrets que les recommandations de la précédente version de ce document sont suivies d'effet.

### Points de vigilance
- **Secrets en clair dans le code versionné** (clé OpenAI, mots de passe FTP/SFTP) — **toujours présents et inchangés au 2026-09-07** (vérifiés à nouveau : `gpt_controller.php:17`, `config/filesystems.php:42,53,66`) — le point le plus urgent, indépendant de toute réécriture de code, et d'autant plus critique si ce dépôt est poussé sur un remote partagé/GitHub.
- **Travail non commité qui casse silencieusement le module `bl` s'il est perdu** : `resources/views/bl/*` (19 fichiers, dont la nouvelle décomposition en onglets) est untracked alors que `bl_controller.php` en dépend directement — aucune protection Git dessus pour l'instant.
- **Intégration Massia livrée "à moitié branchée"** : code Services/Controllers présent mais aucune route, config ou migration associée — risque d'oubli si une autre tâche prend le dessus avant la finalisation (voir §6ter et §9).
- **Contrôleurs géants** (jusqu'à 73 Ko / plusieurs centaines de lignes par méthode) mélangeant Eloquent, SQL brut, génération PDF et appels externes — coûteux à faire évoluer sans régression.
- **Vues formulaire monolithiques** (jusqu'à 83 Ko sur un seul fichier `form.blade.php`) — un seul fichier porte tout le cycle de vie d'une fiche complète.
- **Protection d'accès incohérente entre routes d'un même module** (middleware appliqué sur l'index mais pas sur les routes d'écriture) — même défaut que celui qu'on vient de corriger sur `/transfert`.
- **Trois styles de "logique métier transverse"** (Services/Traits-actions/fonctions procédurales) sans règle de placement — risque de duplication de règles métier.
- **Mass assignment quasi ouvert** sur des modèles sensibles.
- **Absence de docblocks métier** (relations, colonnes, règles) au-delà du tag généré par l'IDE helper.
- **Dossier `Unit/` de tests quasiment vide** — la logique de calcul (tarification, taxes, composition béton) n'est pas couverte par des tests unitaires isolés, seulement par des tests Feature bout-en-bout sur certains modules.
- **Bug confirmé dans `composition_calcul_trait.php` (switch `$type_addition`)** : deux `case` correspondent tous les deux à "Addition calcaire cat A" (au lieu d'un pour cat A et un pour cat B) — la colonne `*_calcaire_b_na` de `t_norme_contrainte` n'est donc **jamais** sélectionnée par ce calcul, quelle que soit l'addition réellement utilisée sur le produit. Repéré le 2026-09-08, pas encore corrigé (tâche flaggée en session, non traitée).
- **`t_code_norme` contient des données historiques de mauvaise qualité** (20 lignes : doublons `X0`/`XS3`, 4 lignes avec `classe_resistance`/`type_ciment`/`type_addition` vides, pas de couverture systématique des valeurs valides) — une migration (`2026_09_09_1000_V905000_NB_t_code_norme_remplissage.php`) les a rendues reproductibles sur un nouvel environnement mais n'a **pas** nettoyé ni complété ce jeu de données ; à trancher avec le métier avant d'aller plus loin (cf. `docs/t_code_norme_remplissage.md`).

---

## 9. Feuille de route d'amélioration progressive

Pensée pour être appliquée **par petites touches, au fil des besoins**, sans chantier de refonte globale.

**Étape 0 — Corrections de sécurité immédiates (peu de risque, à faire dès que possible)**
1. Révoquer/rotater la clé OpenAI codée en dur dans `gpt_controller.php` (**toujours en clair au 2026-09-07**), la passer en `.env`.
2. Sortir les mots de passe FTP/SFTP/Trakkeo des valeurs par défaut littérales de `config/filesystems.php` (**toujours en clair au 2026-09-07**).
3. Forcer `SESSION_SECURE_COOKIE=true` en production.
4. Restreindre `allowed_origins` dans `config/cors.php` aux domaines réellement utilisés par l'API.
5. Committer (ou au moins sauvegarder) `resources/views/bl/*` avant toute autre manipulation Git sur ce dossier — actuellement le seul filet de sécurité est le disque local.

**Étape 1 — Cohérence des accès (module par module, dès qu'on retouche un module)**
6. Auditer et harmoniser, module par module, l'application du middleware d'auth **au niveau du groupe de routes** plutôt qu'à la seule route `index` (web.php ET api.php) — commencer par les modules les plus sensibles (facture, commande, chantier).
7. Décider explicitement d'un `$fillable` (plutôt que `$guarded` à 2 colonnes) sur les modèles manipulant de la donnée sensible (facture, client, commande, reglement), en commençant par celui qu'on modifie déjà.
8. Documenter/retirer la commande planifiée vide `tache_recuperer_fichier_xml_ftp` pour éviter de croire ce flux opérationnel.
9. Terminer le branchement de l'intégration Massia avant de l'oublier : routes `v1/massia/...` + middleware d'auth dédié, entrée `config/services.php['massia']` + `.env`, migration de la colonne `bl_exporte` sur `t_bl` (voir §6ter).

**Étape 2 — Nettoyage progressif de la couche métier transverse (opportuniste)**
10. Chaque fois qu'un trait "action de contrôleur" (`composition_calcul_trait`, `dupliquer_commande_trait`, `envoyer_socket`) est touché pour un besoin métier, en extraire la logique dans un vrai Service testable, sans dépendance à `Request`/`response()`.
11. Migrer progressivement les fichiers de `app/fonctions/` les plus consultés vers `app/Services/` (commencer par ceux dont la complexité rivalise déjà avec les Services existants, ex. calcul de taxes/transport) — le style adopté dans `app/Services/Massia/` (client/mapper/service séparés, docblocks de règles métier) est un bon gabarit à suivre.
12. Ajouter un test unitaire chaque fois qu'une règle de calcul (tarif, taxe, composition) est extraite en Service — combler petit à petit le dossier `tests/Unit/`.

**Étape 3 — Refactor ciblé des zones les plus lourdes (quand elles doivent être modifiées de toute façon)**
13. Découper les contrôleurs les plus gros (`commande_controller`, `formule_controller`, `client_controller`, `facture_controller`) en sous-contrôleurs ou Services par sous-fonction, au moment où une évolution touche déjà cette zone — pas de réécriture préventive.
14. Découper les formulaires Blade monolithiques (`client/form.blade.php` et équivalents) en composants Blade par onglet, en profitant du pattern `fiche_annexe/` déjà en place comme modèle de réutilisation — **le module `bl` vient de le faire** (`bl_onglet_*`, non commité, voir §3) : une fois validé et committé, il peut servir de référence concrète pour les prochains modules plutôt que `fiche_annexe/` seul.
15. Pour les nouvelles migrations, conserver la traçabilité version/auteur mais dans le corps du fichier (commentaire/docblock) plutôt que dans le nom, pour revenir au format standard `YYYY_MM_DD_HHMMSS_nom.php` et retrouver la compatibilité totale avec l'outillage Laravel — sans renommer l'historique existant.

---

## 10. Où chercher quoi — aide-mémoire rapide

- **Une route ne répond pas comme attendu ?** → `routes/web.php` (front métier) ou `routes/api.php` (API), puis le contrôleur `<domaine>_controller.php` correspondant, méthode `index/data_table/afficher_formulaire/enregistrer/supprimer`.
- **Un champ n'apparaît pas dans l'API pour un client externe ?** → vérifier `$champs_invisibles_api_exterieur` sur le modèle concerné et le trait `champs_invisibles_api_si_exterieur_trait`.
- **Un calcul de prix/tarif semble faux ?** → `app/Services/tarif_service.php` et `article_tarif_service.php` (et vérifier qu'il n'y a pas une règle concurrente dans `app/fonctions/`).
- **Un envoi de mail échoue silencieusement ?** → `app/Jobs/`, `AppServiceProvider::boot()` (hook `Queue::failing`), table `t_envoi_mail` (`envoi_mail_statut`).
- **Un problème de communication avec une centrale ?** → `envoyer_socket.php` (échange direct automate) ou `copier_fichier_sftp.php`/commande FTP (échange par fichiers).
- **Une facture électronique PEPPOL est mal formée ?** → `generer_peppol_bis3_xml_service.php` + `generer_data_peppol_par_facture_service.php`, contrôleur `facture_controller::telecharger_facture_peppol()`.
- **Un droit d'accès API à ajuster ?** → middleware `ability` (`CheckTokenAbility`) et `interne` (`CheckUserInterne`), tokens Sanctum.
- **Un échange avec Massia (export BL / import formule) ne fonctionne pas ?** → `app/Services/Massia/*` + `app/Http/Controllers/Massia/*` ; vérifier d'abord que les routes/config manquantes ont bien été ajoutées depuis (voir §6ter, module non branché au 2026-09-07).
- **Un ratio maximal d'addition (composition béton) semble faux ?** → `app/Traits/composition_calcul_trait.php` (lit `t_norme_contrainte` via `classe_exposition`) — vérifier en premier le bug connu de `case` dupliqué sur "cat A" (§8) qui empêche la sélection de la colonne calcaire cat B.
