--- # === IDENTITÉ === module: M4 nom: "Répertoire transporteurs" ecran: repertoire-transporteurs owner_spec: Matthieu backup_spec: Tristan version: V0.1 date_redaction: 2026-06-15 # Historique : # V0.1 (2026-06-15) — Restitution Markdown du docx « M4-repertoire-transporteurs-V0 » # (validé 27/05/2026) + maquette Figma (node 1132-45376). Précisions techniques (back) # dans spec-back.md. Réutilise le pattern et les composants M1/M2/M3. # === LIENS === maquette_figma: "https://www.figma.com/design/jRYgT0T9c03VsEbjGhCwwS/Composants---Design-System?node-id=1132-45376&p=f&m=dev" regles_metier: [RG-4.01, RG-4.02, RG-4.03, RG-4.04, RG-4.05, RG-4.06, RG-4.07, RG-4.08, RG-4.09, RG-4.10, RG-4.11] roles: [Admin, Bureau, Compta, Commerciale, Usine] lien_spec_back: ./spec-back.md # === VALIDATION CLIENT === client_validation_1: statut: validee date: 2026-05-27 version: V0 valide_par: "Matthieu (CP MALIO)" # === LIEN LESSTIME === lesstime_project_id: 6 lesstime_taskgroup_id: 31 # M4 — Répertoire transporteurs (tickets ERP-153 → ERP-171) statut_global: pret_a_dev --- # Module 4 — Répertoire transporteurs (V0.1 front) > **Origine** : spec fonctionnelle `M4-repertoire-transporteurs-V0` (validée le 27/05/2026) + maquette Figma. Restitution Markdown pour intégration au workflow MALIO. Toute décision technique (back) vit dans [`spec-back.md`](./spec-back.md). Le M4 réutilise le pattern et les composants posés aux [M1 clients](../M1-clients/spec-front.md), [M2 fournisseurs](../M2-suppliers/spec-front.md) et [M3 prestataires](../M3-prestataires/spec-front.md). > **Socle déjà en place** : le module back `Transport` existe (ERP-150) et porte deux référentiels **synchronisés par commandes console** : transporteurs **QUALIMAT** (`qualimat_carrier`, ERP-39) et codes **IDTF** (`idtf_product`, ERP-149). Le M4 ajoute le **répertoire éditable** (`Carrier`) **par-dessus** ces référentiels — la saisie assistée du nom interroge le référentiel QUALIMAT (RG-4.01). L'IDTF n'est **pas** utilisé par ces écrans. > **Décisions Matthieu (15/06/2026)** : (1) lien QUALIMAT = FK + **copie éditable** des champs (nom / certification / adresse) ; (2) **pas de cloisonnement par site** (référentiel global) ; (3) le champ « Décharge » s'appuie sur une **infra d'upload réutilisable** (`Shared`), car d'autres uploads suivront. Détails : [`spec-back.md § 2.5 / § 2.3 / § 2.7`](./spec-back.md). ## But Lister tous les transporteurs de l'organisation et accéder rapidement à leurs fiches : consultation, création, modification, archivage. Le nom est **relié à QUALIMAT** (saisie assistée) ; les transporteurs hors QUALIMAT (GMP+, OVOCOM, compte-propre, LIOT, autre) sont saisis manuellement. ## Accès - **Depuis** : menu principal → section **Transport** (route `/carriers`). *(Section « Transport » dédiée ou rattachement à une section « Logistique » — à confirmer, cf. [`spec-back.md § 5.3`](./spec-back.md).)* - **Rôles autorisés** (tableau « Rôles & permissions » du docx) : | Rôle | Consultation | Ajout / Modification | Archive | |---|---|---|---| | **Admin** | ✅ Tout | ✅ Tout | ✅ | | **Bureau** | ✅ Tout | ✅ Tout | ❌ | | **Compta** | ❌ | ❌ | ❌ | | **Commerciale** | ✅ Tout | ❌ | ❌ | | **Usine** | ❌ | ❌ | ❌ | > **Notes** : > - RBAC transposée sur `transport.carriers.*` (cf. [`spec-back.md § 5`](./spec-back.md)). **Commerciale** = consultation seule (pas de « + Ajouter » ni « Modifier »). **Compta** et **Usine** n'ont **aucun** accès au module (item sidebar masqué). > - **Pas de cloisonnement par site** (≠ M3) : tout rôle autorisé voit tous les transporteurs. ## Navigation Page d'entrée du module **Transport** (route `/carriers`). Titre : « **Répertoire transporteurs** ». - Affichage principal : un **datatable** listant tous les transporteurs **actifs** (les archivés sont masqués par défaut — filtre dédié). - **Clic sur une ligne** → écran **Consultation transporteur** (page dédiée). - **Bouton « + Ajouter »** (haut droite, si `manage`) → écran **Ajouter un transporteur**. - **Bouton « Filtrer »** (haut droite) → panneau de filtres. - **Bouton « Exporter »** (haut droite) → télécharge un **XLSX** des transporteurs **affichés** (cf. filtres actifs). Format dans [`spec-back.md § 4.6`](./spec-back.md). ### Panneau de filtres (bouton « Filtrer ») Réutilise le pattern M1/M2/M3. Filtres branchés sur les query params de `GET /api/carriers` (cf. [`spec-back.md § 4.1`](./spec-back.md)) : | Filtre | Composant | Query param back | |---|---|---| | **Recherche** (nom) | `` | `?search=` | | **Certification** | `` (QUALIMAT / GMP+ / OVOCOM / Compte-propre / Autre) | `?certificationType=` | | **Inclure les archivés** | `` | `?includeArchived=true` | - À l'application des filtres → `setFilters(...)` de `usePaginatedList` (retombe en **page 1**). - **État 100 % local** (jamais dans l'URL — règle ABSOLUE n°6). ## Datatable du Répertoire Composant : `` branché sur `usePaginatedList({ url: '/carriers' })` (règle frontend obligatoire — pagination Hydra, état 100 % local). Colonnes : | Colonne | Source | Tri | |---|---|---| | **Nom** | `carrier.name` | ASC par défaut | | **Certification** | `carrier.certificationType` (libellé i18n) | Non | | **Date de validité** | `carrier.qualimatCarrier.validityDate` (format `JJ-MM-AAAA`) — **fond rouge si < aujourd'hui** (RG-4.04) | Non | | **Dernière activité** | `carrier.updatedAt` (format `JJ-MM-AAAA`) | Oui | > **Clic sur une ligne** → écran Consultation. **Pagination** : standard Starseed 10 / 25 / 50 (défaut 10). Tri serveur `name ASC` par défaut. ## Écran « Ajouter un transporteur » Création par **onglets successifs avec validation incrémentale** : pour passer à l'onglet suivant, il faut avoir validé l'onglet en cours. **Une fois un onglet validé, on passe automatiquement au suivant** ; les champs validés passent en lecture seule. **L'onglet Adresses n'est accessible qu'une fois le formulaire principal validé.** Cf. [`spec-back.md § 2.9`](./spec-back.md) (PATCH partiels par groupe de sérialisation). **Accès** : bouton « + Ajouter » du Répertoire. **Rôles** : Admin, Bureau. **Barre d'onglets** : `Qualimat` · `Adresses` · `Contacts` · `Prix`. ### Formulaire principal (pré-onglets) 1er bloc à remplir. Sans validation, les onglets ne sont pas accessibles. Une fois validé → POST `/api/carriers`, puis bascule sur l'onglet Qualimat/Adresses ; les champs passent en readonly. | Champ | Type composant | Obligatoire | Règle | |---|---|---|---| | **Nom** (saisie assistée reliée à QUALIMAT) | `` (autocomplete) | Oui | RG-4.01 ; RG-4.13 (UPPERCASE serveur) ; RG-4.12 (unicité) | | **Liste certification transport** | `` (GMP+ / OVOCOM / Compte-propre / Autre) | Oui | RG-4.02 ; auto = `QUALIMAT` (lecture seule) si transporteur QUALIMAT sélectionné | | **Affréter** | `` | Non | RG-4.03 | | **Indexation %** | `` | Conditionnel | RG-4.03 — visible + obligatoire si « Affréter » coché | | **Benne / Fond mouvant** | `` | Conditionnel | RG-4.03 — visible + obligatoire si « Affréter » coché | | **Volume m³** | `` | Conditionnel | RG-4.03 — visible + obligatoire si « Affréter » coché | | **Décharge** | `` *(cf. note)* | Conditionnel (**obligatoire si AUTRE**) | RG-4.02 — visible **et obligatoire** si certification = `AUTRE`. Upload via infra Shared ([`spec-back.md § 2.7`](./spec-back.md)) | | **Liste immatriculation LIOT** | `` (ou TextArea) | Cas LIOT | RG-4.01 — visible **uniquement** si nom = `LIOT` ; les autres champs disparaissent. Immatriculations séparées par `;` | > **Comportement RG-4.01 (saisie assistée)** : à la saisie du nom, recherche dans le référentiel QUALIMAT via `GET /api/qualimat_carriers?search=`. Sélection d'un résultat → **modal de confirmation** « Êtes-vous sûr de vouloir intégrer ce transporteur ? ». Si confirmé : le **Nom** et la **certification** (= `QUALIMAT`, lecture seule) se remplissent automatiquement, **ainsi que l'onglet Adresse** (copie pays/CP/ville/voie depuis le référentiel). La FK QUALIMAT est conservée (traçabilité + date de validité RG-4.04). > - **Cas transporteur non trouvé** (pas QUALIMAT) : l'utilisateur choisit une autre certification (RG-4.02) → affichage des champs associés. > - **Cas LIOT** : si le nom saisi est exactement `LIOT`, seul le champ « Liste immatriculation LIOT » s'affiche, les autres champs sont masqués. > **Note ``** : si le composant ne couvre pas le drag & drop / type fichier requis, exception autorisée documentée (`// TODO migrer quand Malio couvre`) — cf. exceptions @.claude/rules/frontend.md. **Action** : « Valider » (``) → POST `/api/carriers` ([`spec-back.md § 4.3`](./spec-back.md)). Succès → onglet « Qualimat » / « Adresses ». ### Onglet « Qualimat » Sélectionner un transporteur de la liste QUALIMAT afin de mettre à jour les informations du transporteur (saisie assistée — voir RG-4.01). **Colonnes du tableau de sélection** : | Colonne | Règle | |---|---| | **Sélection** (bouton / clic ligne) | RG-4.03 *(docx)* — clic → modal « Êtes-vous sûr de vouloir intégrer ce transporteur ? » → remplit Nom + certification + onglet adresse | | **Nom** | — | | **Adresse** | — | | **Date de validité** | RG-4.04 — **fond rouge si < date du jour** | > Cet onglet alimente le formulaire principal et l'onglet Adresse par copie (RG-4.01 / RG-4.05). Source : `GET /api/qualimat_carriers?search=` (lecture seule, lignes actives uniquement). ### Onglet « Adresses » Saisir l'adresse du transporteur (un bloc par adresse). | Champ | Type | Obligatoire | Règle | |---|---|---|---| | **Pays** | `` (préremplie « France ») | Conditionnel | RG-4.05 | | **Code postal** | `` (saisie assistée) | Conditionnel | RG-4.06, RG-4.05 — déclenche autocomplete ville (BAN) | | **Ville** | `` (saisie assistée) | Conditionnel | RG-4.06, RG-4.05 — alimentée par api-adresse.data.gouv.fr | | **Adresse** | `` (saisie assistée) | Conditionnel | RG-4.05 | | **Adresse complémentaire** | `` | Non | — | > **RG-4.05** : les champs sont **déjà remplis** si le transporteur est QUALIMAT (copie). Si « Affréter » est coché, l'adresse devient **obligatoire** (Pays, Code postal, Ville, Adresse). > **RG-4.06** : la ville est préremplie automatiquement à partir du code postal via l'API BAN (`useAddressAutocomplete()`, réutilisé M1/M2/M3). Si plusieurs villes → choix dans le select. L'adresse est une saisie assistée basée sur le CP et la ville. > **RG-4.07** : le bouton « Valider » **n'apparaît pas** pour un transporteur QUALIMAT (adresse remplie automatiquement). **Actions** : « Valider » → PATCH `/api/carriers/{id}/addresses` (sauf QUALIMAT, RG-4.07). ### Onglet « Contacts » Saisir un ou plusieurs contacts associés au transporteur. | Champ | Type | Obligatoire | Règle | |---|---|---|---| | **Nom** | `` | Non | RG-4.08 + RG-4.13 (Capitalize) | | **Prénom** | `` | Non | RG-4.08 + RG-4.13 (Capitalize) | | **Fonction** | `` | Non | RG-4.08 | | **Téléphone** (x1, +1 possible, **max 2**) | `` | Non | RG-4.08 + RG-4.13 (format) | | **Email** | `` type email | Non | RG-4.08 + RG-4.13 (lowercase) | **RG-4.08** : un bloc Contact est valide dès qu'au moins 1 champ est rempli. Impossible d'ajouter un nouveau bloc tant que le précédent n'est pas valide. **Actions** : - « + Nouveau contact » : ajoute un bloc. **Désactivé tant que le bloc précédent n'a aucun champ rempli** (RG-4.08). - « Supprimer » (icône) : modal de confirmation, puis suppression du bloc. - « Valider » → PATCH `/api/carriers/{id}/contacts`. ### Onglet « Prix » Saisir un suivi de prix du transporteur (un bloc par prix). Tous les champs sont masqués par défaut sauf le radio « Client / Fournisseur » (RG-4.09). **Bloc Prix** : | Champ | Type | Obligatoire | Règle | |---|---|---|---| | **Client / Fournisseur** | `` | Oui | RG-4.09 | | **Client** | `` (liste des clients) | Conditionnel | RG-4.10 — si Client | | **Adresse de livraison** | `` (adresses du client sélectionné) | Conditionnel | RG-4.10 — si Client | | **Adresse de départ** | `` (86 / 17 / 82) | Conditionnel | RG-4.10 — si Client ; = un des 3 sites | | **Fournisseur** | `` (liste des fournisseurs) | Conditionnel | RG-4.11 — si Fournisseur | | **Adresse d'approvisionnement** | `` (adresses du fournisseur) | Conditionnel | RG-4.11 — si Fournisseur | | **Adresse de livraison** | `` (86 / 17 / 82) | Conditionnel | RG-4.11 — si Fournisseur ; = un des 3 sites | | **Benne / Fond mouvant (FM)** | `` | Oui | — | | **Forfait / Tonne** | `` | Oui | — | | **Prix** | `` (monnaie) | Oui | — | | **État du prix** | `` (En cours / Validé / Non validé) | Oui | — | > **RG-4.10** : si **Client** sélectionné → champs liés au client affichés et obligatoires ; champs fournisseur masqués et non obligatoires. > **RG-4.11** : si **Fournisseur** sélectionné → champs liés au fournisseur affichés et obligatoires ; champs client masqués et non obligatoires. > **Adresse de départ / livraison « 86 / 17 / 82 »** = les 3 `Site` fixes (cf. switcher de site Châtellerault / Saint-Jean / Pommevic en haut de l'app). La sélection stocke un **ID de Site** ([`spec-back.md § 3.2`](./spec-back.md)). **Actions** : - « + Nouveau prix » : ajoute un bloc. Bloqué tant que le précédent n'est pas valide. - « Supprimer » (icône) : modal de confirmation puis suppression. - « Valider » → PATCH `/api/carriers/{id}/prices`. ## Écran « Consultation d'un transporteur » Consulter en **lecture seule** la fiche complète. Affiche en haut du bloc les infos principales du transporteur (comme l'écran d'ajout) ainsi que les onglets Adresses, Contacts, Prix. **Tous les champs sont en lecture seule.** **Accès** : clic sur une ligne du Répertoire. La page s'ouvre par défaut sur l'onglet **Adresses**. Icône « flèche » à gauche pour revenir au répertoire. Deux boutons à droite : - **« Modifier »** (visible si `transport.carriers.manage` → Admin, Bureau). - **« Archiver »** (visible **uniquement Admin** via `transport.carriers.archive`) → modal de confirmation, puis PATCH `/api/carriers/{id}` `{ "isArchived": true }`. > Un transporteur archivé peut être restauré (`isArchived: false`) — bouton « Restaurer » remplace « Archiver » dans la consultation d'un archivé. ### Onglet Adresses (consultation) Un bloc par adresse du transporteur. Chaque bloc, 5 champs en lecture seule : Pays / Code postal / Ville / Adresse / Adresse complémentaire. ### Onglet Contacts (consultation) Un bloc par contact. 5 champs en lecture seule : Nom / Prénom / Fonction / Téléphone (x1 ou x2) / Email. ### Onglet Prix (consultation) Un tableau regroupant les prix par type (**Fond Mouvant / Benne**) : | Colonne | Description | |---|---| | **Colonne de regroupement** | « Fond Mouvant » / « Benne » | | **Transporteurs** | Nom du transporteur | | **Adresse APRO ou Adresse Sites** | Si prix « Client » → Adresse APRO sinon Adresse Sites | | **Adresse livraisons** | — | | **Forfait €** | Prix | | **Tonne €** | Prix | | **Indexation** | Pourcentage d'indexation (vide si non rempli) | | **État du prix** | Validé / Non Validé / En cours | **Action** : « Exporter » → exporte le tableau au **format Excel** (`GET /api/carriers/{id}/prices/export.xlsx`). ## Écran « Modification d'un transporteur » Modifier les informations d'un transporteur existant. **Identique à l'écran « Ajouter un transporteur »** — mêmes formulaires, mêmes règles métier (RG-4.01 à RG-4.11) — sauf : - Les champs sont **pré-remplis** avec les valeurs actuelles. - **Validation par onglet** : on peut modifier UN onglet sans toucher aux autres (PATCH partiel). - **Accès** : depuis l'écran Consultation, bouton « Modifier » (Admin, Bureau). ## Composants UI à utiliser (`@malio/layer-ui`) - **Datatable** : `` (+ `usePaginatedList`) - **Input texte** : `` - **Input nombre / montant** : `` (indexation, volume), `` (prix) - **Select simple** : `` (certification, pays, ville, client, fournisseur, adresses, sites, état du prix) - **Select multi (cases à cocher)** : `` (filtres certification) - **Radio** : `` (Benne/Fond mouvant, Forfait/Tonne, Client/Fournisseur) - **Checkbox** : `` (Affréter, inclure archivés) - **Upload** : `` (Décharge — exception documentée si type non couvert) - **Bouton** : ``, `` - **Toasts** : standards via `useApi()` - **Validation par champ** : `useFormErrors` (mapping 422 inline — règle frontend obligatoire) **Exceptions autorisées** (commenter `// TODO migrer quand Malio couvre`) : - Modal de confirmation : wrapper partagé dans `frontend/shared/` (réutiliser celui du M1/M2/M3). - `` si le type fichier / drag & drop n'est pas couvert. ## Composables & appels API - `usePaginatedList({ url: '/carriers' })` — liste paginée (obligatoire). Consomme `name`, `certificationType`, `qualimatCarrier.validityDate` (RG-4.04), `updatedAt` (cf. [`spec-back.md § 2.11 / § 4.0`](./spec-back.md)). - `useCarrier(id)` — charge le détail via `GET /api/carriers/{id}`, qui **embarque** `addresses`, `contacts`, `prices` (avec `client`/`supplier`/sites imbriqués) + `qualimatCarrier`. Écrans Consultation et Modification peuplés depuis cette seule réponse. **DoD avant intégration** : vérifier le JSON réel (cf. [`spec-back.md § 4.0.bis`](./spec-back.md)). - `useCarrierForm()` — workflow par onglet (POST principal + PATCH partiels par groupe), miroir de `useSupplierForm()`/`useProviderForm()` + gestion des **champs conditionnels** (Affréter, AUTRE→Décharge, cas LIOT). - `useQualimatSearch()` — saisie assistée du nom : `GET /api/qualimat_carriers?search=`, modal de confirmation, copie des champs + FK (RG-4.01). - `useAddressAutocomplete()` — **réutilisé** du M1/M2/M3 (BAN), pas de réécriture (RG-4.06). - `useUpload()` (NOUVEAU, infra Shared) — POST multipart `/api/uploaded_documents` → renvoie l'IRI à poser sur `carrier.dischargeDocument` (RG-4.02). - `usePermissions()` — masque l'item sidebar et les boutons selon les permissions. - Tous les appels passent par `useApi()` (jamais `$fetch` direct — règle ABSOLUE n°4). - Filter `formatPhoneFR()` — **réutilisé** pour l'affichage `XX XX XX XX XX`. ## Règles de formatage et normalisation Le serveur normalise systématiquement (RG-4.13 — cf. [`spec-back.md`](./spec-back.md)) : | Champ | Normalisation serveur | Affichage front | |---|---|---| | Nom transporteur (`name`) | UPPERCASE intégral | UPPERCASE | | Nom + Prénom contact | Capitalize | identique | | Téléphones (`CarrierContact`) | Chiffres uniquement en BDD | Formaté `XX XX XX XX XX` (filter Vue) | | Email | lowercase intégral | identique | | Immatriculations LIOT | `;`-split, trim, UPPER | listées | > Le front **ne normalise pas** : il envoie la valeur saisie, le serveur normalise et renvoie la valeur normalisée que l'UI affiche. ## API adresse postale Code postal + Ville + Adresse branchés sur **api-adresse.data.gouv.fr** (BAN) via le composable `useAddressAutocomplete()` **déjà créé au M1/M2/M3** (réutilisé tel quel) : - À la saisie du CP (5 chiffres) : `GET https://api-adresse.data.gouv.fr/search/?q={cp}&type=municipality` → alimente le select Ville (RG-4.06 : si plusieurs villes, choix dans le select). - À la saisie d'adresse : `?q={addr}&postcode={cp}&type=housenumber` → suggestions. - Cas dégradé (timeout / offline) : Ville en `` libre + toast d'avertissement. ## Différences notables avec les modules précédents | Zone | M2/M3 | M4 transporteurs | |---|---|---| | Source du nom | saisie libre | **saisie assistée reliée à QUALIMAT** (référentiel synchronisé) | | Onglet Comptabilité / RIB | présent (M2/M3) | **Absent** | | Cloisonnement par site | M3 : oui | **Non** (référentiel global) | | Champs conditionnels formulaire principal | peu | **Nombreux** (Affréter, AUTRE→Décharge, cas LIOT) | | Onglet Prix | absent | **Présent** (Client/Fournisseur, sites départ/livraison) | | Upload de fichier | aucun | **Décharge** (infra upload Shared, réutilisable) | | Module | Commercial / Technique | **Transport** (existant, ERP-150) | ## Points résolus côté back | # | Zone d'ombre | Résolution (cf. `spec-back.md`) | |---|---|---| | 1 | Lien QUALIMAT | FK `qualimatCarrier` + **copie éditable** des champs (§ 2.5) | | 2 | Cas LIOT | Champ `liotPlates` (`;`-séparé), autres champs masqués (RG-4.01) | | 3 | Certification QUALIMAT | Valeur `QUALIMAT` lecture seule si lié (§ 2.5) | | 4 | Décharge (upload) | Infra upload générique `Shared` réutilisable (§ 2.7) | | 5 | Onglet Prix — branches | M2M absentes : FK Client/Supplier + adresses + sites (RG-4.10/4.11, § 3.2) | | 6 | Adresse de départ/livraison 86/17/82 | = les 3 `Site` fixes (FK Site) | | 7 | Workflow par onglet | Sauvegarde incrémentale (POST principal + PATCH partiels) — pas d'état « draft » | | 8 | Archive vs delete | Flag `is_archived` séparé ; archivage Admin seul ; soft delete = HP | | 9 | Unicité métier | Nom seul (§ 2.6) | | 10 | Référentiel QUALIMAT | Endpoint lecture seule `GET /api/qualimat_carriers?search=` (§ 4.7) | | 11 | Format export | XLSX (répertoire + onglet Prix regroupé Benne/FM) | | 12 | RBAC | `transport.carriers.view/manage/archive` ; Compta + Usine sans accès (§ 5.2) | --- ## 📦 Tickets Lesstime **TaskGroup Lesstime** : à créer — `M4 — Répertoire transporteurs` (projet `ERP / Starseed`, projectId=6). Découpe détaillée (back en tête) → [`spec-back.md § Tickets Lesstime`](./spec-back.md#-tickets-lesstime-à-découper). | Ordre | Sujet | Tag | |---|---|---| | 0 | Permissions `transport.carriers.*` + sidebar + 3 sources RBAC | Backend | | 1 | Infra upload générique `Shared` (uploaded_document + FileUploader + endpoint) | Backend | | 2 | Migration BDD M4 (carrier + sous-collections + index + COMMENT) | Backend | | 3 | Entité `QualimatCarrier` (lecture seule) + endpoint recherche | Backend | | 4 | Entités + Repositories Carrier* | Backend | | 5 | CarrierProvider + CarrierProcessor (champs conditionnels, archive, LIOT) | Backend | | 6 | Sous-ressources Adresses / Contacts / Prix (RG-4.10/4.11) | Backend | | 7 | Export XLSX (répertoire + onglet Prix) | Backend | | 8 | Tests PHPUnit RG-4.01→4.14 + capture contrat JSON | Backend | | 9 | Page Répertoire (`/carriers`) + usePaginatedList | Frontend | | 10 | Page Ajouter + formulaire principal + saisie assistée QUALIMAT | Frontend | | 11 | Onglets Adresses (BAN) / Contacts / Prix | Frontend | | 12 | Pages Consultation + Modification | Frontend | | 13 | i18n + libellés audit + upload front (useUpload) | Frontend |