diff --git a/sync/tables/stock.js b/sync/tables/stock.js index 2da8a96..6714322 100644 --- a/sync/tables/stock.js +++ b/sync/tables/stock.js @@ -11,6 +11,48 @@ const STOCK_COLS = [ const PA_COLS = ['artnoid','pa']; const PV_COLS = ['artnoid','site','pv']; +// ARTFOU2.PRIXACHAT empile l'historique des prix d'un fournisseur sous forme +// "[01/01/1901,0.990:16/05/2018,1.010]". Le dernier couple porte le prix courant +// et la date à laquelle il a été appliqué. +function lastPriceChange(s) { + if (!s) return null; + const body = String(s).trim().replace(/^\[/, '').replace(/\]$/, ''); + if (!body) return null; + const pairs = body.split(':'); + const [d, p] = pairs[pairs.length - 1].split(','); + const m = d && d.match(/^(\d{2})\/(\d{2})\/(\d{4})$/); + const price = parseFloat(p); + if (!m || Number.isNaN(price)) return null; + return { date: Date.UTC(+m[3], +m[2] - 1, +m[1]), price }; +} + +// Pour chaque artnoid en doublon, retrouve le prix dont le changement est le plus +// récent. Égalité de date de prix → on départage sur SUIVIDATEMODIF de la ligne. +async function resolveDuplicatePa(ms, artnoids) { + const ids = artnoids.map(Number).filter(Number.isFinite); + if (!ids.length) return new Map(); + + const res = await ms.request().query(` + SELECT f1.ART_NO_ID, f2.PRIXACHAT, f2.SUIVIDATEMODIF + FROM ARTFOU1 f1 + JOIN ARTFOU2 f2 ON f2.IDARTFOU1 = f1.NO_ID + WHERE f1.ART_NO_ID IN (${ids.join(',')}) + `); + + const best = new Map(); + for (const r of res.recordset) { + const lp = lastPriceChange(r.PRIXACHAT); + if (!lp) continue; + const key = String(r.ART_NO_ID); + const modif = r.SUIVIDATEMODIF ? new Date(r.SUIVIDATEMODIF).getTime() : 0; + const prev = best.get(key); + if (!prev || lp.date > prev.date || (lp.date === prev.date && modif > prev.modif)) { + best.set(key, { date: lp.date, price: lp.price, modif }); + } + } + return best; +} + async function syncStock(force) { const ms = await getMssql(); const pg = getPg(); @@ -57,7 +99,37 @@ async function syncStock(force) { // === Cube_PA (full refresh) === try { const res = await ms.request().query(`SELECT ArtNoId, PA FROM Cube_PA`); - const rows = res.recordset.map(r => ({ artnoid: r.ArtNoId, pa: r.PA })); + const raw = res.recordset.map(r => ({ artnoid: r.ArtNoId, pa: r.PA })); + + // artnoid est PK côté PostgreSQL, mais le cube peut sortir deux lignes pour + // un article livré par deux fournisseurs → l'INSERT violerait cube_pa_pkey. + const groups = new Map(); + for (const r of raw) { + const key = String(r.artnoid); + const g = groups.get(key); + if (g) g.push(r); else groups.set(key, [r]); + } + const dupIds = [...groups].filter(([, g]) => g.length > 1).map(([id]) => id); + + let rows = raw; + if (dupIds.length) { + const best = await resolveDuplicatePa(ms, dupIds); + rows = []; + for (const [key, g] of groups) { + if (g.length === 1) { rows.push(g[0]); continue; } + const b = best.get(key); + let chosen = b && g.find(x => Math.abs(parseFloat(x.pa) - b.price) < 0.005); + if (!chosen) { + // Pas de prix fournisseur exploitable : on retient le PA le plus élevé, + // qui sous-estime la marge plutôt que de l'inventer. + chosen = g.reduce((a, x) => (parseFloat(x.pa) > parseFloat(a.pa) ? x : a)); + console.warn(`[cube_pa] artnoid ${key} non résolu → PA le plus élevé retenu (${chosen.pa})`); + } + rows.push(chosen); + } + console.log(`[cube_pa] ${dupIds.length} artnoid dédupliqués`); + } + const count = await fullRefresh(pg, 'cube_pa', rows, PA_COLS); await logSync(pg, 'cube_pa', count, 'ok'); console.log(`[cube_pa] ${count} lignes refresh`);