From 3f23f181f4f1f93185616e3add91f6340a0fdaeb Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Mon, 30 Mar 2026 12:43:41 +0200 Subject: [PATCH] feat: Implement initial PostgreSQL schema and synchronization scripts for various data tables. --- postgres/init.sql | 82 ++++++++++++++++++++ sync/index.js | 2 + sync/server.js | 3 +- sync/tables/appro.js | 173 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 sync/tables/appro.js diff --git a/postgres/init.sql b/postgres/init.sql index 2cb11f5..d43e51f 100644 --- a/postgres/init.sql +++ b/postgres/init.sql @@ -370,3 +370,85 @@ CREATE TABLE IF NOT EXISTS phenix_quantite_conseille ( CREATE INDEX IF NOT EXISTS idx_phenix_cde_site ON phenix_quantite_conseille (site); CREATE INDEX IF NOT EXISTS idx_phenix_cde_codein ON phenix_quantite_conseille (codein); CREATE INDEX IF NOT EXISTS idx_phenix_cde_artfou1 ON phenix_quantite_conseille (idartfou1); + +-- ============================================================ +-- Commandes fournisseurs historique + réapprovisionnement +-- ============================================================ + +CREATE TABLE IF NOT EXISTS commande_fou ( + no_id BIGINT PRIMARY KEY, + cdenum TEXT, + sit_cod_emet TEXT, + sit_cod_desti TEXT, + fouident_code TEXT, + foutarif_no_id BIGINT, + port_code TEXT, + cdedate TIMESTAMP, + cdeetat TEXT, + cdetot NUMERIC(14,4), + cdetard TIMESTAMP, + cdeportmontant NUMERIC(12,4), + cdeannulation TEXT, + suividatecreation TIMESTAMP, + suividatemodif TIMESTAMP +); +CREATE INDEX IF NOT EXISTS idx_commande_fou_fouident ON commande_fou (fouident_code); +CREATE INDEX IF NOT EXISTS idx_commande_fou_date ON commande_fou (cdedate); +CREATE INDEX IF NOT EXISTS idx_commande_fou_etat ON commande_fou (cdeetat); + +CREATE TABLE IF NOT EXISTS cdefou_ligne ( + no_id BIGINT PRIMARY KEY, + artfou1_no_id BIGINT, + commande_fou_no_id BIGINT, + cdelig INTEGER, + cdeligtard TIMESTAMP, + cdeligtot TIMESTAMP, + prixbrut NUMERIC(12,4), + remise NUMERIC(10,4), + prixnet NUMERIC(12,4), + qtecde NUMERIC(12,3), + gratuit NUMERIC(12,3), + montant NUMERIC(14,4), + qteacc NUMERIC(12,3), + qteann NUMERIC(12,3), + qteatt NUMERIC(12,3), + qterel NUMERIC(12,3), + colisage NUMERIC(10,3), + suividatecreation TIMESTAMP, + suividatemodif TIMESTAMP +); +CREATE INDEX IF NOT EXISTS idx_cdefou_ligne_artfou1 ON cdefou_ligne (artfou1_no_id); +CREATE INDEX IF NOT EXISTS idx_cdefou_ligne_commande ON cdefou_ligne (commande_fou_no_id); + +CREATE TABLE IF NOT EXISTS commande_auto_qtepropo ( + fou_code TEXT, + art_no_id BIGINT, + codesite TEXT, + qtepropo NUMERIC(12,3), + parametre_numero INTEGER, + suividatecreation TIMESTAMP, + suividatemodif TIMESTAMP, + PRIMARY KEY (fou_code, art_no_id, codesite) +); +CREATE INDEX IF NOT EXISTS idx_caqp_art_no_id ON commande_auto_qtepropo (art_no_id); +CREATE INDEX IF NOT EXISTS idx_caqp_codesite ON commande_auto_qtepropo (codesite); + +CREATE TABLE IF NOT EXISTS plan_reappro ( + id INTEGER PRIMARY KEY, + magasin TEXT, + codein TEXT, + bu_no_id BIGINT, + stockmag NUMERIC(12,3), + encours_palette_mag NUMERIC(12,3), + attente_prepa_mag NUMERIC(12,3), + colis_rea NUMERIC(12,3), + colis_ajout_web NUMERIC(12,3), + stock_dispo NUMERIC(12,3), + attente_prepa_depot NUMERIC(12,3), + stock_dispo_calcul NUMERIC(12,3), + colisage NUMERIC(10,3), + bloque TEXT, + date_besoin TIMESTAMP +); +CREATE INDEX IF NOT EXISTS idx_plan_reappro_magasin ON plan_reappro (magasin); +CREATE INDEX IF NOT EXISTS idx_plan_reappro_codein ON plan_reappro (codein); diff --git a/sync/index.js b/sync/index.js index 3acfd3a..15b088e 100644 --- a/sync/index.js +++ b/sync/index.js @@ -10,6 +10,7 @@ const { syncReferentiel } = require('./tables/referentiel'); const { syncRanking } = require('./tables/ranking'); const { syncStatDispoperm } = require('./tables/stat_dispoperm'); const { syncPhenixQuantiteConseille } = require('./tables/phenix_quantite_conseille'); +const { syncAppro } = require('./tables/appro'); const { closeAll } = require('./db'); const { startDashboard, registerSyncFn, @@ -31,6 +32,7 @@ const STEPS = [ { name: 'ranking', fn: syncRanking }, { name: 'stat_dispoperm', fn: syncStatDispoperm }, { name: 'phenix_quantite_conseille', fn: syncPhenixQuantiteConseille }, + { name: 'appro', fn: syncAppro }, ]; async function syncAll(force = false) { diff --git a/sync/server.js b/sync/server.js index 480ab9f..2ddf952 100644 --- a/sync/server.js +++ b/sync/server.js @@ -16,7 +16,8 @@ const state = { }; const TABLES_ORDER = [ - 'referentiel', 'articles', 'fournisseurs', 'stock', 'mouvements', 'commandes', 'ranking' + 'referentiel', 'articles', 'fournisseurs', 'stock', 'mouvements', 'commandes', + 'ranking', 'stat_dispoperm', 'phenix_quantite_conseille', 'appro' ]; const TABLES_TOTAL = TABLES_ORDER.length; diff --git a/sync/tables/appro.js b/sync/tables/appro.js new file mode 100644 index 0000000..75fac90 --- /dev/null +++ b/sync/tables/appro.js @@ -0,0 +1,173 @@ +const { getMssql, getPg } = require('../db'); +const { batchUpsert, fullRefresh, getLastSync, logSync } = require('../utils'); + +const COMMANDE_FOU_COLS = [ + 'no_id','cdenum','sit_cod_emet','sit_cod_desti','fouident_code', + 'foutarif_no_id','port_code','cdedate','cdeetat','cdetot', + 'cdetard','cdeportmontant','cdeannulation','suividatecreation','suividatemodif', +]; + +const CDEFOU_LIGNE_COLS = [ + 'no_id','artfou1_no_id','commande_fou_no_id','cdelig','cdeligtard','cdeligtot', + 'prixbrut','remise','prixnet','qtecde','gratuit','montant', + 'qteacc','qteann','qteatt','qterel','colisage','suividatecreation','suividatemodif', +]; + +const COMMANDE_AUTO_COLS = [ + 'fou_code','art_no_id','codesite','qtepropo','parametre_numero','suividatecreation','suividatemodif', +]; + +const PLAN_REAPPRO_COLS = [ + 'id','magasin','codein','bu_no_id','stockmag','encours_palette_mag', + 'attente_prepa_mag','colis_rea','colis_ajout_web','stock_dispo', + 'attente_prepa_depot','stock_dispo_calcul','colisage','bloque','date_besoin', +]; + +async function syncAppro(force) { + const ms = await getMssql(); + const pg = getPg(); + + // === COMMANDE_FOU (upsert delta) === + try { + const lastSync = force ? null : await getLastSync(pg, 'commande_fou'); + const where = lastSync ? `WHERE SUIVIDATEMODIF > '${lastSync.toISOString()}'` : ''; + + const res = await ms.request().query(` + SELECT NO_ID, CDENUM, SIT_COD_EMET, SIT_COD_DESTI, FOUIDENT_CODE, + FOUTARIF_NO_ID, PORT_CODE, CDEDATE, CDEETAT, CDETOT, + CDETARD, CDEPORTMONTANT, CDEANNULATION, SUIVIDATECREATION, SUIVIDATEMODIF + FROM COMMANDE_FOU ${where} + `); + + const rows = res.recordset.map(r => ({ + no_id: r.NO_ID, + cdenum: r.CDENUM, + sit_cod_emet: r.SIT_COD_EMET, + sit_cod_desti: r.SIT_COD_DESTI, + fouident_code: r.FOUIDENT_CODE, + foutarif_no_id: r.FOUTARIF_NO_ID ?? null, + port_code: r.PORT_CODE, + cdedate: r.CDEDATE, + cdeetat: r.CDEETAT, + cdetot: r.CDETOT ?? null, + cdetard: r.CDETARD ?? null, + cdeportmontant: r.CDEPORTMONTANT ?? null, + cdeannulation: r.CDEANNULATION ?? null, + suividatecreation: r.SUIVIDATECREATION, + suividatemodif: r.SUIVIDATEMODIF, + })); + + const count = await batchUpsert(pg, 'commande_fou', rows, ['no_id'], COMMANDE_FOU_COLS); + await logSync(pg, 'commande_fou', count, 'ok'); + console.log(`[commande_fou] ${count} lignes upsert`); + } catch (err) { + await logSync(pg, 'commande_fou', 0, 'error', err.message); + console.error(`[commande_fou] ERREUR: ${err.message}`); + } + + // === CDEFOU_LIGNE (upsert delta) === + try { + const lastSync = force ? null : await getLastSync(pg, 'cdefou_ligne'); + const where = lastSync ? `WHERE SUIVIDATEMODIF > '${lastSync.toISOString()}'` : ''; + + const res = await ms.request().query(` + SELECT NO_ID, ARTFOU1_NO_ID, COMMANDE_FOU_NO_ID, CDELIG, CDELIGTARD, CDELIGTOT, + PRIXBRUT, REMISE, PRIXNET, QTECDE, GRATUIT, MONTANT, + QTEACC, QTEANN, QTEATT, QTEREL, COLISAGE, SUIVIDATECREATION, SUIVIDATEMODIF + FROM CDEFOU_LIGNE ${where} + `); + + const rows = res.recordset.map(r => ({ + no_id: r.NO_ID, + artfou1_no_id: r.ARTFOU1_NO_ID, + commande_fou_no_id: r.COMMANDE_FOU_NO_ID, + cdelig: r.CDELIG ?? null, + cdeligtard: r.CDELIGTARD ?? null, + cdeligtot: r.CDELIGTOT ?? null, + prixbrut: r.PRIXBRUT ?? null, + remise: r.REMISE ?? null, + prixnet: r.PRIXNET ?? null, + qtecde: r.QTECDE ?? null, + gratuit: r.GRATUIT ?? null, + montant: r.MONTANT ?? null, + qteacc: r.QTEACC ?? null, + qteann: r.QTEANN ?? null, + qteatt: r.QTEATT ?? null, + qterel: r.QTEREL ?? null, + colisage: r.COLISAGE ?? null, + suividatecreation: r.SUIVIDATECREATION, + suividatemodif: r.SUIVIDATEMODIF, + })); + + const count = await batchUpsert(pg, 'cdefou_ligne', rows, ['no_id'], CDEFOU_LIGNE_COLS); + await logSync(pg, 'cdefou_ligne', count, 'ok'); + console.log(`[cdefou_ligne] ${count} lignes upsert`); + } catch (err) { + await logSync(pg, 'cdefou_ligne', 0, 'error', err.message); + console.error(`[cdefou_ligne] ERREUR: ${err.message}`); + } + + // === COMMANDE_AUTO_QTEPROPO (full refresh — état courant) === + try { + const res = await ms.request().query(` + SELECT FOU_CODE, ART_NO_ID, CODESITE, QTEPROPO, + PARAMETRE_NUMERO, SUIVIDATECREATION, SUIVIDATEMODIF + FROM COMMANDE_AUTO_QTEPROPO + `); + + const rows = res.recordset.map(r => ({ + fou_code: r.FOU_CODE, + art_no_id: r.ART_NO_ID, + codesite: r.CODESITE, + qtepropo: r.QTEPROPO ?? null, + parametre_numero: r.PARAMETRE_NUMERO ?? null, + suividatecreation: r.SUIVIDATECREATION, + suividatemodif: r.SUIVIDATEMODIF, + })); + + const count = await fullRefresh(pg, 'commande_auto_qtepropo', rows, COMMANDE_AUTO_COLS); + await logSync(pg, 'commande_auto_qtepropo', count, 'ok'); + console.log(`[commande_auto_qtepropo] ${count} lignes`); + } catch (err) { + await logSync(pg, 'commande_auto_qtepropo', 0, 'error', err.message); + console.error(`[commande_auto_qtepropo] ERREUR: ${err.message}`); + } + + // === PLAN_REAPPRO (full refresh — état courant) === + try { + const res = await ms.request().query(` + SELECT id, Magasin, Codein, BU_no_id, StockMag, EncoursPaletteMag, + AttentePrepaMag, ColisRea, ColisAjoutWeb, StockDispo, + AttentePrepaDepot, StockDispoCalcul, Colisage, + [Bloqué] AS bloque, DateBesoin + FROM PLAN_REAPPRO + `); + + const rows = res.recordset.map(r => ({ + id: r.id, + magasin: r.Magasin, + codein: r.Codein, + bu_no_id: r.BU_no_id ?? null, + stockmag: r.StockMag ?? null, + encours_palette_mag: r.EncoursPaletteMag ?? null, + attente_prepa_mag: r.AttentePrepaMag ?? null, + colis_rea: r.ColisRea ?? null, + colis_ajout_web: r.ColisAjoutWeb ?? null, + stock_dispo: r.StockDispo ?? null, + attente_prepa_depot: r.AttentePrepaDepot ?? null, + stock_dispo_calcul: r.StockDispoCalcul ?? null, + colisage: r.Colisage ?? null, + bloque: r.bloque ?? null, + date_besoin: r.DateBesoin ?? null, + })); + + const count = await fullRefresh(pg, 'plan_reappro', rows, PLAN_REAPPRO_COLS); + await logSync(pg, 'plan_reappro', count, 'ok'); + console.log(`[plan_reappro] ${count} lignes`); + } catch (err) { + await logSync(pg, 'plan_reappro', 0, 'error', err.message); + console.error(`[plan_reappro] ERREUR: ${err.message}`); + } +} + +module.exports = { syncAppro };