Files
Starseed/docs/migration/mixgraine-migration-analysis.md
T
Matthieu ca79b8f8e6
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Failing after 34s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m20s
chore(migration) : outils d'extraction des tiers Mixgraine (WIP)
Boite a outils de migration des tiers (clients / fournisseurs / prestataires)
depuis l'ancien CRM Mixgraine vers Starseed :

- extract_mixgraine.py : extraction + normalisation via l'API Mixgraine (cache
  disque reprenable, debit ~1 req/s, backoff 429/5xx) -> JSON format Starseed
- build_tiers_xlsx.py  : Excel de relecture (1 onglet par type + Synthese,
  colonne 'Site manquant' filtrable)
- run.sh               : enchaine extraction + Excel
- README.md            : prerequis, recuperation du token, lancement
- mixgraine-migration-analysis.md : analyse + mapping des champs Mixgraine -> Starseed

WIP : les commandes d'import Symfony cote Starseed (seed referentiels/sites,
import Client/Supplier/Provider, 2e passe distributeur/courtier) restent a faire.

Le dossier de sortie mixgraine-export/ (IBAN/BIC + PII reelles) est volontairement
.gitignore : reproductible localement via MIXGRAINE_JWT.
2026-06-17 08:38:23 +02:00

307 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Migration Mixgraine → Starseed — Analyse des tiers (clients / fournisseurs)
> Sources analysées (exports Mixgraine triés par groupe) :
> - `client.json.json` — **1023 clients** (`customer=true`)
> - `fournisseur.json` — **306 fournisseurs** (`supplier=true`)
> - `fournisseur-client.json` — **106 tiers mixtes** (`customer=true` ET `supplier=true`)
>
> Cible : module `Commercial` de Starseed — entités `Client` / `Supplier` et leurs sous-entités `*Contact`, `*Address`, `*Rib`.
> Date d'analyse : 2026-06-16. (Remplace l'analyse initiale faite sur `customer.json`, 737 tiers, où la classification était absente.)
>
> 🔑 **Source de migration recommandée : l'endpoint unitaire, pas l'export en masse.**
> Les exports CSV/JSON en masse sont **incomplets** : ni RIB/IBAN, ni N° TVA, ni banque de virement, ni rattachement aux sites. Toutes ces données existent dans l'endpoint détail `GET /api/customer/{id}` et surtout dans `PUT /api/customer/{id}` avec `{"__data":true}` (renvoie le **schéma de formulaire complet + les valeurs**). Procédure : paginer `GET /api/customer/?...&limit=...&page=N` pour récupérer les `id`, puis appeler l'endpoint détail pour chaque id. Cf. § 9.
---
## 1. Synthèse
L'export est désormais **trié par groupe** : la distinction client / fournisseur est nette, ce qui lève le principal point bloquant de l'analyse précédente. **1435 tiers distincts** (aucun recoupement d'id entre les trois fichiers).
| Groupe source | Tiers | Devient dans Starseed |
|---|---|---|
| `client.json.json` (`customer=true`) | 1023 | `Client` |
| `fournisseur.json` (`supplier=true`) | 306 | `Supplier` |
| `fournisseur-client.json` (`customer=true` + `supplier=true`) | 106 | **`Client` ET `Supplier`** (cf. § 4) |
| **prestataires** (`prestataire=true`, vue séparée Mixgraine) | à extraire via l'API (filtre `prestataire:true`) | **`Provider`** (module Technique, M3) |
> ✅ Les **prestataires** sont un 4e type de tiers, géré dans une vue dédiée de Mixgraine mais sur le **même endpoint** `/api/customer/` (filtre `{"prestataire":true}`). Côté Starseed, ils ont une **entité dédiée** : `Provider` (module **Technique**, `src/Module/Technique/Domain/Entity/Provider.php`), jumelle du `Supplier`, avec `ProviderContact` / `ProviderAddress` / `ProviderRib`. **Ils ne deviennent donc PAS des `Supplier`.** Particularités vs Supplier : pas d'onglet Information (entité minimale nom + comptabilité) et les **sites sont rattachés directement au prestataire** (M2M `provider_site`, min 1, RG-3.03) — le script agrège donc les sites des adresses au niveau du prestataire. Catégories de type **PRESTATAIRE** exigées (RG-3.09). Le script collecte les prestataires via leur liste dédiée et produit `providers.json` (§ 9).
Volumétrie migrable (tous groupes confondus) :
| Objet source | Quantité | Cible Starseed |
|---|---|---|
| Tiers racine | 1435 | `Client` (1129) + `Supplier` (412) = **1541 entités** créées (les 106 mixtes comptent double) |
| Adresses | **2282** | `ClientAddress` / `SupplierAddress` |
| Contacts | **2704** | `ClientContact` / `SupplierContact` |
| RIB | **0** | *rien à migrer (ni IBAN ni BIC dans l'export)* |
Détail par groupe :
| Groupe | Tiers | Adresses | Contacts | Avec tél | Avec catégorie | Avec type paiement |
|---|---|---|---|---|---|---|
| Clients | 1023 | 1125 | 1279 | 1023 (100 %) | 648 (63 %) | 599 |
| Fournisseurs | 306 | 585 | 758 | 306 (100 %) | 92 (30 %) | 283 |
| Mixtes | 106 | 572 | 667 | 106 (100 %) | 37 (35 %) | 95 |
Points de décision restants (détaillés plus bas) :
1. **Les 106 tiers mixtes** doivent être dédoublés en un `Client` + un `Supplier` (le modèle Starveed sépare les deux entités). § 4.
2. **Référentiels à compléter** : `paymentDelay` (« 20 jours » absent du seed), `tvaMode` (Intracom/Export/achats), mapping des ~100 catégories. § 5.
3. **Site obligatoire par adresse** (min 1) — notion toujours absente de l'export. § 6.
---
## 2. Structure de l'export
Chaque ligne = un tiers. Les champs `addresses.*` et `contacts.*` sont des **listes parallèles** (un tiers peut avoir plusieurs adresses / contacts).
Qualité des données (tous groupes) :
- **Téléphone racine** : rempli sur **100 %** des tiers (1435/1435).
- **Emails contacts** : 1257 présents, **1256 valides** (1 invalide).
- **Codes postaux** : 2282 présents, **10 invalides** à nettoyer : `.` (×3), `B.5300`, `?`, `684`, `854300`, etc. (sinon 422 sur la regex 4-5 chiffres RG-1.09/2.05).
- **Catégories** : 55 % des tiers en ont au moins une (45 % sans) ; **92 % des adresses** ont une catégorie (2104/2282).
- **Noms de contact** : format « `CIVILITÉ NOM PRÉNOM` » (`M `, `Mme`, `M.`) → à parser pour `lastName` / `firstName`.
Pays des adresses : FR (2266), ES (11), BE (2), NL (2), PT (1).
---
## 3. Mapping champ par champ
Légende : ✅ champ existant · ⚠️ migrable avec transformation · ❌ pas de cible (donnée perdue) · 🔒 contrainte de validation à gérer.
### 3.1 Niveau Tiers → `Client` / `Supplier`
| Champ Mixgraine | Cible Starseed | Statut | Note de transformation |
|---|---|---|---|
| `name` / `reference` | `companyName` (≤180) | ✅ | Vérifier l'unicité (index partiel `LOWER(company_name)`). Risque : un même nom présent en client ET fournisseur reste OK (tables distinctes) ; doublons intra-table à contrôler. |
| `phone` (tél de l'objet de base) | `*Contact.phonePrimary` | ⚠️ | **Migré dans la liste de contacts** (cf. § 3.3bis), jamais au niveau racine. Rempli à 100 %. |
| `categoriesStr` / `category.name` | `categories` (M2M `Category`) | ⚠️🔒 | **Min 1 obligatoire**. ~100 valeurs distinctes à mapper vers le référentiel `Category` (§ 5.1). Attention `Courtier`/`Distributeur` (cf. broker/distributor). |
| `liability.name` | `tvaMode` | ⚠️ | `France (ventes)``FRANCE_VENTES` (1426), `Intracom (ventes)``INTRACOM_VENTES` (6), `Export (ventes)``EXPORT_VENTES` (2), `France (achats)`→**pas de mode « achats » au seed** (1 cas). |
| `paymentDelay.label` | `paymentDelay` | ⚠️🔒 | `15 jours``J15` (1376), `30 jours``J30` (43), `A réception``A_RECEPTION` (1), **`20 jours` (15)→ code `J20` à créer** (absent du seed). |
| `paymentType.label` | `paymentType` | ⚠️🔒 | `LCR non soumise`→**à trancher** (`LCR` impose ≥1 RIB, or 0 RIB → préférer `NON_SOUMISE`), `Virement``VIREMENT` (95, **exige `bank`** absente de la source), `Chèque``CHEQUE` (28). |
| `billingAccount` | `accountNumber` (≤40) | ✅ | Peu rempli. |
| `distributor.name` | `distributor` (FK auto-réf. `Client`) | ⚠️ | Suppose que le distributeur existe comme `Client`. Pas d'équivalent côté `Supplier`. |
| `courtier.id` / `courtier.name` | `broker` (FK auto-réf.) | ⚠️ | À vérifier sur les nouveaux exports (vide dans l'ancien). Mutuellement exclusif avec `distributor` (RG-1.03). |
| `customer` / `supplier` | choix de l'entité cible (`Client` vs `Supplier`) | ⚠️ | **Sert au routage**, pas stocké tel quel. |
| `tiersIndexable`, `prestataire`, `coreLocked`, `isOgm`, `maxOwed`, `autoGenerateInvoice` | — | ❌ | Flags Mixgraine sans équivalent métier. |
| `organization1..3` / `organizationsStr` | — | ❌ | Notion d'organisation/agence absente du modèle. |
| `articlesSold.*` | — | ❌ | Relève du catalogue, hors périmètre Client/Supplier. |
| `siren`, `nTva`, `bank` | `siren`, `nTva`, `bank` | ❌ | **Non présents dans l'export** → resteront vides. |
### 3.2 Niveau Adresse → `ClientAddress` / `SupplierAddress`
| Champ Mixgraine | Cible Starseed | Statut | Note |
|---|---|---|---|
| `addresses.street1` | `street` (≤255) | ✅ | Obligatoire. |
| `addresses.street2` | `streetComplement` (≤255) | ✅ | |
| `addresses.postcode` | `postalCode` (≤20, regex 4-5 chiffres) | ⚠️ | 10 valeurs à nettoyer. |
| `addresses.city` | `city` (≤120) | ✅ | Obligatoire. |
| `addresses.country` | `country` (≤80, défaut « France ») | ⚠️ | Convertir code ISO → libellé (`FR`→France, `ES`→Espagne, `BE`→Belgique, `NL`→Pays-Bas, `PT`→Portugal). |
| `addresses.lat` / `addresses.lng` | — | ❌ | Coordonnées GPS perdues (pas de champ géo). |
| `addresses.billing` (true) | `isBilling` (Client) | ⚠️🔒 | Si `true`, `billingEmail` **obligatoire** (RG-1.11), absent de la source → soit ne pas marquer facturation, soit collecter l'email. |
| `addresses.sales` | `isDelivery` (Client) | ⚠️ | |
| `addresses.purchase` | type adresse (Supplier : `DEPART`/`RENDU`) | ⚠️ | Côté fournisseur, l'adresse a un enum `PROSPECT`/`DEPART`/`RENDU`. |
| `addresses.salesTrip` | `isProspect` (?) | ⚠️ | Sémantique « tournée commerciale » à confirmer. |
| `addresses.benneCount` | `bennes` (SupplierAddress) | ⚠️ | Souvent vide. |
| `addresses.carrierTypeStr`, `addresses.name`, `addresses.distances` | — | ❌ | Sans cible. |
| `addresses.categories.name` | `categories` (M2M adresse) | ⚠️🔒 | 92 % des adresses en ont une. **Min 1 obligatoire** côté `ClientAddress`. ⚠️ Codes `DISTRIBUTEUR`/`COURTIER` **interdits** au niveau adresse (RG-1.29). |
| — (aucun) | `sites` (M2M, **min 1**) | 🔒 | **Notion absente de l'export** — bloquant majeur (§ 6). |
### 3.3 Niveau Contact → `ClientContact` / `SupplierContact`
| Champ Mixgraine | Cible Starseed | Statut | Note |
|---|---|---|---|
| `contacts.name` | `lastName` / `firstName` (≤120) | ⚠️ | Parser « CIVILITÉ NOM PRÉNOM » : retirer civilité, 1er token = `lastName`, reste = `firstName`. Au moins l'un des deux requis (RG-1.05/2.04). |
| `contacts.function` | `jobTitle` (≤120) | ✅ | |
| `contacts.email` | `email` (≤180) | ✅ | 99,9 % valides. |
| `contacts.phone` | `phonePrimary` (≤20) | ✅ | |
| `contacts.mobile` | `phoneSecondary` (≤20) | ✅ | |
| `contacts.fax` | — | ❌ | Pas de champ fax dans les contacts Starseed. |
| `contacts.siege` | — | ❌ | Notion « contact siège » non modélisée. |
| `addresses.contacts.*` | `*Address.contacts` (M2M) | ⚠️ | Rattachement contact↔adresse via les ids `addresses.contacts.id`. |
### 3.3bis — Règle : le contact porté par l'objet de base va dans la liste de contacts
Starseed n'a **aucun champ contact au niveau racine** de `Client`/`Supplier`. Tout doit atterrir dans la collection `*Contact` :
- Le bloc `contacts.*` (nom, fonction, email, tél, mobile) → une entrée `*Contact` par contact.
- Le `phone` de l'objet de base (rempli à 100 %) → reporté sur un contact :
- si le numéro figure déjà sur un contact → ne rien créer (déduplication) ;
- s'il diffère → l'ajouter comme `phoneSecondary` d'un contact existant ;
- si le tiers **n'a aucun contact****créer un contact « Standard »** (`lastName = "Standard"` pour respecter RG-1.05/2.04) portant ce `phonePrimary`.
Conséquence : aucun tiers ne perd son téléphone, et rien n'est stocké au niveau racine (conforme au modèle Starseed).
### 3.4 Niveau RIB → `ClientRib` / `SupplierRib`
**Disponible via l'endpoint unitaire** (champ `banks[]`), absent de l'export en masse. Mapping direct :
| Champ Mixgraine (`banks[]`) | Cible Starseed (`*Rib`) | Statut |
|---|---|---|
| `banks[].label` | `label` (≤120) | ✅ |
| `banks[].iban` | `iban` (≤34) | ✅ (validé Assert\Iban) |
| `banks[].bic` | `bic` (≤20) | ✅ (validé Assert\Bic) |
⚠️ Toujours mapper `paymentType="LCR non soumise"` vers `NON_SOUMISE` (et pas `LCR`) : les tiers sans `banks[]` violeraient sinon RG-1.13/2.08 (min 1 RIB).
### 3.5 Champs supplémentaires de l'endpoint unitaire (absents de l'export en masse)
| Champ Mixgraine (détail / `__data`) | Cible Starseed | Statut | Note |
|---|---|---|---|
| `vatNumber` | `nTva` (≤40) | ✅ | N° TVA. |
| `accountingBank` (1=CIC, 2=SOCIETE GENERALE, 3=CREDIT AGRICOLE) | `bank` (FK) | ✅ | **Correspond au seed Starseed** (CIC/SG/CA). Banque de virement. |
| `courtier` (FK select, 18 valeurs) | `broker` (FK auto-réf.) | ✅ | Courtier réel (≠ catégorie « Courtier »). Exclusif avec `distributor` (RG-1.03). |
| `distributor` (FK select, 14 valeurs) | `distributor` (FK auto-réf.) | ✅ | Distributeur réel. |
| `mails[]` {`mail`, `invoice`} | `ClientAddress.billingEmail` / secondaire | ⚠️ | Emails d'envoi de documents ; `invoice=true` → email de facturation (RG-1.11). |
| address `carrierType` (1=Rendu, 2=Départ) | `SupplierAddress.addressType` (RENDU/DEPART) | ⚠️ | Radio exclusif côté fournisseur (RG-2.09). |
| address `organization_1/2/3` | `*Address.sites` (M2M) | ✅ | **Les sites** (cf. § 6). |
| address `hasBenne` / `benneCount` | `SupplierAddress.bennes` | ⚠️ | Nombre de bennes. |
| address `validated` | — | ❌ | « Adresse vérifiée », pas de cible. |
| `isDistributor`, `directBilling`, `distributorDirectOrder`, `newGuarantees`, `commentMail`, `zoneChartering(2)`, `emailDocument*` | — | ❌ | Champs Mixgraine sans équivalent métier. |
---
## 4. Tiers mixtes (client + fournisseur) — 106 cas
Les 106 tiers du fichier `fournisseur-client.json` sont **à la fois client et fournisseur**. Le modèle Starseed sépare strictement `Client` et `Supplier` (deux tables, deux jeux de sous-entités). Deux options :
- **(Recommandé)** Créer **deux entités** par tiers mixte : un `Client` et un `Supplier`, chacun avec sa copie des adresses/contacts. C'est cohérent avec le modèle, au prix d'une duplication (à reconcilier plus tard si un lien inter-entités est ajouté).
- Choisir une seule face (client OU fournisseur) selon l'usage dominant — risque de perte d'information.
Impact volumétrie : 1023 + 106 = **1129 `Client`**, 306 + 106 = **412 `Supplier`**.
---
## 5. Référentiels à préparer avant import
### 5.1 Catégories (`Category`)
Situation nettement meilleure que l'export initial : **55 % des tiers** et **92 % des adresses** portent une catégorie. L'export contient **~100 valeurs distinctes** (top : Eleveur 392, Semencier 61, Coopérative 53, Courtier 38, Négociant 30, …, puis une longue traîne de catégories « fournisseur » métier : Transports, Informatique, Garagistes, Banques…).
À faire :
- **Construire le référentiel `Category`** depuis ces valeurs (dédoublonner casse/accents, ex. `Courtier`/`COURTIERS`).
- **Politique pour les 45 % sans catégorie** : attribuer une catégorie par défaut (ex. `À QUALIFIER`) pour satisfaire la contrainte min 1.
- **Niveau adresse** : exclure `DISTRIBUTEUR`/`COURTIER` (interdits RG-1.29) — les router vers les FK `distributor`/`broker` ou la catégorie tiers.
- Distinguer catégories **type CLIENT** vs **type FOURNISSEUR** (RG-2.10 : une catégorie fournisseur est exigée sur un `Supplier`).
### 5.2 Référentiels comptables
| Référentiel | Valeurs export | Action |
|---|---|---|
| `paymentDelay` | 15 jours, 30 jours, **20 jours**, A réception | Le seed n'a que `J15`/`J30`/`A_RECEPTION`**créer `J20`**. |
| `paymentType` | LCR non soumise, Virement, Chèque | Mapper `LCR non soumise``NON_SOUMISE` (éviter la contrainte RIB) ; `Virement``VIREMENT` (mais `bank` absente → soit la laisser vide en assouplissant RG-1.12, soit collecter). |
| `tvaMode` | France (ventes), Intracom (ventes), Export (ventes), **France (achats)** | Mapper sur `FRANCE_VENTES`/`INTRACOM_VENTES`/`EXPORT_VENTES` ; pas de mode « achats » au seed (1 cas) → décision. |
---
## 6. Sites — résolu via les « organisations » Mixgraine
Chaque `ClientAddress`/`SupplierAddress` doit référencer **au moins un `Site`** (RG-1.10/2.06). Cette notion **existe dans Mixgraine** sous le nom d'**organisations**, visible dans l'endpoint unitaire :
- au niveau tiers : `organizationsStr` (ex. `"Châtellerault, Saint-Jean, Pommevic"`) ;
- au niveau adresse : trois booléens `organization_1` / `organization_2` / `organization_3` étiquetés **Châtellerault / Saint-Jean / Pommevic** ;
- le bloc `addresses.distances` donne même la distance de l'adresse vers chacun de ces 3 sites.
**Les 3 sites à créer dans Starseed** : `Châtellerault`, `Saint-Jean`, `Pommevic`. Le rattachement adresse↔site se reconstruit depuis les booléens `organization_n=true`. Pour une adresse sans aucune organisation cochée, prévoir un site par défaut (à décider).
⚠️ Ce rattachement n'est lisible que via l'endpoint unitaire — raison de plus pour migrer depuis le détail (cf. § 9).
---
## 7. Synthèse des pertes de données
Perdus si non traités en amont : coordonnées GPS (`lat`/`lng`), fax des contacts, contact siège, articles vendus, « adresse vérifiée », et divers flags Mixgraine (`isOgm`, `maxOwed`, `directBilling`, `zoneChartering`, `commentMail`).
Champs cibles qui resteront vides (vraiment absents de la source, même au détail) : `siren` (le `vatNumber` alimente `nTva`, mais pas de SIREN distinct).
> Note : `nTva`, `bank` et les `*Rib` ne sont **plus** des pertes — ils sont disponibles via l'endpoint unitaire (§ 3.4 / § 3.5). Ils n'étaient absents que de l'export en masse.
---
## 7bis. Excel de relecture (un onglet par type)
Beaucoup d'adresses n'ont **aucun site** rattaché (les organisations ne sont pas cochées dans Mixgraine), et d'autres trous existent (CP invalide, facturation sans email…). Avant tout import, on produit un classeur via `build_tiers_xlsx.py` : **`mixgraine-tiers.xlsx`**.
- Un onglet **Synthèse** (compteurs) + un onglet par type : **Clients**, **Fournisseurs**, **Prestataires**.
- Chaque onglet liste toutes les données, **une ligne par adresse** (colonnes du tiers répétées).
- Colonne **Site manquant** (OUI/vide) + **filtre automatique** → un clic pour isoler les adresses sans site. Colonne **Problèmes** pour le reste. Lignes à problème surlignées.
Chiffres réels (extraction 2026-06-16, 1442 tiers) :
| Type | Tiers | Lignes (adresses) | Adresses sans site | Sans catégorie |
|---|---|---|---|---|
| Clients | 1129 | 1697 | 1111 | 444 (39 %) |
| Fournisseurs | 412 | 1157 | 612 | 283 (68 %) |
| Prestataires | 300 | 333 | 0 | 8 (2 %) |
| **Total** | **1841\*** | **3187** | **1723** | **735** |
\* mixtes comptés en Client + Fournisseur. RIB extraits : 90. Tiers totalement bloqués : 90.
Motifs de blocage détectés :
| Niveau | Motif bloquant |
|---|---|
| Adresse | **aucun site rattaché** (RG-1.10/2.06/3.03), code postal absent/invalide, ville absente, rue absente, facturation sans email (client) |
| Tiers | nom absent, `VIREMENT` sans banque, `LCR` sans RIB, **prestataire sans aucun site** (RG-3.03), catégorie `À QUALIFIER` |
Filtrer « Site manquant = OUI » dans chaque onglet permet de corriger la donnée à la source (re-cocher les organisations dans Mixgraine, compléter les emails) **avant** de relancer l'import (le cache accélère).
---
## 8. Séquence d'import recommandée
1. **Extraire la donnée complète** depuis l'endpoint unitaire (§ 9) — pas depuis l'export en masse.
2. **Relecture** : `build_tiers_xlsx.py``mixgraine-tiers.xlsx` (un onglet par type, filtre « Site manquant »), faire corriger à la source (§ 7bis).
2. **Sites** : créer les 3 sites (Châtellerault, Saint-Jean, Pommevic) + un site par défaut éventuel (§ 6).
3. **Référentiels** : créer/compléter `Category` (~100 valeurs + défaut `À QUALIFIER`), `paymentDelay` (ajouter `J20`), `tvaMode`, vérifier `paymentType`, `bank` (CIC/SG/CA déjà OK) (§ 5).
4. **Nettoyer** : 10 codes postaux invalides, parser les noms de contacts (civilité), convertir codes pays ISO → libellés (table fournie par le schéma `country`).
5. **Importer dans l'ordre** : référentiels + sites → tiers → adresses (+ rattachement site via `organization_n`) → contacts (+ rattachement adresse + tél de l'objet de base) → RIB (`banks[]`).
6. **Tiers mixtes** (106) : créer Client + Supplier (§ 4).
7. **Gérer les 422 attendus** : adresses `isBilling=true` sans `billingEmail` ; `paymentType=VIREMENT` sans `bank`.
---
## 9. Stratégie d'extraction via l'API Mixgraine
L'export en masse est insuffisant (§ entête). La donnée complète s'obtient en deux temps depuis `https://liot.mixsuite.fr` (auth : `Authorization: Bearer <JWT>`) :
1. **Lister les ids par groupe, page par page** :
`GET /api/customer/?fields=["name"]&filters={...}&limit=200&order=name&page=N`
La réponse porte `count` (total) et `data[].id`. On pagine **trois listes filtrées** puis on fait l'union dédupliquée :
- `{"customer":true}` → clients (`Client`),
- `{"supplier":true}` → fournisseurs (`Supplier`),
- `{"prestataire":true}` → prestataires (`Provider`, module Technique).
La **classification se fait par appartenance** à ces listes (plus fiable que les flags du formulaire `__data`) : un id dans `customer` ET `supplier` = mixte (Client + Supplier) ; un id dans `prestataire` = `Provider`. Le script produit `clients.json`, `suppliers.json`, `providers.json` + `referentials.json`.
2. **Récupérer le détail complet de chaque tiers** :
`GET /api/customer/{id}` (objet riche : contacts, addresses, banks, liability, paymentType…)
ou `PUT /api/customer/{id}` avec corps `{"__data":true}` qui renvoie en plus le **schéma de formulaire** (libellés des organisations/sites, choix des référentiels, FK distributor/courtier) **et** les valeurs sous `__data`.
Champs clés présents uniquement au détail : `banks[]` (RIB), `vatNumber`, `accountingBank`, `courtier`, `distributor`, `mails[]`, `addresses[].organization_1/2/3` (sites), `addresses[].carrierType`, `details.geo` (lat/lng par adresse).
> ⚠️ Le JWT fourni est un secret de session : ne pas le committer dans le repo. À passer en variable d'environnement au script d'extraction.
---
## 📦 Tickets Lesstime générés
TaskGroup Lesstime : **#32 — Migration tiers Mixgraine → Starseed** (projet STARSEED / ERP)
| # | Ticket | Réf. | Effort | Tag |
|---|---|---|---|---|
| 1.1 | Extraire et normaliser les tiers Mixgraine en JSON | ERP-174 | M | Backend |
| 1.2 | Seeder les référentiels et les 3 sites | ERP-175 | M | Backend |
| 1.3 | Importer les clients (Commercial) + adresses/contacts/RIB | ERP-176 | L | Backend |
| 1.4 | Importer les fournisseurs (Commercial) + adresses/contacts/RIB | ERP-177 | M | Backend |
| 1.5 | Importer les prestataires (Technique/Provider) | ERP-178 | M | Backend |
| 1.6 | Relier distributeurs/courtiers, traiter les mixtes et vérifier | ERP-179 | M | Backend |
Tous au statut **Prêt à dev**. Prochaine action : lancer le 1.1 (le script `extract_mixgraine.py` est déjà écrit, reste à le tester avec un vrai token).
---
*Document basé sur l'analyse des exports en masse `client.json.json` / `fournisseur.json` / `fournisseur-client.json` et sur les réponses des endpoints `/api/customer/` (liste), `/api/customer/{id}` (détail) et `PUT /api/customer/{id}` (`__data`). Aucune donnée n'a été importée — rapport d'analyse préalable.*