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.
22 KiB
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=trueETsupplier=true)Cible : module
Commercialde Starseed — entitésClient/Supplieret leurs sous-entités*Contact,*Address,*Rib. Date d'analyse : 2026-06-16. (Remplace l'analyse initiale faite surcustomer.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 dansPUT /api/customer/{id}avec{"__data":true}(renvoie le schéma de formulaire complet + les valeurs). Procédure : paginerGET /api/customer/?...&limit=...&page=Npour récupérer lesid, 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 duSupplier, avecProviderContact/ProviderAddress/ProviderRib. Ils ne deviennent donc PAS desSupplier. Particularités vs Supplier : pas d'onglet Information (entité minimale nom + comptabilité) et les sites sont rattachés directement au prestataire (M2Mprovider_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 produitproviders.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) :
- Les 106 tiers mixtes doivent être dédoublés en un
Client+ unSupplier(le modèle Starveed sépare les deux entités). § 4. - Référentiels à compléter :
paymentDelay(« 20 jours » absent du seed),tvaMode(Intracom/Export/achats), mapping des ~100 catégories. § 5. - 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 pourlastName/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*Contactpar contact. - Le
phonede 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
phoneSecondaryd'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 cephonePrimary.
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
Clientet unSupplier, 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
Categorydepuis 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 FKdistributor/brokerou 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.distancesdonne 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,banket les*Ribne 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
- Extraire la donnée complète depuis l'endpoint unitaire (§ 9) — pas depuis l'export en masse.
- Relecture :
build_tiers_xlsx.py→mixgraine-tiers.xlsx(un onglet par type, filtre « Site manquant »), faire corriger à la source (§ 7bis). - Sites : créer les 3 sites (Châtellerault, Saint-Jean, Pommevic) + un site par défaut éventuel (§ 6).
- Référentiels : créer/compléter
Category(~100 valeurs + défautÀ QUALIFIER),paymentDelay(ajouterJ20),tvaMode, vérifierpaymentType,bank(CIC/SG/CA déjà OK) (§ 5). - Nettoyer : 10 codes postaux invalides, parser les noms de contacts (civilité), convertir codes pays ISO → libellés (table fournie par le schéma
country). - 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[]). - Tiers mixtes (106) : créer Client + Supplier (§ 4).
- Gérer les 422 attendus : adresses
isBilling=truesansbillingEmail;paymentType=VIREMENTsansbank.
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>) :
-
Lister les ids par groupe, page par page :
GET /api/customer/?fields=["name"]&filters={...}&limit=200&order=name&page=NLa réponse portecount(total) etdata[].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 danscustomerETsupplier= mixte (Client + Supplier) ; un id dansprestataire=Provider. Le script produitclients.json,suppliers.json,providers.json+referentials.json. -
Récupérer le détail complet de chaque tiers :
GET /api/customer/{id}(objet riche : contacts, addresses, banks, liability, paymentType…) ouPUT /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.