Demander un devis

Cas réel · migration rejouée

Cas pratique : migration d’un ERP WINDEV® 25 et HFSQL® vers React, Node.js et PostgreSQL

Un ERP de gestion développé en interne par une PME, dont nous avons rejoué la migration WINDEV de bout en bout. Cette page montre ce que nous avons tiré du dossier PDF, les correspondances et les points durs, puis la méthode suivie pour le code, la reprise des données et la recette.

Cadre de ce cas

Ce cas est réel : l’ERP existe et tourne aujourd’hui en WINDEV 25. Nous avons rejoué sa migration complète avec notre méthode, à partir du dossier PDF que WINDEV génère automatiquement. Le nom de l’entreprise n’est pas publié.

La version migrée n’est pas en service dans l’entreprise : vous lisez le travail réalisé et la méthode suivie, pas un retour d’utilisateurs.

Le cas en chiffres

L’essentiel
  • Pourquoi migrer : rester en WINDEV 25 fige l’ERP ; le recompiler avec la génération 2026 le fait entrer dans la redevance par session ; la facture électronique l’oblige à évoluer.
  • Comment : inventaire tiré du dossier PDF, traduction par l’IA, relecture et tests par nos développeurs seniors ; dans un projet, la recette se fait sur un carnet validé avec l’entreprise.
  • Pour les utilisateurs : par défaut, les mêmes écrans, les mêmes libellés et le même enchaînement, dans un navigateur.
  • Le prix : un forfait unique calculé sur l’inventaire, payé une fois, sans redevance de notre part.

Avant, après : la même fenêtre, dans le navigateur

Par défaut, chaque fenêtre garde sa disposition, ses libellés et son enchaînement. Seul l’habillage change, et l’application s’ouvre dans un navigateur.

Avant : Une fenêtre WINDEV 25

Migration rejouée par WinExit

Après : Le même écran, en React

Pour les utilisateurs, seul l’habillage change : 1 la commande et son client, 2 les lignes et leurs remises, 3 le total et le bouton Facturer restent au même endroit. Schéma d’une fenêtre de facturation : les écrans de l’ERP ne sont pas publiés. Le code derrière le bouton Facturer : avant et après.

Pourquoi migrer maintenant

L’ERP est en WINDEV 25, version annoncée par PC SOFT en octobre 2019, avec une base HFSQL. Pour lui, la redevance par session n’est pas l’argument principal : selon la Wx Alliance (22 septembre 2026), les applications compilées avec une version antérieure à la génération 2026 restent hors Sessions. Aujourd’hui, cet ERP ne paie aucune session. Le sujet est l’impasse.

Rester en WINDEV 25 avec dongleAucune redevance. Tant que le dongle de l’entreprise fonctionne, son équipe ouvre les sources en WINDEV 25. Mais depuis février 2025, PC SOFT ne vend plus de licence perpétuelle à dongle ; selon la Wx Alliance, il ne remplace plus un dongle perdu ou cassé. Chaque mise à jour de Windows se vérifie par des tests, sans nouvelle version possible du côté de WINDEV 25.
Faire évoluer l’ERP sur une version récenteIl faut l’abonnement : 1 068 € par an et par développeur. Passer à l’abonnement convertit le dongle, qui ne sert plus ensuite qu’aux versions 23 et antérieures (CGV PC SOFT, version 1.6 du 25 mars 2026). Et si l’ERP était recompilé avec la génération 2026, chaque session coûterait 290 € HT par an au tarif catalogue, en plus de l’abonnement. Une session = un exécutable, un utilisateur, une machine.
Migrer vers React, Node.js et PostgreSQLUn forfait unique, payé une fois, sans abonnement ni redevance de notre part. Le code est cédé en exclusivité au paiement intégral.

Recompiler en 2026 fait entrer l’ERP dans les Sessions ; ne pas recompiler le fige. Or un ERP doit évoluer : depuis le 1er septembre 2026, toute entreprise assujettie à la TVA établie en France doit pouvoir recevoir des factures électroniques, et une PME devra les émettre à partir du 1er septembre 2027 (voir la facture électronique).

Le plan de continuité annoncé par PC SOFT le 21 septembre 2026 ne concerne que les entreprises de moins de dix salariés. Montants et sources : prix WINDEV 2026.

Ce que nous avons reçu : le dossier PDF

WINDEV génère automatiquement la documentation d’un projet : l’analyse (le modèle de données), les fenêtres, le code, les états (les documents imprimés), les requêtes. Pour ce cas, tout le travail est parti de ce dossier PDF.

Partie du dossierCe que nous en avons tiré
Analyse : fichiers, rubriques, types, clés, liaisonsLe schéma PostgreSQL des 232 tables : types, clés primaires et étrangères, index, contraintes. Et le plan de reprise des données.
Fenêtres : image, champs et libellés, menus et raccourcis, code de la fenêtre et des champsLes écrans React des 120 fenêtres, aux mêmes libellés et au même enchaînement ; les règles de saisie, vérifiées dans le navigateur puis par l’API.
Procédures, classes, collections de procéduresLes services TypeScript de l’API, traduits des 110 000 lignes de WLangage.
RequêtesDu SQL paramétré et typé.
États : sources de données, ruptures, sous-étatsLes 10 états, en modèles de documents PDF générés côté serveur.

Un dossier PDF décrit le projet, pas ce qui vit autour. Voici ce qu’il ne contient pas, et comment nous le traitons :

Absent du dossierÀ quoi il sertComment nous le traitons
Les donnéesMesurer les volumes, trouver les anomalies, rapprocher après la repriseÉchantillon anonymisé de préférence ; sinon copie de la base sous NDA, avec un accord de sous-traitance RGPD (DPA) si elle contient des données personnelles
Les droits du groupware utilisateurReprendre les droits de chaque utilisateur, stockés dans des fichiers HFSQL hors du projetLus dans ces fichiers, puis repris en rôles
Les états personnels des utilisateurs, créés ou modifiés avec Etats & Requêtes (hors du projet)Garder ces états, qui vivent hors du projet : sans inventaire, ils disparaissentRecensés avec l’entreprise, poste par poste
Les matériels et imprimantes des postesPrévoir l’impression directe, les étiquettes, les codes-barres, la peséeListés avec l’entreprise pendant l’audit

Pour ce cas, le travail est parti du dossier PDF. Pour un devis ferme, l’audit part de l’export global du projet : architecture, code source, ressources.

La cible : architecture avant et après

Avant : WINDEV 25

  1. Postes Windows L’exécutable WINDEV 25 de l’ERP : fenêtres, états, impressions.
  2. Base HFSQL 232 tables, décrites dans l’analyse du projet.

Après : application web

  1. Navigateur React 19, TypeScript en mode strict. Rien à installer sur les postes, sauf un agent d’impression là où l’impression directe est nécessaire.
  2. API Node.js (version LTS) Services métier, droits, PDF, fichiers Excel et Word, tâches planifiées.
  3. PostgreSQL 18 Les 232 tables, leurs contraintes et les compteurs de factures.
InterfaceReact 19, TypeScript en mode strict, Vite, TanStack Query et TanStack Table, React Hook Form, Zod.
APINode.js (version LTS), Fastify, node-postgres. SQL explicite pour les traitements critiques (facturation, compteurs), Kysely pour les requêtes typées des écrans.
BasePostgreSQL 18.
Tâches planifiéespg-boss : file de tâches et planification dans PostgreSQL, sans serveur de plus.
DocumentsPDF rendus par Chromium sans interface, piloté par Playwright ; xlsx par exceljs ; docx par docxtemplater.

Ces briques sont open source, à une exception près : les modules images et tableaux de docxtemplater sont payants, si un modèle Word les demande. Aucune ne se paie à la session.

Objets WINDEV et types HFSQL : leurs équivalents

Objets WINDEV et équivalents React / Node.js

Dans WINDEV 25Dans la version web
FenêtrePage React avec sa route, ou boîte de dialogue. Par défaut, même disposition, mêmes libellés, même ordre de saisie.
FichierVersEcran, EcranVersFichierFormulaire React Hook Form relié à une API typée : lecture par GET, enregistrement par PUT.
Contrôles de saisie, champs obligatoires, masquesSchémas Zod partagés : vérifiés dans le navigateur, puis à nouveau par l’API.
Champ Table relié à un fichierTanStack Table. Tri, filtres et pagination exécutés par PostgreSQL, pagination par clé plutôt que par OFFSET.
Info, OuiNon, ErreurBoîtes de dialogue React, avec les mêmes messages.
Procédures globales, collections, classes, composants internesServices et modules TypeScript côté API.
Variables globales du projetContexte de session côté serveur (utilisateur, société, droits), passé explicitement aux services.
Requêtes (.WDR)SQL paramétré, typé avec Kysely.
Procédures stockées, triggers serveur, HAjoutePlanificationServices Node.js, SQL ou PL/pgSQL ; tâches pg-boss planifiées.
États (.WDE), iImprimeEtatPDF générés côté serveur : HTML et CSS rendus par Chromium sans interface.
TableVersExcel, automation OLE de Word et ExcelFichiers xlsx et docx générés côté serveur, sans Office sur le poste.
Groupware utilisateurRôles et permissions vérifiés par l’API ; authentification unique et double authentification en option.

Types HFSQL et types PostgreSQL

Rubrique HFSQLPostgreSQL 18À contrôler
Identifiant automatique, 4 ou 8 octetsbigint GENERATED BY DEFAULT AS IDENTITYIdentifiants d’origine conservés, puis séquence recalée par setval.
Entier sur 1, 2, 4 ou 8 octetssmallint, integer, bigintEntier sans signe : type immédiatement supérieur (sur 8 octets : numeric(20,0)).
Monétairenumeric(24,6), ou numeric(14,2) si la règle métier le permetDécimal exact : 24 chiffres significatifs, dont 6 décimales au plus. Jamais de double precision.
Réel sur 4 ou 8 octetsreal, double precisionRéservé aux grandeurs non monétaires : poids, dimensions.
Texte ANSI ou Unicodevarchar(n) ou text, en UTF-8Choisi rubrique par rubrique dans l’analyse. ANSI décodé en Windows-1252, pas en ISO-8859-1, qui n’a ni « € » ni « œ ».
Date (AAAAMMJJ)dateDates non renseignées (des valeurs vides plutôt que NULL) et dates incohérentes repérées, puis converties en NULL.
Heure (HHMMSSCC)time(2)Centièmes de seconde conservés.
Date et heure, à la millisecondetimestamptz(3)Valeurs sans fuseau, interprétées à l’heure de Paris.
Mémo texte, mémo binairetext, bytea ou stockage objetContenu lu dans le fichier .MMO, à côté du .FIC.
Clé composéeIndex multicolonne, UNIQUE si la clé est uniqueColonnes dans le même ordre que la clé d’origine.
Clé texte insensible à la casse, aux accents ou à la ponctuationCollation ICU non déterministe, ou colonne normalisée indexéeVoir les points durs.
Liaison avec intégrité référentielleFOREIGN KEYPosée après le traitement des orphelins.

Côté Node.js, node-postgres a trois comportements par défaut à connaître. Un bigint arrive en chaîne : l’identifiant reste une chaîne. Un numeric arrive aussi en chaîne : le montant se calcule en SQL ou en décimal exact, jamais en Number. Une date devient un objet Date fixé à minuit, heure locale du processus Node.js, sauf avec setTypeParser(1082), qui la garde en « AAAA-MM-JJ ».

Après : schéma PostgreSQL d’une table reprise

-- Clé insensible à la casse et aux accents, comme la clé d'origine
CREATE COLLATION ci_ai (provider = icu, deterministic = false, locale = 'und-u-ks-level1');

CREATE TABLE customer (
  id           bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, -- identifiant automatique repris
  name         text COLLATE ci_ai NOT NULL,
  siren        text,                              -- NULL si inconnu, et non plus chaîne vide
  credit_limit numeric(24,6) NOT NULL DEFAULT 0,  -- monétaire, décimal exact
  version      integer NOT NULL DEFAULT 1         -- verrou optimiste
);
CREATE INDEX customer_name_idx ON customer (name);

-- Après l'import des identifiants d'origine : la séquence repart après le plus grand
SELECT setval(pg_get_serial_sequence('customer', 'id'), (SELECT max(id) FROM customer));

Les points durs d’un ERP WINDEV, et ce que nous en faisons

Point durWINDEV 25 et HFSQLVersion web
Numérotation continue des facturesCompteur lu avec hBlocageEcriture et incrémenté dans la transaction : il reste bloqué jusqu’à HTransactionFin.Une ligne par série dans une table de compteurs, incrémentée par UPDATE … RETURNING dans la même transaction que la facture. Pas de SEQUENCE : la documentation PostgreSQL prévient qu’elle peut laisser des trous, car nextval n’est pas annulé au ROLLBACK. Contrepartie : les facturations passent une par une. Aucune facture n’est supprimée ensuite : on émet un avoir.
Montants et arrondisMonétaire : décimal exact. Arrondi(x, 2) arrondit 0,5 vers le haut.numeric de bout en bout. round(numeric, 2) départage les égalités en s’éloignant de zéro : même résultat sur les montants positifs ; les négatifs (avoirs, remises) passent par des tests de non-régression. En SQL, 15 / 100 vaut 0 : on écrit / 100.0.
BlocagesBlocage pessimiste (hBlocageEcriture, hBlocageLectureEcriture), réessayé H.NbEssais fois, puis fenêtre « réessayer / annuler ».Une application web ne garde pas de verrou pendant la saisie. Pour la saisie : verrou optimiste par colonne version, conflit signalé à l’utilisateur. Pour les traitements : verrous courts, SELECT … FOR UPDATE dans une transaction.
Isolation des transactionsEn HFSQL Client/Serveur, isolation par défaut READ UNCOMMITTED : un autre poste peut lire les écritures d’une transaction pas encore validée.READ COMMITTED par défaut : une lecture ne voit que des données validées. Les calculs qui lisent des données en cours de transaction sont revérifiés par des tests.
Erreurs pendant une transactionLa gestion assistée des erreurs HFSQL peut ouvrir une fenêtre en pleine transaction, verrous posés.Toute erreur lève une exception : ROLLBACK, verrous libérés, message à l’utilisateur.
Absence de valeurLe NULL n’existe que si le fichier a « NULL supporté » et la rubrique « NULL autorisé ». Ailleurs, l’absence de valeur est une chaîne vide, une date vide ou 0.Conversion en NULL à la reprise, règle par règle. Un test SI Client.DateRelance = "" devient IS NULL. Les 0 qui ont un sens métier restent des 0.
Clés insensiblesClés texte sensibles ou non à la casse, aux accents, à la ponctuation. Sur une clé sensible à la casse, « DUPONT » ne trouve pas « Dupont ».PostgreSQL compare au caractère près. Choix clé par clé : collation ICU non déterministe, plus lente, qui accepte LIKE depuis PostgreSQL 18 mais ni ILIKE ni les expressions régulières ; ou colonne normalisée indexée.
OU et ETEn WINDEV 25, OU et ET évaluent toujours les deux opérandes. Seuls _OU_ et _ET_ s’arrêtent au premier.|| et && s’arrêtent dès que le résultat est connu. Chaque condition dont le second opérande a un effet (appel de procédure, lecture HFSQL qui déplace l’enregistrement en cours, écriture) est réécrite : voir l’exemple plus bas.
Date du jourDateDuJour() renvoie la date du poste client.Date du serveur, avec un fuseau horaire explicite (Europe/Paris). Un poste mal réglé ne change plus la date d’une facture.
Traitements dans la baseEn HFSQL Client/Serveur : procédures stockées et triggers serveur écrits en WLangage, tâches planifiées du serveur.Ils ne fonctionnent pas sur PostgreSQL. Réécrits un par un : une règle de cohérence des données va dans la base (contrainte ou trigger PL/pgSQL) ; une règle métier va dans l’API, où elle se teste ; une tâche planifiée va dans pg-boss.
Intégrité référentielleDécrite sur les liaisons de l’analyse, désactivable par programmation (HGèreIntégrité, hIgnoreIntégrité).FOREIGN KEY posées après la reprise, une fois les orphelins traités.
Plusieurs sociétés dans la même baseLe filtre sur la société dépend du code de chaque requête.Sécurité au niveau des lignes (Row-Level Security) : la société courante est posée à chaque transaction ; le rôle de l’application n’est pas superutilisateur et n’a ni la propriété des tables ni l’attribut BYPASSRLS. Un filtre oublié ne montre plus les données d’une autre société.

Le code, avant et après

Prenons une procédure présente dans la plupart des ERP de gestion : la facturation d’une commande. Le code de l’ERP n’est pas publié : celui-ci, écrit pour cette page et ramené à l’essentiel, montre les mécanismes WLangage qu’une migration doit traduire.

Avant : WLangage

PROCÉDURE FacturerCommande(nIDCommande est un entier sur 8 octets)
moTotalHT est un monétaire = 0
HTransactionDébut()
// Commande bloquée en écriture : un autre poste ne peut pas la facturer en même temps
HLitRecherchePremier(Commande, IDCommande, nIDCommande, hBlocageEcriture)
SI (PAS HTrouve(Commande)) _OU_ (Commande.Etat <> "VALIDEE") ALORS
	HTransactionAnnule()
	RENVOYER Faux
FIN
// Numéro continu : le compteur reste bloqué jusqu'à la fin de la transaction
HLitRecherchePremier(Compteur, Code, "FACTURE", hBlocageEcriture)
Compteur.Valeur++
HModifie(Compteur)
HLitRecherchePremier(LigneCmd, IDCommande, nIDCommande)
TANTQUE HTrouve(LigneCmd)
	moTotalHT += Arrondi(LigneCmd.PrixHT * LigneCmd.Qte * (1 - LigneCmd.Remise / 100), 2)
	HLitSuivant(LigneCmd, IDCommande)
FIN
HRAZ(Facture)
Facture.NumFacture = Compteur.Valeur
Facture.IDCommande = nIDCommande
Facture.DateFacture = DateDuJour()
Facture.TotalHT = moTotalHT
HAjoute(Facture)
Commande.Etat = "FACTUREE"
HModifie(Commande)
HTransactionFin()
RENVOYER Vrai

Après : TypeScript / Node.js

// Codes d'état repris de la base d'origine, sans traduction
export async function invoiceOrder(orderId: string): Promise<boolean> {
  return withTransaction(async (tx) => {
    // Une facturation concurrente attend ce verrou, puis relit 'FACTUREE' et s'arrête
    const { rows: [order] } = await tx.query<{ status: string }>(
      'SELECT status FROM sales_order WHERE id = $1 FOR UPDATE', [orderId]);
    if (!order || order.status !== 'VALIDEE') return false;
    // Total HT en numeric (décimal exact), arrondi ligne par ligne comme dans WINDEV
    const { rows: [sum] } = await tx.query<{ total: string }>(
      `SELECT COALESCE(SUM(ROUND(unit_price * quantity * (1 - discount / 100.0), 2)), 0) AS total
         FROM order_line WHERE order_id = $1`, [orderId]);
    // Numéro continu : pas de SEQUENCE (elle laisse des trous) ; ligne verrouillée jusqu'au COMMIT
    const { rows: [counter] } = await tx.query<{ value: string }>(
      "UPDATE counter SET value = value + 1 WHERE code = 'FACTURE' RETURNING value");
    if (!counter) throw new Error('Compteur FACTURE absent');
    // Date du serveur à l'heure de Paris, prise après l'obtention du numéro
    await tx.query(
      `INSERT INTO invoice (number, order_id, invoice_date, total_excl_tax)
       VALUES ($1, $2, (statement_timestamp() AT TIME ZONE 'Europe/Paris')::date, $3)`,
      [counter.value, orderId, sum.total]);
    await tx.query("UPDATE sales_order SET status = 'FACTUREE' WHERE id = $1", [orderId]);
    return true;
  });
}

Après : la transaction, un seul client du pool

import type { PoolClient } from 'pg';
import { pool } from './db'; // pool partagé de l'application (new Pool())

export async function withTransaction<T>(work: (tx: PoolClient) => Promise<T>): Promise<T> {
  const client = await pool.connect();
  let broken = false;
  try {
    await client.query('BEGIN');
    const result = await work(client);
    await client.query('COMMIT');
    return result;
  } catch (err) {
    // ROLLBACK impossible : la connexion est détruite au lieu de revenir dans le pool
    await client.query('ROLLBACK').catch(() => { broken = true; });
    throw err;
  } finally {
    client.release(broken);
  }
}
WLangageTypeScript / PostgreSQL
HTransactionDébut, HTransactionFin, HTransactionAnnulewithTransaction : un seul client, BEGIN, puis COMMIT, ou ROLLBACK à la première exception.
HLitRecherchePremier(…, hBlocageEcriture)SELECT … FOR UPDATE : le second poste attend, relit l’état « FACTUREE » et s’arrête.
Boucle TANTQUE / HLitSuivantUne seule requête SUM, avec l’arrondi ligne par ligne d’origine.
Compteur bloqué puis HModifieUPDATE counter … RETURNING, annulé avec la transaction en cas d’échec.
DateDuJour()Date du serveur, fuseau Europe/Paris, lue après l’obtention du numéro : les dates suivent l’ordre des numéros.
_OU_||, qui s’arrête aussi au premier opérande vrai.

Un relecteur WINDEV le remarquera : dans le code WLangage ci-dessus, les résultats de HModifie et HAjoute ne sont pas testés, et la gestion assistée des erreurs HFSQL peut ouvrir une fenêtre pendant la transaction. La version TypeScript annule la transaction à la première erreur.

Quand OU appelle une procédure

Avant : WLangage

// OU évalue les deux opérandes : EnvoieAlerte() s'exécute même si EstAJour() renvoie Vrai
SI EstAJour(nIDClient) OU EnvoieAlerte(nIDClient) ALORS
	Trace("Client traité")
FIN

Après : TypeScript / Node.js

// || s'arrêterait au premier opérande vrai : l'appel sort de la condition pour garder le comportement
const upToDate = await isUpToDate(customerId);
const alertSent = await sendAlert(customerId);
if (upToDate || alertSent) log.info('Client traité');

Saisie concurrente d’une fiche : le verrou optimiste

Quand une application WINDEV bloque une fiche à l’ouverture (hBlocageEcriture), un second poste qui veut la modifier attend, puis voit la fenêtre « réessayer / annuler ». Dans la version web, personne n’est bloqué : le second enregistrement est refusé, et l’utilisateur recharge la fiche.

Après : TypeScript / Node.js

// La fiche porte un numéro de version, lu à l'ouverture et renvoyé à l'enregistrement
const { rowCount } = await db.query(
  `UPDATE customer SET name = $2, siren = $3, version = version + 1
    WHERE id = $1 AND version = $4`,
  [id, name, siren, version]);
// Aucune ligne modifiée : un autre utilisateur a enregistré la fiche entre-temps (HTTP 409)
if (!rowCount) throw new ConflictError('Fiche modifiée par un autre utilisateur : rechargez-la');

Ce qui ne se migre pas tel quel, et ce que nous faisons à la place

Dans un ERP WINDEVPourquoiÀ la place
Impression directe, sans boîte de dialogueUn navigateur n’imprime pas sans boîte de dialogue par défaut.PDF générés côté serveur. Pour l’impression directe et les étiquettes : un agent local sur les postes concernés.
Raccourcis clavierLe navigateur se réserve certaines combinaisons, comme Ctrl+W, Ctrl+T ou Ctrl+N.Touches de fonction (F1 à F12), Alt + lettre, Entrée et Tab reprises ; les combinaisons réservées sont remplacées par d’autres touches, listées dans la documentation.
Fonctionnalités automatiques du champ Table : tri, filtres, export Excel ou Word, impressionWINDEV les fournit sans code. Une page web ne les a pas d’office.Recréées sur chaque liste : tri et filtres par PostgreSQL, export xlsx, impression en PDF.
États modifiés par les utilisateurs avec Etats & RequêtesIls vivent hors du projet.Recensés, puis repris comme états serveur.
Automation OLE de Word et ExcelIl faut Office installé sur le poste.Fichiers docx et xlsx générés côté serveur, sans Office.
DouchettesElles fonctionnent en émulation clavier.Rien à changer : tous les navigateurs les acceptent.
Balance ou matériel sur port sérieLe navigateur n’accède au port série que par Web Serial.Web Serial, sur les navigateurs Chromium de bureau seulement ; sinon, agent local.
Groupware : droits sur fenêtres, champs, menusStockés dans des fichiers HFSQL, appliqués par l’exécutable du poste.Rôles et permissions vérifiés par l’API : l’écran masque, l’API refuse.

La reprise des données de HFSQL vers PostgreSQL

Le dossier PDF décrit les 232 tables, pas leur contenu : la reprise se prépare avec les données, transmises sous NDA, et sous DPA si elles contiennent des données personnelles. Elle suit cinq étapes, rejouables autant de fois que nécessaire.

  1. Extraction. Lecture des fichiers par le pilote ODBC HFSQL que PC SOFT fournit pour Windows et Linux 64 bits. Aucune écriture dans la base d’origine.
  2. Transformation. Textes ANSI décodés en Windows-1252, absences de valeur converties en NULL, dates incohérentes isolées, codes métier conservés tels quels.
  3. Chargement. Identifiants d’origine conservés, séquences recalées, compteurs de factures repris à leur dernière valeur.
  4. Contraintes. Clés étrangères, contraintes d’unicité et index posés après le chargement. Avant chaque contrainte, une requête liste les lignes qui la violeraient (orphelins, doublons), car PostgreSQL refuse la contrainte entière dès la première violation.
  5. Rapprochement. Contrôles automatiques entre la base HFSQL et PostgreSQL. Si l’un d’eux échoue, la reprise est refusée.

Chaque anomalie que la reprise détecte (date vide ou incohérente, orphelin, doublon révélé par une clé insensible, texte mal encodé) est listée et remise à l’entreprise. Nous ne fusionnons ni ne supprimons rien d’office : modifier l’historique est sa décision.

ContrôleMéthode
ComptagesEnregistrements par fichier HFSQL, lignes par table PostgreSQL.
SommesTotaux HT, TVA et TTC des factures, par exercice et par mois.
Recalcul de l’historiqueFactures historiques recalculées par le nouveau code, comparées au centime aux totaux enregistrés par WINDEV.
En-coursEncours clients, commandes non facturées, stock valorisé.
NumérotationContinuité de chaque série de factures : ni trou ni doublon.
FEC (fichier des écritures comptables)Si l’ERP tient la comptabilité : FEC d’un exercice produit par la version web, comparé aux écritures de la base HFSQL ; structure contrôlée avec l’outil Test Compta Demat de la DGFiP, qui vérifie la forme, pas le fond.

Le carnet de recette

Les projets WINDEV anciens ont rarement une documentation fonctionnelle : le dossier PDF décrit l’analyse, les fenêtres et le code, pas ce que chaque écran doit faire pour l’utilisateur. L’IA reconstitue cette documentation écran par écran, et nous en tirons un carnet de recette : des lignes vérifiables, à faire valider par les référents de l’entreprise. Le carnet pilote les tests automatisés, puis la recette des utilisateurs. Voici les scénarios que nous écrivons pour les points durs d’un ERP :

ModuleScénarioRésultat attendu
FacturationDeux postes facturent la même commande au même instantUne seule facture ; le second poste lit « commande déjà facturée »
FacturationCoupure de la base au milieu d’une facturationAucune facture, aucun numéro consommé, série continue
FacturationFacture avec remises, puis avoir partielTotaux HT, TVA et TTC égaux à ceux de l’ERP WINDEV, au centime près
FacturationFacture validée à 23 h 30, heure de Paris, depuis un poste mal régléDate de facture du jour, à Paris
ClientsRecherche « dupont », « DUPONT », « Dupont »Les mêmes fiches qu’avec la clé HFSQL
ClientsDeux utilisateurs enregistrent la même ficheLe second est prévenu ; aucune saisie perdue en silence
ListesTri, filtre, puis export ExcelMêmes lignes, même ordre ; fichier xlsx produit sans Office
DroitsUtilisateur sans droit sur les tarifs, qui appelle l’API directementÉcran masqué, appel refusé (HTTP 403)
ComptabilitéFEC d’un exercice18 champs, structure acceptée par Test Compta Demat

Quand la recette révèle une anomalie de l’ERP d’origine, la version migrée reproduit d’abord le comportement d’origine, hors changements décrits plus haut (date du serveur, verrous, traitement des erreurs). Toute autre correction est une décision de l’entreprise, notée dans le carnet.

La facture électronique, à intégrer à la migration

Une PME doit pouvoir recevoir des factures électroniques depuis le 1er septembre 2026. Elle devra les émettre, et transmettre ses données de transaction (e-reporting), à partir du 1er septembre 2027. Les grandes entreprises et les ETI le font depuis le 1er septembre 2026. Ces fonctions peuvent être intégrées à la migration et chiffrées dans le devis ferme, au lieu d’être développées en WLangage.

ObligationRéférenceÀ intégrer à la migration
Recevoir et émettrePlateforme agréée ; formats UBL, CII et Factur-X (PDF/A-3 et XML CII), norme EN 16931.Émettre et recevoir des factures UBL, CII ou Factur-X, échangées avec la plateforme agréée choisie par l’entreprise. Seules les plateformes sont agréées : un ERP ne l’est pas.
Nouvelles mentionsCGI, annexe II, art. 242 nonies ASIREN du client, adresse de livraison si elle diffère, nature des opérations, option pour la TVA d’après les débits : champs à ajouter au schéma et aux factures.
Numérotation« Un numéro unique basé sur une séquence chronologique et continue » (art. 242 nonies A, I, 7°) ; séries distinctes possibles si elles sont justifiées.Compteur par série, dans la transaction de facturation.
Références de TVALa TVA passe du CGI au code des impositions sur les biens et services (CIBS) au 1er janvier 2027 : les articles cités sur les factures changent.Mentions légales paramétrables, plutôt que figées dans les états.
FECLPF, art. L47 A (remise au contrôle) et A47 A-1 (format, 18 champs).Export FEC, structure contrôlée avec Test Compta Demat.
Conservation et corrections6 ans (LPF, art. L102 B), 10 ans (code de commerce, art. L123-22).Factures émises archivées ; écritures validées non modifiables, corrigées par contre-passation ; facture corrigée par un avoir.
Pénalités de retardCode de commerce, art. L441-9 et L441-10 ; indemnité forfaitaire de 40 € (art. D441-5).Mentions paramétrées dans les conditions de paiement.

État du droit au 23 octobre 2026, d’après Légifrance. Votre expert-comptable valide le paramétrage retenu.

Lots et bascule

Au-delà de 30 fenêtres, la migration se découpe en lots indépendants au sens du contrat, chacun au forfait : avec 120 fenêtres, cet ERP serait découpé ainsi. Mais dans un ERP, les modules écrivent dans les mêmes tables : une commande touche le stock, la facture et la comptabilité. Un module ne peut donc pas basculer seul sans double saisie, ou sans une synchronisation dans les deux sens, plus coûteuse et plus fragile. D’où notre plan par défaut : les lots sont réalisés et recettés l’un après l’autre, sur une copie des données, puis l’ERP bascule en une fois.

  1. Jusqu’à la bascule, HFSQL fait foi. L’ERP WINDEV reste l’outil de travail, et la facturation continue. Aucune double saisie.
  2. Bascule. Hors période de clôture : saisies gelées, reprise finale, rapprochement complet. La facturation ne s’interrompt que pendant la reprise et les contrôles.
  3. Continuité des numéros. Les compteurs sont repris à leur dernière valeur : la première facture de la version web suit la dernière facture WINDEV.
  4. Ouverture. PostgreSQL fait foi. L’ERP WINDEV reste consultable en lecture seule.
  5. Retour arrière. Si un contrôle échoue, l’ERP WINDEV reprend la main sur une base HFSQL restée intacte. Ce retour reste possible jusqu’à la première facture émise par la version web ; ensuite, les corrections se font dans la version web.

Pour les utilisateurs : ce qui reste, ce qui change

ÉcransDisposition, libellés et enchaînement repris par défaut. La refonte des écrans reste une option.
ClavierTouches de fonction (F1 à F12), Alt + lettre, Entrée et Tab reprises, sauf les combinaisons réservées par le navigateur.
ListesChargées page par page, triées et filtrées par PostgreSQL.
AccèsUn navigateur, au bureau ou en déplacement. Double authentification en option.
Mises à jourUn déploiement sur le serveur, sans rien installer sur les postes.

Ce que l’entreprise récupérerait

Les données seraient dans une base ouverte, lisibles par les outils standards, et le code en TypeScript : l’équipe de l’entreprise, la nôtre ou un autre prestataire pourrait le maintenir.

Le prix, comparé au fait de rester

Le prix est un forfait unique calculé sur l’inventaire complet : fenêtres, lignes, tables, mais aussi états, tâches planifiées et interfaces. Il est payé une fois, en trois échéances : 30 % à la commande, 40 % à la livraison pour recette, 30 % à la validation de la recette.

PosteMigrerFaire évoluer en génération 2026
Une foisLe forfait de migration, par lotsLe passage du projet en génération 2026, puis la recette de l’ERP recompilé
Chaque annéeL’hébergement et la maintenance, optionnels et résiliables chez nousL’abonnement, 1 068 € par développeur, et 290 € HT par session au tarif catalogue
Temps de l’entrepriseLa recette par les utilisateurs clés, la formationUne recette à chaque nouvelle génération
Facture électroniqueIntégrable au projet, chiffrée dans le devis fermeÀ développer en WLangage

Rester en WINDEV 25 ne coûte rien en licences aujourd’hui, mais fige l’ERP. Pour chiffrer votre propre application, le simulateur donne une fourchette en 2 minutes.

Avant le devis : NDA mutuel, audit gratuit, devis ferme 48 h ouvrées après réception de l’export. Preuve de concept en option, de 2 000 à 3 000 € HT, déduite si la migration est validée sous 30 jours. Forfaits dès 5 k€ HT. Tarifs PC SOFT en engagement de trois ans : prix WINDEV 2026.

Ce que ce cas ne dit pas

Votre ERP est en WINDEV ? Commencez par l’audit gratuit

Estimez votre migration en 2 minutes, demandez l’audit gratuit ou prenez 30 minutes pour en parler avec nous.

Estimer ma migration en 2 minutes Demander mon devis gratuit Réserver 30 minutes en visio (nouvel onglet)

À lire aussi : notre méthode, étape par étape · garder ou quitter HFSQL · migrer vers React / Node.js · faut-il quitter WINDEV ? · l’équipe