From 984ed4da5f7b84f58d669171c730a179e64a0f79 Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Wed, 18 Mar 2026 11:03:29 +0100 Subject: [PATCH] =?UTF-8?q?Fix=20sync:=20null=20bytes,=20BIT=E2=86=92SMALL?= =?UTF-8?q?INT,=20DECIMAL=20complex=20types,=20NbJours=20safe=20int,=20err?= =?UTF-8?q?or=5Fmsg=20column?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sync/server.js | 4 ++-- sync/tables/articles.js | 26 ++++++++++---------- sync/tables/fournisseurs.js | 6 ++--- sync/tables/stock.js | 36 ++++++++++++++-------------- sync/utils.js | 48 ++++++++++++++++++++++++++++++++++++- 5 files changed, 83 insertions(+), 37 deletions(-) diff --git a/sync/server.js b/sync/server.js index 7123abf..c22643e 100644 --- a/sync/server.js +++ b/sync/server.js @@ -61,7 +61,7 @@ app.get('/api/status', async (req, res) => { try { const pg = getPg(); const { rows } = await pg.query( - `SELECT table_name, last_sync, rows_synced, status, error_message + `SELECT table_name, last_sync, rows_synced, status, error_msg FROM sync_log ORDER BY table_name` ); res.json({ @@ -238,7 +238,7 @@ function renderStatus(data){ \${t.table_name} \${fmtDate(t.last_sync)} \${t.rows_synced ?? '—'} - \${t.status ?? '—'}\${t.error_message?' — '+t.error_message:''} + \${t.status ?? '—'}\${t.error_msg?' — '+t.error_msg:''} \`).join(''); } diff --git a/sync/tables/articles.js b/sync/tables/articles.js index c5b5944..6f98b3e 100644 --- a/sync/tables/articles.js +++ b/sync/tables/articles.js @@ -1,5 +1,5 @@ const { getMssql, getPg } = require('../db'); -const { batchUpsert, getLastSync, logSync } = require('../utils'); +const { batchUpsert, getLastSync, logSync, safeStr, safeBit } = require('../utils'); const ARTICLES_COLS = [ 'no_id','codein','libelle1','libelle2','lib_ticket', @@ -32,15 +32,15 @@ async function syncArticles(force) { const rows = res.recordset.map(r => ({ no_id: r.NO_ID, - codein: r.CODEIN, - libelle1: r.LIBELLE1, - libelle2: r.LIBELLE2, - lib_ticket: r.LIB_TICKET, - tax_code: r.TAX_CODE, - ach_code: r.ACH_CODE, - utilisable: r.UTILISABLE, - actif: r.ACTIF, - suspendu: r.SUSPENDU, + codein: safeStr(r.CODEIN), + libelle1: safeStr(r.LIBELLE1), + libelle2: safeStr(r.LIBELLE2), + lib_ticket: safeStr(r.LIB_TICKET), + tax_code: safeStr(r.TAX_CODE), + ach_code: safeStr(r.ACH_CODE), + utilisable: safeStr(r.UTILISABLE), + actif: safeStr(r.ACTIF), + suspendu: safeStr(r.SUSPENDU), suividatecreation: r.SUIVIDATECREATION, suividatemodif: r.SUIVIDATEMODIF, nom_no_id: r.NOM_NO_ID, @@ -102,9 +102,9 @@ async function syncArticles(force) { `SELECT IDARTICLE, GTIN, PREFERENTIEL FROM ART_GTIN` ); const rows = res.recordset.map(r => ({ - idarticle: r.IDARTICLE, - gtin: r.GTIN, - preferentiel: r.PREFERENTIEL, + idarticle: r.IDARTICLE, + gtin: safeStr(r.GTIN), + preferentiel: safeBit(r.PREFERENTIEL), })); const count = await batchUpsert(pg, 'art_gtin', rows, ['idarticle','gtin'], GTIN_COLS); await logSync(pg, 'art_gtin', count, 'ok'); diff --git a/sync/tables/fournisseurs.js b/sync/tables/fournisseurs.js index 8d47753..ac01131 100644 --- a/sync/tables/fournisseurs.js +++ b/sync/tables/fournisseurs.js @@ -1,5 +1,5 @@ const { getMssql, getPg } = require('../db'); -const { batchUpsert, fullRefresh, getLastSync, logSync } = require('../utils'); +const { batchUpsert, fullRefresh, getLastSync, logSync, safeDecimal } = require('../utils'); const ARTFOU1_COLS = [ 'no_id','art_no_id','code','reference','ean13','itf', @@ -58,8 +58,8 @@ async function syncFournisseurs(force) { ); const rows = res.recordset.map(r => ({ idartfou1: r.IDARTFOU1, - prixachat: r.PRIXACHAT, - remise_promotion: r.REMISE_PROMOTION, + prixachat: safeDecimal(r.PRIXACHAT), + remise_promotion: safeDecimal(r.REMISE_PROMOTION), })); const count = await batchUpsert(pg, 'artfou2', rows, ['idartfou1'], ARTFOU2_COLS); await logSync(pg, 'artfou2', count, 'ok'); diff --git a/sync/tables/stock.js b/sync/tables/stock.js index 0183f77..2da8a96 100644 --- a/sync/tables/stock.js +++ b/sync/tables/stock.js @@ -1,5 +1,5 @@ const { getMssql, getPg } = require('../db'); -const { fullRefresh, batchUpsert, logSync } = require('../utils'); +const { fullRefresh, batchUpsert, logSync, safeNum, safeInt, safeStr } = require('../utils'); const STOCK_COLS = [ 'artnoid','site','qte','prmp','valstock','pv', @@ -27,23 +27,23 @@ async function syncStock(force) { `); const rows = res.recordset.map(r => ({ - artnoid: r.ArtNoId, - site: r.Site, - qte: r.QTE, - prmp: r.Prmp, - valstock: r.ValStock, - pv: r.PV, - stockdispo: r.StockDispo, - stockmort: r.StockMort, - stockcolis: r.StockColis, - dernierevente: r.DerniereVente, - dernierereception: r.DerniereReception, - premierevente: r.PremiereVente, - nbjoursdernierMouvement: r.NbJoursDernierMouvement, - nbjoursdernierevente: r.NbJoursDerniereVente, - nbjoursdernierrereception: r.NbJoursDerniereReception, - interditachat: r.InterditAchat, - codefou: r.CODEFOU, + artnoid: r.ArtNoId, + site: safeStr(r.Site), + qte: safeNum(r.QTE), + prmp: safeNum(r.Prmp), + valstock: safeNum(r.ValStock), + pv: safeNum(r.PV), + stockdispo: safeNum(r.StockDispo), + stockmort: safeNum(r.StockMort), + stockcolis: safeNum(r.StockColis), + dernierevente: r.DerniereVente, + dernierereception: r.DerniereReception, + premierevente: r.PremiereVente, + nbjoursdernierMouvement: safeInt(r.NbJoursDernierMouvement), + nbjoursdernierevente: safeInt(r.NbJoursDerniereVente), + nbjoursdernierrereception: safeInt(r.NbJoursDerniereReception), + interditachat: safeStr(r.InterditAchat), + codefou: safeStr(r.CODEFOU), })); const count = await fullRefresh(pg, 'cube_stock', rows, STOCK_COLS); diff --git a/sync/utils.js b/sync/utils.js index c4a9197..867d0a9 100644 --- a/sync/utils.js +++ b/sync/utils.js @@ -88,4 +88,50 @@ async function logSync(pg, tableName, rowsSynced, status, errorMsg = null) { `, [tableName, rowsSynced, status, errorMsg]); } -module.exports = { batchUpsert, fullRefresh, getLastSync, logSync }; +// ── Helpers de conversion SQL Server → PostgreSQL ─────────── + +/** Nettoie une chaîne : supprime null bytes (0x00), retourne null si vide */ +function safeStr(v) { + if (v == null) return null; + return String(v).replace(/\0/g, '') || null; +} + +/** Convertit en nombre, retourne null si NaN ou non numérique */ +function safeNum(v) { + if (v == null) return null; + if (typeof v === 'number') return isNaN(v) ? null : v; + const n = parseFloat(String(v).replace(/[^0-9.\-]/g, '')); + return isNaN(n) ? null : n; +} + +/** Convertit en entier, retourne null si NaN */ +function safeInt(v) { + if (v == null) return null; + if (typeof v === 'number') return Math.round(v); + const n = parseInt(v, 10); + return isNaN(n) ? null : n; +} + +/** Convertit un BIT SQL Server (true/false) en SMALLINT (1/0) */ +function safeBit(v) { + if (v == null) return null; + return v ? 1 : 0; +} + +/** + * Convertit une valeur DECIMAL/MONEY qui peut être retournée par mssql + * sous forme d'objet, tableau, ou string "[date,valeur]". + */ +function safeDecimal(v) { + if (v == null) return null; + if (typeof v === 'number') return isNaN(v) ? null : v; + if (Array.isArray(v)) { + const last = v[v.length - 1]; + return typeof last === 'number' ? last : safeNum(last); + } + // ex: "[01/01/1901,163.000]" → 163 + const m = String(v).match(/([\d]+\.?[\d]*)[\]]*$/); + return m ? parseFloat(m[1]) : null; +} + +module.exports = { batchUpsert, fullRefresh, getLastSync, logSync, safeStr, safeNum, safeInt, safeBit, safeDecimal };