Après qu'une inondation catastrophique a détruit notre entrepôt et nous a contraints à tout reconstruire de zéro, j'ai décidé de bien faire les choses cette fois-ci : migrer des feuilles de calcul vers une véritable base de données, bâtir un ERP digne de ce nom et ne plus jamais perdre le contrôle de nos données. Voici l'histoire de cette migration.
Contexte : l'inondation qui a tout changé
En avril 2024, nous avons emménagé dans un entrepôt industriel à Alfafar, près de Valencia, pour développer nos activités. Six mois plus tard, en octobre 2024, la DANA — un phénomène météorologique catastrophique — a frappé Valencia. Notre entrepôt se trouvait dans l'une des zones les plus durement touchées. Les eaux de crue ont détruit le stock, le matériel et des mois d'élan opérationnel.
S'ensuivirent deux mois de gestion de crise qui n'avaient rien à voir avec le code. Dans les semaines qui ont suivi l'inondation, des bénévoles et des militaires sont venus nous aider à remettre l'entrepôt en état — inventaire, matériel, nettoyage et logistique. Puis vinrent les déclarations de sinistre auprès des assurances, les budgets d'urgence sans aucun chiffre d'affaires et le choc mental de voir sous l'eau tout ce que l'on avait construit. Ce fut la période la plus difficile de ma vie.
Mais ce fut aussi un nouveau départ. Lorsque nous avons reconstruit — et nous l'avons fait — j'ai pris une décision : cette fois, nous allions bien faire les choses. Fini le bricolage. Fini le « ça suffira ». Nous avons reconstitué l'équipe, passant de 2 à 5 salariés, et je me suis engagé à bâtir une infrastructure solide : une vraie base de données, un vrai ERP, une vraie documentation. L'ère de Google Sheets était terminée.
Le point de départ : pourquoi Google Sheets devait disparaître
Lorsque j'ai fondé l'entreprise, Google Sheets s'imposait comme le choix évident pour gérer l'inventaire. C'est collaboratif, visuel, tout le monde sait s'en servir et c'est gratuit. Mon équipe d'entrepôt pouvait mettre à jour les emplacements de stock. Je pouvais écrire des formules. Tout le monde voyait tout en temps réel.
Le problème, c'est que Google Sheets n'a jamais été conçu pour faire office de base de données. Et quand on traite des milliers de SKU de liquidation Amazon répartis sur plusieurs comptes de marketplaces, le système commence à se fissurer de manières difficiles à anticiper :
- Limites de débit de l'API — l'API Google Sheets a un quota d'environ 60 requêtes par minute et par utilisateur. Quand 5 scripts automatisés sollicitent la même feuille, ce quota est consommé instantanément
- Cauchemars de concurrence — deux scripts mettant à jour la même ligne au même moment. Un employé qui modifie pendant qu'un script écrit. Des recalculs de formules pendant des imports de données. La corruption des données était un risque permanent
- Aucune intégrité référentielle — une « vente » était une ligne dans un onglet. L'« article » qu'elle référençait était une ligne dans un autre onglet. Le lien ? Une chaîne de texte (le code LPN). Une faute de frappe dans le LPN ? Une vente orpheline. Aucune clé étrangère pour vous sauver
- Dégradation des performances — à environ 8 000 lignes avec des formules, la feuille mettait plus de 15 secondes à se charger. Mes employés attendaient. Les opérations ralentissaient
- Aucune capacité de requête — « Montre-moi tous les articles du camion A2Z33838 encore en stock et qui n'ont pas été mis en vente sur eBay » exige une formule QUERY de 200 caractères qui casse dès que quelqu'un insère une colonne
Je devais migrer. Mais je ne pouvais pas arrêter l'entreprise pour le faire.
La contrainte qui a tout changé
Il ne s'agissait pas d'une migration en mode projet du week-end. C'était une entreprise en production, avec de vraies contraintes :
- 5 employés qui dépendaient de Google Sheets au quotidien — des opérateurs d'entrepôt scannant des articles, l'administration traitant les commandes, la logistique coordonnant les expéditions
- Des commandes arrivant tous les jours sur Wallapop (notre canal principal), chacune devant être traitée en quelques heures
- Des données financières qui devaient être exactes au centime près — nous suivons les coûts par camion Amazon (codes A2Z) à des fins fiscales et de reporting de rentabilité
- Aucun second ingénieur — j'étais le seul développeur, ET je dirigeais simultanément les opérations de l'entreprise
Je ne pouvais pas simplement actionner un interrupteur. Il me fallait une stratégie de migration qui me permette d'avancer progressivement, de valider à chaque étape et de revenir en arrière si quelque chose tournait mal.
Le pipeline en 5 phases
J'ai conçu la migration comme un pipeline en 5 phases, où chaque phase pouvait être exécutée indépendamment en mode dry-run ou execute :
Phase 1: EXTRACT & ANALYZE
└─ Pull all data from 3 Google Sheets tabs → CSVs
└─ Generate analysis report (row counts, data types, anomalies)
Phase 2: VALIDATE
└─ Parse every row against the target PostgreSQL schema
└─ Type conversions (dates, decimals, booleans, enums)
└─ Flag invalid rows with specific error reasons
└─ Generate validation report
Phase 3: MIGRATE
└─ Load validated data into PostgreSQL
└─ Respect foreign key ordering (items → listings → sales)
└─ Dry-run mode: simulate without writing
└─ Execute mode: actual INSERT with transaction safety
Phase 4: SYNC OPERATIONAL DATA
└─ Pull "Prep de Pedidos" (order preparation) tab
└─ Match warehouse status to database records
└─ Update pick/pack states
Phase 5: CONTINUOUS SYNC
└─ Daily: Sheet → DB delta sync
└─ Handles new rows, updates, and edge cases
└─ Runs alongside the database as source of truth transitions
Les parties difficiles dont personne ne parle
1. La qualité des données n'est jamais celle qu'on imagine
Google Sheets est « flexible » — ce qui signifie que vos données sont un véritable fouillis. Voici ce que j'ai découvert en essayant d'analyser plus de 8 000 lignes d'inventaire :
- Des prix stockés sous forme de texte avec des symboles euro :
"12,50 €","12.50","12,5"et""(vide) — tous signifiant des choses différentes - Des dates dans 4 formats différents selon la personne qui les avait saisies et les paramètres de langue de son navigateur
- Des codes LPN (notre identifiant principal) avec une casse incohérente, des espaces en fin de chaîne et des doublons occasionnels
- Des champs de statut qui avaient évolué de manière organique :
"vendido","VENDIDO","Vendido (devuelto)","vendido?"
J'ai passé plus de temps à écrire du code de nettoyage et de normalisation des données que la logique de migration proprement dite. Chaque cas limite que je corrigeais en révélait deux autres.
2. Le rapprochement financier par camion
Nous achetons les liquidations Amazon au camion entier. Chaque camion possède un code (A2Z33838, A2Z34102, etc.) et un coût. À des fins fiscales, j'ai besoin de savoir, pour chaque camion : combien nous avons payé, combien nous avons vendu, ce qui reste en stock et quelle est la marge.
Dans Google Sheets, il s'agissait d'un onglet avec des formules SUMIFS. Dans PostgreSQL, j'ai dû reconstruire cela sous forme de vue (v_resumen_a2z) et valider que les chiffres correspondaient au centime près.
Truckload | Sheet Total | DB Total | Diff | Status
A2Z33838 | €14,230.50 | €14,230.50| €0.00 | ✓ MATCH
A2Z34102 | €8,445.00 | €8,445.00 | €0.00 | ✓ MATCH
A2Z34567 | €11,200.75 | €11,198.25| €2.50 | ✗ INVESTIGATE
| | | | → 1 sale missing
| | | | → LPN XU-20240312
J'ai généré des centaines de ces rapports de rapprochement. Chaque écart devait faire l'objet d'une enquête. La plupart étaient dus à des problèmes de synchronisation temporelle (une vente enregistrée dans la feuille mais pas encore extraite de l'API de Wallapop) ou à des corrections manuelles effectuées par mes employés directement dans la feuille.
3. La période d'exploitation en parallèle
Pendant environ 3 semaines, j'ai fait tourner les deux systèmes simultanément. Google Sheets restait la source de vérité pour mes employés, tandis que la base de données était alimentée et validée en arrière-plan.
Chaque matin :
- Lancer le script de synchronisation pour récupérer dans PostgreSQL les changements provenant de Sheets
- Consulter le rapport de rapprochement pour repérer les écarts
- Enquêter sur les problèmes et les corriger (en général 2 à 5 par jour)
- Tester les tableaux de bord Retool face aux données réelles
- Traiter les commandes de la journée (les véritables opérations de l'entreprise)
Chaque soir :
- Examiner ce que les employés avaient modifié dans Sheets durant la journée
- Relancer la synchronisation
- Vérifier que les totaux financiers concordaient toujours
C'était épuisant. Je faisais essentiellement tourner deux systèmes, j'assurais mon rôle de dirigeant d'entreprise ET je déboguais des cas limites de migration. Mais c'était la seule façon d'être en confiance avant la bascule.
4. Les scripts de correction
Aucune migration n'est parfaitement propre. J'ai écrit plus de 15 scripts de correction spécialisés pour les problèmes découverts pendant ou après la période de fonctionnement en parallèle :
fix_listing_prices_from_sheet.py— certains prix d'annonces dans la base provenaient de l'API de Wallapop (qui affiche le prix actuel), alors que la feuille contenait le prix d'annonce d'origine. Ils nécessitaient un rapprochementfix_cash_transaction_duplicates.py— des transactions en espèces saisies deux fois (une fois par le script de synchronisation, une fois par l'interface Retool après la bascule)fix_incidents_create_returned_sales.py— des retours qui existaient en tant qu'« incidents » dans Sheets mais qui nécessitaient des enregistrements de « vente » correspondants dans la base de donnéesborrar_ventas_no_cuadran_sheet.py— des ventes qui existaient dans la base mais sans correspondance dans la feuille (données fantômes issues des premiers bugs d'extraction de l'API)fix_zero_price_sales.py— des ventes importées avec un prix de 0,00 € à cause d'un bug de l'extracteur qui ne capturait pas le champ prix pour certains états de commande
Chaque script de correction suivait le même schéma : diagnostiquer → simuler (dry-run) → vérifier → exécuter → contrôler. Aucune correction à l'aveugle.
5. Expliquer à l'équipe
La partie la plus difficile n'était pas technique. C'était d'expliquer à 4 employés non techniques pourquoi nous changions leur façon de travailler.
« Le tableur fonctionne très bien » est une remarque parfaitement légitime de la part d'un opérateur d'entrepôt. Et de leur point de vue, il fonctionnait effectivement très bien. Ils ne voyaient pas les limites de débit de l'API, les risques de corruption des données, ni les scripts qui plantaient à 3 heures du matin à cause d'un recalcul de formule.
J'ai dû :
- Construire des interfaces Retool au moins aussi simples que le tableur (sinon l'adoption échouerait)
- Former chaque personne individuellement au nouveau flux de travail
- Maintenir la feuille accessible en lecture seule comme sauvegarde durant les 2 premières semaines suivant la bascule
- Être réactif face aux retours du type « ça ne marche pas comme avant » (qui étaient pour la plupart fondés)
La bascule
Après 3 semaines de fonctionnement en parallèle, plus de 200 rapports de rapprochement et 15 scripts de correction, j'ai documenté le plan de bascule :
CUTOVER CHECKLIST
─────────────────
[✓] All truckloads reconciled to €0.00 difference
[✓] All active listings matched between Sheet and DB
[✓] All pending orders present in both systems
[✓] Retool modules tested: orders, items, sales, invoicing
[✓] Employees trained on new workflow
[✓] Sheet set to read-only with banner: "USE RETOOL"
[✓] Automated extractors pointed to DB (not Sheet)
[✓] Backup snapshot of all table counts taken
[✓] Rollback procedure documented (Sheet restore)
La bascule elle-même fut décevante de banalité — ce qui est exactement ce que l'on recherche. J'ai fait pointer les extracteurs directement vers PostgreSQL en écriture, mis le Google Sheets en lecture seule et annoncé à mon équipe : « à partir d'aujourd'hui, utilisez Retool pour tout ».
Ce que j'ai construit (la stack technique)
La migration ne se résumait pas à des scripts. Elle a nécessité de bâtir l'intégralité de l'infrastructure cible :
- 16 fichiers DDL modulaires définissant le schéma PostgreSQL (tables, vues, vues matérialisées, fonctions, triggers)
- Plus de 35 fichiers de migration incrémentale pour l'évolution du schéma après la bascule
- Plus de 25 modèles de données SQLModel en Python pour le backend FastAPI
- Des vues matérialisées comme
mv_portalhero_feedpour la génération de flux marketplace - Des vues comme
v_resumen_a2zpour le reporting financier par camion - Des fonctions de trigger pour le suivi automatique des ventes et les mises à jour d'inventaire
Résultats
6 mois après la migration :
- Temps de requête : de plus de 15 secondes (chargement de la feuille) à moins de 50 ms (requête PostgreSQL)
- Concurrence : 5 scripts automatisés + 5 employés + tableaux de bord Retool, tous sollicitant la base simultanément sans aucun conflit
- Intégrité des données : les clés étrangères, contraintes et triggers détectent des erreurs qui auraient silencieusement corrompu la feuille
- Montée en charge opérationnelle : passage de Wallapop seul à Wallapop + eBay + PortalHero — chose qui aurait été impossible avec Sheets comme colonne vertébrale
- Exactitude financière : le rapprochement par camion qui me prenait auparavant 2 heures avec des SUMIFS s'exécute désormais en 3 secondes sous forme de vue matérialisée
Ce que je ferais différemment
Si c'était à refaire :
- Commencer avec la base de données dès le premier jour. La « dette » du « on migrera plus tard » est bien réelle et coûteuse. Même une simple configuration PostgreSQL + frontend ERP sur mesure dans Retool dès le départ m'aurait fait économiser des centaines d'heures
- Automatiser les contrôles de rapprochement au lieu de les exécuter manuellement chaque matin. Un pipeline de type CI qui ne m'envoie un e-mail que lorsqu'un écart est détecté
- Construire l'interface Retool d'abord, puis migrer. Je les ai construites en parallèle, ce qui signifie que les employés ont vu des interfaces à moitié terminées pendant la période de transition. Si l'interface avait été peaufinée avant la bascule, l'adoption aurait été plus fluide
L'enseignement à retenir
Migrer un système en production n'est pas un problème technique. C'est un problème opérationnel qui exige des solutions techniques. Le code représente peut-être 30 % du travail. Les 70 % restants, ce sont :
- Comprendre chaque cas limite de vos données existantes
- Bâtir la confiance par le fonctionnement en parallèle et le rapprochement
- Gérer le facteur humain — formation, communication, retours
- Avoir la discipline de ne pas bâcler quand on est épuisé et que le nombre d'écarts est enfin tombé à zéro
J'ai accompli tout cela en tant qu'ingénieur solo dirigeant une entreprise de 5 salariés. Pas d'équipe de DBA. Pas de QA dédiée. Juste de l'ingénierie méthodique et beaucoup de café.
Le pipeline de migration est open source : github.com/AspiranteD/sheets-to-postgres-migration