feat: implement advertising data synchronization and API endpoints for performance tracking
This commit is contained in:
1 parent
de69370d9e
commit
9e993b3559
6 files changed
+506
-5
No files matched your search
+49
-4
@@ -3,7 +3,7 @@
|
|||||||
**Base URL** : `https://api.ffnancy.fr`
|
**Base URL** : `https://api.ffnancy.fr`
|
||||||
**Format** : JSON
|
**Format** : JSON
|
||||||
**Méthode** : GET uniquement (API lecture seule)
|
**Méthode** : GET uniquement (API lecture seule)
|
||||||
**Données** : Synchronisées depuis SQL Server chaque nuit à 3h00
|
**Données** : Synchronisées depuis SQL Server chaque nuit à 6h45 (heure Paris)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -253,7 +253,9 @@ GET /api/articles/:id/mouvements
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Valeurs `GenreMvt`** : `1` = entrée stock, `3` = vente
|
**Valeurs `GenreMvt`** : `1` = entrée stock, `3` = vente, `4` = avoir/annulation, `9` = autre vente
|
||||||
|
|
||||||
|
> Le CA TTC (`ca_ttc`) représente les **ventes brutes** (mouvements négatifs des genremvt 3, 4, 9). Le champ `retours_ttc` contient les retours séparément. CA net = `ca_ttc - retours_ttc`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -763,6 +765,7 @@ GET /api/performance/ca
|
|||||||
"qte_vendue": 9640,
|
"qte_vendue": 9640,
|
||||||
"ca_ht": 85200.00,
|
"ca_ht": 85200.00,
|
||||||
"ca_ttc": 97980.00,
|
"ca_ttc": 97980.00,
|
||||||
|
"retours_ttc": 1250.00,
|
||||||
"marge": 21300.00,
|
"marge": 21300.00,
|
||||||
"taux_marge": 21.74
|
"taux_marge": 21.74
|
||||||
}
|
}
|
||||||
@@ -808,6 +811,39 @@ GET /api/performance/hitparade
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### CA par fournisseur
|
||||||
|
```
|
||||||
|
GET /api/performance/ca/fournisseur?dateDebut=&dateFin=&site=
|
||||||
|
```
|
||||||
|
|
||||||
|
| Paramètre | Type | Description | Défaut |
|
||||||
|
|-------------|--------|--------------------------|------------|
|
||||||
|
| `dateDebut` | date | Date début `YYYY-MM-DD` | 2024-01-01 |
|
||||||
|
| `dateFin` | date | Date fin `YYYY-MM-DD` | 2099-12-31 |
|
||||||
|
| `site` | string | Filtrer par site | tous |
|
||||||
|
|
||||||
|
**Réponse** :
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ca_par_fournisseur": [
|
||||||
|
{
|
||||||
|
"fournisseur": "DURACELL",
|
||||||
|
"code_fournisseur": "D034",
|
||||||
|
"nb_articles": 12,
|
||||||
|
"qte_vendue": 840,
|
||||||
|
"ca_ht": 4200.00,
|
||||||
|
"ca_ttc": 5040.00,
|
||||||
|
"marge": 1680.00,
|
||||||
|
"taux_marge": 40.00
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> La jointure se fait via `artfou1.preference = true` (fournisseur principal de l'article) → `fouident.code` pour le nom.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### CA par nomenclature (rayon)
|
### CA par nomenclature (rayon)
|
||||||
```
|
```
|
||||||
GET /api/performance/ca/nomenclature?dateDebut=&dateFin=&site=&niveau=1
|
GET /api/performance/ca/nomenclature?dateDebut=&dateFin=&site=&niveau=1
|
||||||
@@ -996,15 +1032,24 @@ GET /api/sync/status
|
|||||||
| `cube_stock` | Refresh complet | Nuit |
|
| `cube_stock` | Refresh complet | Nuit |
|
||||||
| `cube_pa` | Refresh complet | Nuit |
|
| `cube_pa` | Refresh complet | Nuit |
|
||||||
| `cube_pv` | Refresh complet | Nuit |
|
| `cube_pv` | Refresh complet | Nuit |
|
||||||
| `mvtart` | Append (DatMvt >= hier) | Nuit |
|
| `mvtart` | Append par NO_ID (PK SQL Server) | Nuit |
|
||||||
|
| `mvtreg` | Append par date | Nuit |
|
||||||
| `nomenclature` | Refresh complet | Nuit |
|
| `nomenclature` | Refresh complet | Nuit |
|
||||||
| `gammes` | Refresh complet | Nuit |
|
| `gammes` | Refresh complet | Nuit |
|
||||||
| `art_gamme_saison` | Refresh complet | Nuit |
|
| `art_gamme_saison` | Refresh complet | Nuit |
|
||||||
| `saisons` | Refresh complet | Nuit |
|
| `saisons` | Refresh complet | Nuit |
|
||||||
|
| `fouident` | Refresh complet | Nuit |
|
||||||
| `cdefou_vivant` | Refresh complet | Nuit |
|
| `cdefou_vivant` | Refresh complet | Nuit |
|
||||||
| `cdefou_reception` | Delta | Nuit |
|
| `cdefou_reception` | Delta | Nuit |
|
||||||
| `cdefou_receplig` | Delta | Nuit |
|
| `cdefou_receplig` | Delta | Nuit |
|
||||||
|
| `commande_fou` | Upsert (NO_ID) | Nuit |
|
||||||
|
| `cdefou_ligne` | Upsert (NO_ID) | Nuit |
|
||||||
|
| `commande_auto_qtepropo` | Refresh complet | Nuit |
|
||||||
|
| `plan_reappro` | Refresh complet | Nuit |
|
||||||
| `ranking` | Refresh complet | Nuit |
|
| `ranking` | Refresh complet | Nuit |
|
||||||
|
| `stat_dispoperm` | Upsert | Nuit |
|
||||||
|
| `phenix_quantite_conseille` | Refresh complet | Nuit |
|
||||||
|
| `statopca` | Upsert (site+date) | Nuit |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1084,7 +1129,7 @@ async function getCommandesFournisseur(codeFou) {
|
|||||||
## Notes importantes
|
## Notes importantes
|
||||||
|
|
||||||
- Les données sont en lecture seule — aucun endpoint d'écriture
|
- Les données sont en lecture seule — aucun endpoint d'écriture
|
||||||
- Les données sont décalées d'au plus 24h (sync à 3h00)
|
- Les données sont décalées d'au plus 24h (sync automatique à 6h45 heure Paris)
|
||||||
- Les champs dates sont au format ISO 8601 UTC
|
- Les champs dates sont au format ISO 8601 UTC
|
||||||
- La pagination commence à `page=1`
|
- La pagination commence à `page=1`
|
||||||
- Le champ `no_id` est l'identifiant interne SQL Server de l'article
|
- Le champ `no_id` est l'identifiant interne SQL Server de l'article
|
||||||
|
|||||||
@@ -447,6 +447,83 @@ CREATE TABLE IF NOT EXISTS commande_auto_qtepropo (
|
|||||||
CREATE INDEX IF NOT EXISTS idx_caqp_art_no_id ON commande_auto_qtepropo (art_no_id);
|
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 INDEX IF NOT EXISTS idx_caqp_codesite ON commande_auto_qtepropo (codesite);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Publicités (FBENTPUB + ECOULEMENT + ECOULEMENT_DETAIL SQL Server)
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS pub_entetes (
|
||||||
|
ent_npub TEXT PRIMARY KEY,
|
||||||
|
ent_datdeb TIMESTAMP,
|
||||||
|
ent_datfin TIMESTAMP,
|
||||||
|
ent_titre TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS pub_ecoulement (
|
||||||
|
site TEXT NOT NULL,
|
||||||
|
tcr_code TEXT NOT NULL,
|
||||||
|
tcr_libelle TEXT,
|
||||||
|
tcr_nature SMALLINT,
|
||||||
|
tcr_type SMALLINT,
|
||||||
|
tcrd_datedeb TIMESTAMP,
|
||||||
|
tcrd_datefin TIMESTAMP,
|
||||||
|
datecalcul TIMESTAMP,
|
||||||
|
stock_datedebut NUMERIC(14,3),
|
||||||
|
ca_total_periode_pub NUMERIC(14,4),
|
||||||
|
ca_pub_periode_pub NUMERIC(14,4),
|
||||||
|
pourc_capub_catotal NUMERIC(8,4),
|
||||||
|
qte_vendue_pub NUMERIC(14,3),
|
||||||
|
client_total_periode NUMERIC(14,3),
|
||||||
|
client_pub_periode NUMERIC(14,3),
|
||||||
|
stock_datefin NUMERIC(14,3),
|
||||||
|
ca_pub_30_jours NUMERIC(14,4),
|
||||||
|
stock_30_jours NUMERIC(14,3),
|
||||||
|
ca_pub_60_jours NUMERIC(14,4),
|
||||||
|
stock_60_jours NUMERIC(14,3),
|
||||||
|
ca_pub_90_jours NUMERIC(14,4),
|
||||||
|
stock_90_jours NUMERIC(14,3),
|
||||||
|
ca_pub_180_jours NUMERIC(14,4),
|
||||||
|
stock_180_jours NUMERIC(14,3),
|
||||||
|
ca_depuis_finpub NUMERIC(14,4),
|
||||||
|
taux_sortie NUMERIC(8,4),
|
||||||
|
marge NUMERIC(14,4),
|
||||||
|
taux_marge NUMERIC(8,4),
|
||||||
|
PRIMARY KEY (site, tcr_code)
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_pub_ecoulement_tcr_code ON pub_ecoulement (tcr_code);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_pub_ecoulement_tcrd_datedeb ON pub_ecoulement (tcrd_datedeb);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS pub_ecoulement_detail (
|
||||||
|
site TEXT NOT NULL,
|
||||||
|
tcr_code TEXT NOT NULL,
|
||||||
|
artnoid BIGINT NOT NULL,
|
||||||
|
tcr_libelle TEXT,
|
||||||
|
tcrd_datedeb TIMESTAMP,
|
||||||
|
tcrd_datefin TIMESTAMP,
|
||||||
|
datecalcul TIMESTAMP,
|
||||||
|
ca_pub_periode_pub NUMERIC(14,4),
|
||||||
|
qte_vendue_pub NUMERIC(14,3),
|
||||||
|
stock_datedebut NUMERIC(14,3),
|
||||||
|
stock_datefin NUMERIC(14,3),
|
||||||
|
ca_pub_30_jours NUMERIC(14,4),
|
||||||
|
ca_pub_60_jours NUMERIC(14,4),
|
||||||
|
ca_pub_90_jours NUMERIC(14,4),
|
||||||
|
ca_pub_180_jours NUMERIC(14,4),
|
||||||
|
ca_depuis_finpub NUMERIC(14,4),
|
||||||
|
taux_sortie NUMERIC(8,4),
|
||||||
|
marge NUMERIC(14,4),
|
||||||
|
taux_marge NUMERIC(8,4),
|
||||||
|
codein TEXT,
|
||||||
|
libelle TEXT,
|
||||||
|
prmp NUMERIC(12,4),
|
||||||
|
pa NUMERIC(12,4),
|
||||||
|
pv NUMERIC(12,4),
|
||||||
|
prixpub TEXT,
|
||||||
|
PRIMARY KEY (site, tcr_code, artnoid)
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_pub_detail_tcr_code ON pub_ecoulement_detail (tcr_code);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_pub_detail_artnoid ON pub_ecoulement_detail (artnoid);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_pub_detail_codein ON pub_ecoulement_detail (codein);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS plan_reappro (
|
CREATE TABLE IF NOT EXISTS plan_reappro (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
magasin TEXT,
|
magasin TEXT,
|
||||||
|
|||||||
+5
-1
@@ -10,7 +10,8 @@ const stockRouter = require('./routes/stock');
|
|||||||
const mouvementsRouter = require('./routes/mouvements');
|
const mouvementsRouter = require('./routes/mouvements');
|
||||||
const performanceRouter = require('./routes/performance');
|
const performanceRouter = require('./routes/performance');
|
||||||
const syncRouter = require('./routes/sync');
|
const syncRouter = require('./routes/sync');
|
||||||
const rankingRouter = require('./routes/ranking');
|
const rankingRouter = require('./routes/ranking');
|
||||||
|
const publicitesRouter = require('./routes/publicites');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
@@ -36,6 +37,7 @@ app.use('/api/mouvements', mouvementsRouter);
|
|||||||
app.use('/api/performance', performanceRouter);
|
app.use('/api/performance', performanceRouter);
|
||||||
app.use('/api/sync', syncRouter);
|
app.use('/api/sync', syncRouter);
|
||||||
app.use('/api/ranking', rankingRouter);
|
app.use('/api/ranking', rankingRouter);
|
||||||
|
app.use('/api/publicites', publicitesRouter);
|
||||||
|
|
||||||
// 404
|
// 404
|
||||||
app.use((req, res) => {
|
app.use((req, res) => {
|
||||||
@@ -72,5 +74,7 @@ app.listen(PORT, () => {
|
|||||||
console.log(' GET /api/performance/hitparade[?dateDebut=&dateFin=&site=&limit=&groupBy=qte|ca|marge]');
|
console.log(' GET /api/performance/hitparade[?dateDebut=&dateFin=&site=&limit=&groupBy=qte|ca|marge]');
|
||||||
console.log(' GET /api/performance/ca/nomenclature[?dateDebut=&dateFin=&site=&niveau=]');
|
console.log(' GET /api/performance/ca/nomenclature[?dateDebut=&dateFin=&site=&niveau=]');
|
||||||
console.log(' GET /api/performance/ca/gamme[?dateDebut=&dateFin=&site=]');
|
console.log(' GET /api/performance/ca/gamme[?dateDebut=&dateFin=&site=]');
|
||||||
|
console.log(' GET /api/publicites[?search=&site=&dateDebut=&dateFin=&page=&limit=]');
|
||||||
|
console.log(' GET /api/publicites/:code[?site=]');
|
||||||
console.log(' GET /api/sync/status');
|
console.log(' GET /api/sync/status');
|
||||||
});
|
});
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const { getPool } = require('../config/database');
|
||||||
|
|
||||||
|
// GET /api/publicites?search=&site=&dateDebut=&dateFin=&page=&limit=
|
||||||
|
router.get('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const pool = getPool();
|
||||||
|
const {
|
||||||
|
search = '',
|
||||||
|
site = '',
|
||||||
|
dateDebut = '2000-01-01',
|
||||||
|
dateFin = '2099-12-31',
|
||||||
|
page = 1,
|
||||||
|
limit = 50,
|
||||||
|
} = req.query;
|
||||||
|
|
||||||
|
const pageNum = Math.max(1, parseInt(page) || 1);
|
||||||
|
const limitNum = Math.max(1, Math.min(parseInt(limit) || 50, 500));
|
||||||
|
const offset = (pageNum - 1) * limitNum;
|
||||||
|
|
||||||
|
// Synthèse par pub (agrégat multi-sites)
|
||||||
|
const result = await pool.query(`
|
||||||
|
SELECT
|
||||||
|
e.tcr_code,
|
||||||
|
e.tcr_libelle AS intitule,
|
||||||
|
e.tcrd_datedeb AS date_debut,
|
||||||
|
e.tcrd_datefin AS date_fin,
|
||||||
|
e.datecalcul,
|
||||||
|
e.site,
|
||||||
|
e.ca_pub_periode_pub,
|
||||||
|
e.qte_vendue_pub,
|
||||||
|
e.ca_total_periode_pub,
|
||||||
|
ROUND(e.pourc_capub_catotal::NUMERIC, 2) AS pourc_capub_catotal,
|
||||||
|
e.client_pub_periode,
|
||||||
|
e.client_total_periode,
|
||||||
|
e.stock_datedebut,
|
||||||
|
e.stock_datefin,
|
||||||
|
e.ca_pub_30_jours,
|
||||||
|
e.ca_pub_60_jours,
|
||||||
|
e.ca_pub_90_jours,
|
||||||
|
e.ca_pub_180_jours,
|
||||||
|
e.ca_depuis_finpub,
|
||||||
|
ROUND(e.taux_sortie::NUMERIC, 2) AS taux_sortie,
|
||||||
|
ROUND(e.marge::NUMERIC, 4) AS marge,
|
||||||
|
ROUND(e.taux_marge::NUMERIC, 2) AS taux_marge,
|
||||||
|
(SELECT COUNT(DISTINCT d.artnoid)
|
||||||
|
FROM pub_ecoulement_detail d
|
||||||
|
WHERE d.tcr_code = e.tcr_code AND d.site = e.site) AS nb_articles
|
||||||
|
FROM pub_ecoulement e
|
||||||
|
WHERE ($1 = '' OR e.tcr_libelle ILIKE '%' || $1 || '%' OR e.tcr_code ILIKE '%' || $1 || '%')
|
||||||
|
AND ($2 = '' OR e.site ILIKE '%' || $2 || '%')
|
||||||
|
AND e.tcrd_datedeb >= $3
|
||||||
|
AND e.tcrd_datedeb <= $4
|
||||||
|
ORDER BY e.tcrd_datedeb DESC, e.tcr_code, e.site
|
||||||
|
LIMIT $5 OFFSET $6
|
||||||
|
`, [search, site, dateDebut, dateFin, limitNum, offset]);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
page: pageNum,
|
||||||
|
limit: limitNum,
|
||||||
|
publicites: result.rows,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// GET /api/publicites/:code — synthèse par site + articles
|
||||||
|
router.get('/:code', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const pool = getPool();
|
||||||
|
const { code } = req.params;
|
||||||
|
const { site = '' } = req.query;
|
||||||
|
|
||||||
|
// Synthèse par site
|
||||||
|
const synthese = await pool.query(`
|
||||||
|
SELECT
|
||||||
|
e.tcr_code,
|
||||||
|
e.tcr_libelle AS intitule,
|
||||||
|
e.tcrd_datedeb AS date_debut,
|
||||||
|
e.tcrd_datefin AS date_fin,
|
||||||
|
e.datecalcul,
|
||||||
|
e.site,
|
||||||
|
e.ca_pub_periode_pub,
|
||||||
|
e.qte_vendue_pub,
|
||||||
|
e.ca_total_periode_pub,
|
||||||
|
ROUND(e.pourc_capub_catotal::NUMERIC, 2) AS pourc_capub_catotal,
|
||||||
|
e.client_pub_periode,
|
||||||
|
e.client_total_periode,
|
||||||
|
e.stock_datedebut,
|
||||||
|
e.stock_datefin,
|
||||||
|
e.ca_pub_30_jours,
|
||||||
|
e.ca_pub_60_jours,
|
||||||
|
e.ca_pub_90_jours,
|
||||||
|
e.ca_pub_180_jours,
|
||||||
|
e.ca_depuis_finpub,
|
||||||
|
ROUND(e.taux_sortie::NUMERIC, 2) AS taux_sortie,
|
||||||
|
ROUND(e.marge::NUMERIC, 4) AS marge,
|
||||||
|
ROUND(e.taux_marge::NUMERIC, 2) AS taux_marge
|
||||||
|
FROM pub_ecoulement e
|
||||||
|
WHERE e.tcr_code = $1
|
||||||
|
AND ($2 = '' OR e.site ILIKE '%' || $2 || '%')
|
||||||
|
ORDER BY e.site
|
||||||
|
`, [code, site]);
|
||||||
|
|
||||||
|
if (!synthese.rows.length) {
|
||||||
|
return res.status(404).json({ error: 'Publication introuvable' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Articles de la pub
|
||||||
|
const articles = await pool.query(`
|
||||||
|
SELECT
|
||||||
|
d.site,
|
||||||
|
d.artnoid,
|
||||||
|
d.codein,
|
||||||
|
d.libelle,
|
||||||
|
d.prmp,
|
||||||
|
d.pa,
|
||||||
|
d.pv,
|
||||||
|
d.prixpub,
|
||||||
|
d.qte_vendue_pub,
|
||||||
|
ROUND(d.ca_pub_periode_pub::NUMERIC, 4) AS ca_pub_periode_pub,
|
||||||
|
d.stock_datedebut,
|
||||||
|
d.stock_datefin,
|
||||||
|
d.ca_pub_30_jours,
|
||||||
|
d.ca_pub_60_jours,
|
||||||
|
d.ca_pub_90_jours,
|
||||||
|
d.ca_pub_180_jours,
|
||||||
|
d.ca_depuis_finpub,
|
||||||
|
ROUND(d.taux_sortie::NUMERIC, 2) AS taux_sortie,
|
||||||
|
ROUND(d.marge::NUMERIC, 4) AS marge,
|
||||||
|
ROUND(d.taux_marge::NUMERIC, 2) AS taux_marge
|
||||||
|
FROM pub_ecoulement_detail d
|
||||||
|
WHERE d.tcr_code = $1
|
||||||
|
AND ($2 = '' OR d.site ILIKE '%' || $2 || '%')
|
||||||
|
ORDER BY d.site, d.ca_pub_periode_pub DESC
|
||||||
|
`, [code, site]);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
tcr_code: synthese.rows[0].tcr_code,
|
||||||
|
intitule: synthese.rows[0].intitule,
|
||||||
|
date_debut: synthese.rows[0].date_debut,
|
||||||
|
date_fin: synthese.rows[0].date_fin,
|
||||||
|
datecalcul: synthese.rows[0].datecalcul,
|
||||||
|
synthese_par_site: synthese.rows,
|
||||||
|
articles: articles.rows,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -11,6 +11,7 @@ const { syncRanking } = require('./tables/ranking');
|
|||||||
const { syncStatDispoperm } = require('./tables/stat_dispoperm');
|
const { syncStatDispoperm } = require('./tables/stat_dispoperm');
|
||||||
const { syncPhenixQuantiteConseille } = require('./tables/phenix_quantite_conseille');
|
const { syncPhenixQuantiteConseille } = require('./tables/phenix_quantite_conseille');
|
||||||
const { syncAppro } = require('./tables/appro');
|
const { syncAppro } = require('./tables/appro');
|
||||||
|
const { syncPublicites } = require('./tables/publicites');
|
||||||
const { closeAll } = require('./db');
|
const { closeAll } = require('./db');
|
||||||
const {
|
const {
|
||||||
startDashboard, registerSyncFn,
|
startDashboard, registerSyncFn,
|
||||||
@@ -33,6 +34,7 @@ const STEPS = [
|
|||||||
{ name: 'stat_dispoperm', fn: syncStatDispoperm },
|
{ name: 'stat_dispoperm', fn: syncStatDispoperm },
|
||||||
{ name: 'phenix_quantite_conseille', fn: syncPhenixQuantiteConseille },
|
{ name: 'phenix_quantite_conseille', fn: syncPhenixQuantiteConseille },
|
||||||
{ name: 'appro', fn: syncAppro },
|
{ name: 'appro', fn: syncAppro },
|
||||||
|
{ name: 'publicites', fn: syncPublicites },
|
||||||
];
|
];
|
||||||
|
|
||||||
async function syncAll(force = false) {
|
async function syncAll(force = false) {
|
||||||
|
|||||||
@@ -0,0 +1,219 @@
|
|||||||
|
const { getMssql, getPg } = require('../db');
|
||||||
|
const { logSync } = require('../utils');
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// pub_entetes — FBENTPUB (refresh complet)
|
||||||
|
// ============================================================
|
||||||
|
const ENTETES_COLS = ['ent_npub', 'ent_datdeb', 'ent_datfin', 'ent_titre'];
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// pub_ecoulement — ECOULEMENT (refresh complet)
|
||||||
|
// ============================================================
|
||||||
|
const ECOULEMENT_COLS = [
|
||||||
|
'site', 'tcr_code', 'tcr_libelle', 'tcr_nature', 'tcr_type',
|
||||||
|
'tcrd_datedeb', 'tcrd_datefin', 'datecalcul',
|
||||||
|
'stock_datedebut', 'ca_total_periode_pub', 'ca_pub_periode_pub',
|
||||||
|
'pourc_capub_catotal', 'qte_vendue_pub', 'client_total_periode',
|
||||||
|
'client_pub_periode', 'stock_datefin',
|
||||||
|
'ca_pub_30_jours', 'stock_30_jours',
|
||||||
|
'ca_pub_60_jours', 'stock_60_jours',
|
||||||
|
'ca_pub_90_jours', 'stock_90_jours',
|
||||||
|
'ca_pub_180_jours', 'stock_180_jours',
|
||||||
|
'ca_depuis_finpub', 'taux_sortie', 'marge', 'taux_marge',
|
||||||
|
];
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// pub_ecoulement_detail — ECOULEMENT_DETAIL (refresh complet)
|
||||||
|
// ============================================================
|
||||||
|
const DETAIL_COLS = [
|
||||||
|
'site', 'tcr_code', 'artnoid', 'tcr_libelle',
|
||||||
|
'tcrd_datedeb', 'tcrd_datefin', 'datecalcul',
|
||||||
|
'ca_pub_periode_pub', 'qte_vendue_pub',
|
||||||
|
'stock_datedebut', 'stock_datefin',
|
||||||
|
'ca_pub_30_jours', 'ca_pub_60_jours', 'ca_pub_90_jours', 'ca_pub_180_jours',
|
||||||
|
'ca_depuis_finpub', 'taux_sortie', 'marge', 'taux_marge',
|
||||||
|
'codein', 'libelle', 'prmp', 'pa', 'pv', 'prixpub',
|
||||||
|
];
|
||||||
|
|
||||||
|
const CHUNK = 500;
|
||||||
|
|
||||||
|
async function insertChunk(pg, table, rows, cols, conflictTarget) {
|
||||||
|
if (!rows.length) return;
|
||||||
|
const values = [];
|
||||||
|
const placeholders = rows.map((row, ri) => {
|
||||||
|
const ph = cols.map((col, ci) => {
|
||||||
|
values.push(row[col] ?? null);
|
||||||
|
return `$${ri * cols.length + ci + 1}`;
|
||||||
|
});
|
||||||
|
return `(${ph.join(', ')})`;
|
||||||
|
});
|
||||||
|
const colList = cols.join(', ');
|
||||||
|
const updateSet = cols
|
||||||
|
.filter(c => !conflictTarget.split(', ').includes(c))
|
||||||
|
.map(c => `${c}=EXCLUDED.${c}`)
|
||||||
|
.join(', ');
|
||||||
|
await pg.query(
|
||||||
|
`INSERT INTO ${table} (${colList}) VALUES ${placeholders.join(', ')}
|
||||||
|
ON CONFLICT (${conflictTarget}) DO UPDATE SET ${updateSet}`,
|
||||||
|
values
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncPublicites(force) {
|
||||||
|
const ms = await getMssql();
|
||||||
|
const pg = getPg();
|
||||||
|
|
||||||
|
// --- pub_entetes (FBENTPUB) ---
|
||||||
|
try {
|
||||||
|
const res = await ms.request().query(`
|
||||||
|
SELECT ENT_NPUB, ENT_DATDEB, ENT_DATFIN, ENT_TITRE
|
||||||
|
FROM FBENTPUB
|
||||||
|
`);
|
||||||
|
|
||||||
|
const rows = res.recordset.map(r => ({
|
||||||
|
ent_npub: String(r.ENT_NPUB),
|
||||||
|
ent_datdeb: r.ENT_DATDEB && r.ENT_DATDEB.getFullYear() > 1901 ? r.ENT_DATDEB : null,
|
||||||
|
ent_datfin: r.ENT_DATFIN && r.ENT_DATFIN.getFullYear() > 1901 ? r.ENT_DATFIN : null,
|
||||||
|
ent_titre: r.ENT_TITRE,
|
||||||
|
}));
|
||||||
|
|
||||||
|
await pg.query('TRUNCATE TABLE pub_entetes');
|
||||||
|
for (let i = 0; i < rows.length; i += CHUNK) {
|
||||||
|
const chunk = rows.slice(i, i + CHUNK);
|
||||||
|
const values = [];
|
||||||
|
const placeholders = chunk.map((row, ri) => {
|
||||||
|
const ph = ENTETES_COLS.map((col, ci) => {
|
||||||
|
values.push(row[col] ?? null);
|
||||||
|
return `$${ri * ENTETES_COLS.length + ci + 1}`;
|
||||||
|
});
|
||||||
|
return `(${ph.join(', ')})`;
|
||||||
|
});
|
||||||
|
await pg.query(
|
||||||
|
`INSERT INTO pub_entetes (${ENTETES_COLS.join(', ')}) VALUES ${placeholders.join(', ')}`,
|
||||||
|
values
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await logSync(pg, 'pub_entetes', rows.length, 'ok');
|
||||||
|
console.log(`[pub_entetes] ${rows.length} lignes`);
|
||||||
|
} catch (err) {
|
||||||
|
await logSync(pg, 'pub_entetes', 0, 'error', err.message);
|
||||||
|
console.error(`[pub_entetes] ERREUR: ${err.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- pub_ecoulement (ECOULEMENT) ---
|
||||||
|
try {
|
||||||
|
const res = await ms.request().query(`
|
||||||
|
SELECT
|
||||||
|
SITE, TCR_CODE, TCR_LIBELLE, TCR_NATURE, TCR_TYPE,
|
||||||
|
TCRD_DATEDEB, TCRD_DATEFIN, DATECALCUL,
|
||||||
|
STOCK_DATEDEBUT, CA_TOTAL_PERIODE_PUB, CA_PUB_PERIODE_PUB,
|
||||||
|
POURC_CAPUB_CATOTAL, QTE_VENDUE_PUB, CLIENT_TOTAL_PERIODE,
|
||||||
|
CLIENT_PUB_PERIODE, STOCK_DATEFIN,
|
||||||
|
CA_PUB_30_JOURS, STOCK_30_JOURS,
|
||||||
|
CA_PUB_60_JOURS, STOCK_60_JOURS,
|
||||||
|
CA_PUB_90_JOURS, STOCK_90_JOURS,
|
||||||
|
CA_PUB_180_JOURS, STOCK_180_JOURS,
|
||||||
|
CA_DEPUIS_FINPUB, TAUX_SORTIE, MARGE, TAUX_MARGE
|
||||||
|
FROM ECOULEMENT
|
||||||
|
`);
|
||||||
|
|
||||||
|
const rows = res.recordset.map(r => ({
|
||||||
|
site: r.SITE,
|
||||||
|
tcr_code: r.TCR_CODE,
|
||||||
|
tcr_libelle: r.TCR_LIBELLE,
|
||||||
|
tcr_nature: r.TCR_NATURE,
|
||||||
|
tcr_type: r.TCR_TYPE,
|
||||||
|
tcrd_datedeb: r.TCRD_DATEDEB,
|
||||||
|
tcrd_datefin: r.TCRD_DATEFIN,
|
||||||
|
datecalcul: r.DATECALCUL,
|
||||||
|
stock_datedebut: r.STOCK_DATEDEBUT,
|
||||||
|
ca_total_periode_pub: r.CA_TOTAL_PERIODE_PUB,
|
||||||
|
ca_pub_periode_pub: r.CA_PUB_PERIODE_PUB,
|
||||||
|
pourc_capub_catotal: r.POURC_CAPUB_CATOTAL,
|
||||||
|
qte_vendue_pub: r.QTE_VENDUE_PUB,
|
||||||
|
client_total_periode: r.CLIENT_TOTAL_PERIODE,
|
||||||
|
client_pub_periode: r.CLIENT_PUB_PERIODE,
|
||||||
|
stock_datefin: r.STOCK_DATEFIN,
|
||||||
|
ca_pub_30_jours: r.CA_PUB_30_JOURS,
|
||||||
|
stock_30_jours: r.STOCK_30_JOURS,
|
||||||
|
ca_pub_60_jours: r.CA_PUB_60_JOURS,
|
||||||
|
stock_60_jours: r.STOCK_60_JOURS,
|
||||||
|
ca_pub_90_jours: r.CA_PUB_90_JOURS,
|
||||||
|
stock_90_jours: r.STOCK_90_JOURS,
|
||||||
|
ca_pub_180_jours: r.CA_PUB_180_JOURS,
|
||||||
|
stock_180_jours: r.STOCK_180_JOURS,
|
||||||
|
ca_depuis_finpub: r.CA_DEPUIS_FINPUB,
|
||||||
|
taux_sortie: r.TAUX_SORTIE,
|
||||||
|
marge: r.MARGE,
|
||||||
|
taux_marge: r.TAUX_MARGE,
|
||||||
|
}));
|
||||||
|
|
||||||
|
await pg.query('TRUNCATE TABLE pub_ecoulement_detail');
|
||||||
|
await pg.query('TRUNCATE TABLE pub_ecoulement');
|
||||||
|
for (let i = 0; i < rows.length; i += CHUNK) {
|
||||||
|
await insertChunk(pg, 'pub_ecoulement', rows.slice(i, i + CHUNK), ECOULEMENT_COLS, 'site, tcr_code');
|
||||||
|
}
|
||||||
|
|
||||||
|
await logSync(pg, 'pub_ecoulement', rows.length, 'ok');
|
||||||
|
console.log(`[pub_ecoulement] ${rows.length} lignes`);
|
||||||
|
} catch (err) {
|
||||||
|
await logSync(pg, 'pub_ecoulement', 0, 'error', err.message);
|
||||||
|
console.error(`[pub_ecoulement] ERREUR: ${err.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- pub_ecoulement_detail (ECOULEMENT_DETAIL) ---
|
||||||
|
try {
|
||||||
|
const res = await ms.request().query(`
|
||||||
|
SELECT
|
||||||
|
SITE, TCR_CODE, ARTNOID, TCR_LIBELLE,
|
||||||
|
TCRD_DATEDEB, TCRD_DATEFIN, DATECALCUL,
|
||||||
|
CA_PUB_PERIODE_PUB, QTE_VENDUE_PUB,
|
||||||
|
STOCK_DATEDEBUT, STOCK_DATEFIN,
|
||||||
|
CA_PUB_30_JOURS, CA_PUB_60_JOURS, CA_PUB_90_JOURS, CA_PUB_180_JOURS,
|
||||||
|
CA_DEPUIS_FINPUB, TAUX_SORTIE, MARGE, TAUX_MARGE,
|
||||||
|
CODEIN, LIBELLE, PRMP, PA, PV, PRIXPUB
|
||||||
|
FROM ECOULEMENT_DETAIL
|
||||||
|
`);
|
||||||
|
|
||||||
|
const rows = res.recordset.map(r => ({
|
||||||
|
site: r.SITE,
|
||||||
|
tcr_code: r.TCR_CODE,
|
||||||
|
artnoid: r.ARTNOID,
|
||||||
|
tcr_libelle: r.TCR_LIBELLE,
|
||||||
|
tcrd_datedeb: r.TCRD_DATEDEB,
|
||||||
|
tcrd_datefin: r.TCRD_DATEFIN,
|
||||||
|
datecalcul: r.DATECALCUL,
|
||||||
|
ca_pub_periode_pub: r.CA_PUB_PERIODE_PUB,
|
||||||
|
qte_vendue_pub: r.QTE_VENDUE_PUB,
|
||||||
|
stock_datedebut: r.STOCK_DATEDEBUT,
|
||||||
|
stock_datefin: r.STOCK_DATEFIN,
|
||||||
|
ca_pub_30_jours: r.CA_PUB_30_JOURS,
|
||||||
|
ca_pub_60_jours: r.CA_PUB_60_JOURS,
|
||||||
|
ca_pub_90_jours: r.CA_PUB_90_JOURS,
|
||||||
|
ca_pub_180_jours: r.CA_PUB_180_JOURS,
|
||||||
|
ca_depuis_finpub: r.CA_DEPUIS_FINPUB,
|
||||||
|
taux_sortie: r.TAUX_SORTIE,
|
||||||
|
marge: r.MARGE,
|
||||||
|
taux_marge: r.TAUX_MARGE,
|
||||||
|
codein: r.CODEIN,
|
||||||
|
libelle: r.LIBELLE,
|
||||||
|
prmp: r.PRMP,
|
||||||
|
pa: r.PA,
|
||||||
|
pv: r.PV,
|
||||||
|
prixpub: r.PRIXPUB,
|
||||||
|
}));
|
||||||
|
|
||||||
|
for (let i = 0; i < rows.length; i += CHUNK) {
|
||||||
|
await insertChunk(pg, 'pub_ecoulement_detail', rows.slice(i, i + CHUNK), DETAIL_COLS, 'site, tcr_code, artnoid');
|
||||||
|
}
|
||||||
|
|
||||||
|
await logSync(pg, 'pub_ecoulement_detail', rows.length, 'ok');
|
||||||
|
console.log(`[pub_ecoulement_detail] ${rows.length} lignes`);
|
||||||
|
} catch (err) {
|
||||||
|
await logSync(pg, 'pub_ecoulement_detail', 0, 'error', err.message);
|
||||||
|
console.error(`[pub_ecoulement_detail] ERREUR: ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { syncPublicites };
|
||||||
Reference in new issue
Block a user