mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-17 20:30:07 +02:00
feat: implement product row data retrieval and processing logic for the grid.
This commit is contained in:
1 parent
65f455b4e2
commit
58d467a7ea
2 files changed
+420
-169
No files matched your search
@@ -0,0 +1,285 @@
|
||||
/**
|
||||
* CollectFlow — Client PostgreSQL FF Nancy
|
||||
*
|
||||
* Remplace les appels HTTP per-article vers api.ffnancy.fr par des requêtes
|
||||
* SQL bulk directes sur la base PostgreSQL FF Nancy.
|
||||
*
|
||||
* Chaque fonction fait 1 seule requête filtrée par codefou — O(1) au lieu de O(N).
|
||||
*/
|
||||
|
||||
import { db } from "@/db";
|
||||
import { sql } from "drizzle-orm";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types résultats SQL
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface PgArticle {
|
||||
no_id: number;
|
||||
codein: string;
|
||||
codefou: string;
|
||||
nomfou?: string;
|
||||
libelle1?: string;
|
||||
pcb?: number;
|
||||
reference?: string;
|
||||
ean13?: string;
|
||||
gtin?: string; // art_gtin.gtin (préférentiel)
|
||||
pv_central?: number; // article_infosup.prix_vente_mini
|
||||
pa?: number; // cube_pa.pa
|
||||
}
|
||||
|
||||
export interface PgMensuelRow {
|
||||
codein: string;
|
||||
site: string;
|
||||
mois: string; // "YYYY-MM"
|
||||
qte_vendue: number;
|
||||
ca_ht: number; // mntmvtttc (à vérifier si HT ou TTC)
|
||||
marge: number;
|
||||
stock_fin_mois: number; // MAX(qtestock) sur le mois
|
||||
qte_recue: number;
|
||||
}
|
||||
|
||||
export interface PgStockRow {
|
||||
codein: string;
|
||||
site: string;
|
||||
stockdispo: number;
|
||||
qte: number;
|
||||
valstock: number;
|
||||
prmp: number;
|
||||
dernierevente?: string;
|
||||
dernierereception?: string;
|
||||
}
|
||||
|
||||
export interface PgRankingRow {
|
||||
codein: string;
|
||||
site: string;
|
||||
ranking_ca?: number;
|
||||
ranking_qte?: number;
|
||||
ranking_mag_ca?: number;
|
||||
ranking_mag_qte?: number;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 1. Articles du fournisseur
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Retourne tous les articles d'un fournisseur avec leurs métadonnées.
|
||||
* 1 seule requête SQL — remplace getArticlesByFournisseur() + per-article fetches.
|
||||
*/
|
||||
export async function pgGetArticlesByFournisseur(codefou: string): Promise<PgArticle[]> {
|
||||
const result = await db.execute(sql`
|
||||
SELECT
|
||||
a.no_id,
|
||||
a.codein,
|
||||
af.code AS codefou,
|
||||
fa.raisonsociale AS nomfou,
|
||||
a.libelle1,
|
||||
af.pcb,
|
||||
af.reference,
|
||||
af.ean13,
|
||||
COALESCE(ag.gtin, af.ean13) AS gtin,
|
||||
ai.prix_vente_mini AS pv_central,
|
||||
pa.pa
|
||||
FROM artfou1 af
|
||||
JOIN articles a
|
||||
ON a.no_id = af.art_no_id
|
||||
LEFT JOIN fouadr1 fa
|
||||
ON fa.code = af.code AND fa.sit_code = '000'
|
||||
LEFT JOIN article_infosup ai
|
||||
ON ai.artnoid = a.no_id
|
||||
LEFT JOIN cube_pa pa
|
||||
ON pa.artnoid = a.no_id
|
||||
LEFT JOIN art_gtin ag
|
||||
ON ag.idarticle = a.no_id AND ag.preferentiel = 1
|
||||
WHERE af.code = ${codefou}
|
||||
AND a.codein IS NOT NULL
|
||||
ORDER BY a.codein
|
||||
`);
|
||||
|
||||
return (result.rows as unknown as PgArticle[]).filter(r => r.codein);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 2. Données mensuelles (ventes + stock + réceptions)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Retourne les données mensuelles agrégées par (codein, site, mois).
|
||||
* 1 seule requête SQL — remplace getMensuelByArticles() (N appels HTTP).
|
||||
*
|
||||
* genremvt = 3 → ventes clients
|
||||
* genremvt 1/2 → réceptions (à confirmer selon nomenclature FF Nancy)
|
||||
* qtestock → stock après mouvement (MAX du mois ≈ stock fin de mois)
|
||||
*/
|
||||
export async function pgGetMensuelByFournisseur(
|
||||
codefou: string,
|
||||
dateDebut: string,
|
||||
dateFin: string
|
||||
): Promise<PgMensuelRow[]> {
|
||||
const result = await db.execute(sql`
|
||||
SELECT
|
||||
a.codein,
|
||||
m.site,
|
||||
TO_CHAR(m.datmvt, 'YYYY-MM') AS mois,
|
||||
SUM(CASE WHEN m.genremvt = 3 THEN ABS(m.qtemvt) ELSE 0 END)::float AS qte_vendue,
|
||||
SUM(CASE WHEN m.genremvt = 3 THEN ABS(m.mntmvtttc) ELSE 0 END)::float AS ca_ht,
|
||||
SUM(CASE WHEN m.genremvt = 3 THEN m.margemvt ELSE 0 END)::float AS marge,
|
||||
MAX(m.qtestock)::float AS stock_fin_mois,
|
||||
SUM(CASE WHEN m.genremvt IN (1, 2) THEN ABS(m.qtemvt) ELSE 0 END)::float AS qte_recue
|
||||
FROM mvtart m
|
||||
JOIN articles a
|
||||
ON a.no_id = m.artnoid
|
||||
JOIN artfou1 af
|
||||
ON af.art_no_id = a.no_id AND af.code = ${codefou}
|
||||
WHERE m.datmvt BETWEEN ${dateDebut}::date AND ${dateFin}::date
|
||||
AND m.site IN ('292', '579')
|
||||
GROUP BY a.codein, m.site, TO_CHAR(m.datmvt, 'YYYY-MM')
|
||||
ORDER BY a.codein, m.site, mois
|
||||
`);
|
||||
|
||||
return result.rows as unknown as PgMensuelRow[];
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 3. Gammes (saison active)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Retourne la gamme courante (saison active) pour chaque article du fournisseur.
|
||||
* 1 requête SQL — remplace getReferentielByArticles() pour la partie gammes.
|
||||
*/
|
||||
export async function pgGetGammesByFournisseur(codefou: string): Promise<Map<string, string>> {
|
||||
const result = await db.execute(sql`
|
||||
SELECT
|
||||
a.codein,
|
||||
g.code AS gamme_code
|
||||
FROM art_gamme_saison ags
|
||||
JOIN articles a
|
||||
ON a.no_id = ags.artnoid
|
||||
JOIN artfou1 af
|
||||
ON af.art_no_id = a.no_id AND af.code = ${codefou}
|
||||
JOIN gammes g
|
||||
ON g.no_id = ags.idgamme
|
||||
JOIN saisons s
|
||||
ON s.no_id = ags.idsaison
|
||||
WHERE s.actif = true
|
||||
`);
|
||||
|
||||
const map = new Map<string, string>();
|
||||
for (const row of result.rows as unknown as { codein: string; gamme_code: string }[]) {
|
||||
if (row.codein && row.gamme_code) {
|
||||
map.set(row.codein, row.gamme_code);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 4. Stock temps réel (cube_stock)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Retourne le stock actuel par site pour chaque article du fournisseur.
|
||||
* 1 requête SQL — remplace la partie stock du référentiel.
|
||||
*/
|
||||
export async function pgGetStockByFournisseur(codefou: string): Promise<Map<string, PgStockRow[]>> {
|
||||
const result = await db.execute(sql`
|
||||
SELECT
|
||||
a.codein,
|
||||
cs.site,
|
||||
cs.stockdispo::float,
|
||||
cs.qte::float,
|
||||
cs.valstock::float,
|
||||
cs.prmp::float,
|
||||
cs.dernierevente::text,
|
||||
cs.dernierereception::text
|
||||
FROM cube_stock cs
|
||||
JOIN articles a
|
||||
ON a.no_id = cs.artnoid
|
||||
JOIN artfou1 af
|
||||
ON af.art_no_id = a.no_id AND af.code = ${codefou}
|
||||
`);
|
||||
|
||||
const map = new Map<string, PgStockRow[]>();
|
||||
for (const row of result.rows as unknown as PgStockRow[]) {
|
||||
if (!map.has(row.codein)) map.set(row.codein, []);
|
||||
map.get(row.codein)!.push(row);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 5. Ranking réseau
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Retourne le classement réseau pour chaque article du fournisseur.
|
||||
* 1 requête SQL — remplace getRankingByArticles() (N appels HTTP per-article,
|
||||
* capés à 500 par l'API).
|
||||
*/
|
||||
export async function pgGetRankingByFournisseur(codefou: string): Promise<{
|
||||
rankings: Map<string, PgRankingRow>;
|
||||
totalRankedProducts: number;
|
||||
}> {
|
||||
const [rankResult, totalResult] = await Promise.all([
|
||||
db.execute(sql`
|
||||
SELECT DISTINCT ON (a.codein)
|
||||
a.codein,
|
||||
r.site,
|
||||
r.ranking_ca,
|
||||
r.ranking_qte,
|
||||
r.ranking_mag_ca,
|
||||
r.ranking_mag_qte
|
||||
FROM ranking r
|
||||
JOIN art_gtin ag
|
||||
ON ag.gtin = r.gencod
|
||||
JOIN articles a
|
||||
ON a.no_id = ag.idarticle
|
||||
JOIN artfou1 af
|
||||
ON af.art_no_id = a.no_id AND af.code = ${codefou}
|
||||
ORDER BY a.codein, r.site
|
||||
`),
|
||||
db.execute(sql`
|
||||
SELECT COUNT(DISTINCT gencod)::int AS total FROM ranking
|
||||
`),
|
||||
]);
|
||||
|
||||
const rankings = new Map<string, PgRankingRow>();
|
||||
for (const row of rankResult.rows as unknown as PgRankingRow[]) {
|
||||
if (row.codein) rankings.set(row.codein, row);
|
||||
}
|
||||
|
||||
const totalRankedProducts = Number((totalResult.rows[0] as unknown as { total: number })?.total ?? 0);
|
||||
console.log(`[pg-ff] Ranking: ${rankings.size}/${codefou} articles classés (réseau: ${totalRankedProducts})`);
|
||||
|
||||
return { rankings, totalRankedProducts };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 6. Commandes en cours
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Retourne les quantités commandées en cours pour chaque article du fournisseur.
|
||||
* 1 requête SQL — remplace getCommandesByFournisseur().
|
||||
*/
|
||||
export async function pgGetCommandesByFournisseur(codefou: string): Promise<Map<string, number>> {
|
||||
const result = await db.execute(sql`
|
||||
SELECT
|
||||
a.codein,
|
||||
SUM(cv.qtecde)::float AS qtecde
|
||||
FROM cdefou_vivant cv
|
||||
JOIN artfou1 af
|
||||
ON af.no_id = cv.artfou1_no_id AND af.code = ${codefou}
|
||||
JOIN articles a
|
||||
ON a.no_id = af.art_no_id
|
||||
GROUP BY a.codein
|
||||
`);
|
||||
|
||||
const map = new Map<string, number>();
|
||||
for (const row of result.rows as unknown as { codein: string; qtecde: number }[]) {
|
||||
if (row.codein) map.set(row.codein, Number(row.qtecde) || 0);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
Reference in new issue
Block a user