mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-20 13:38:08 +02:00
Fournisseur : les articles sans fournisseur principal ne disparaissent plus
Les pages Stock (stock négatif, stock sans vente, sans vente depuis 6 mois) joignaient obligatoirement le fournisseur principal (artfou1.preference = 1) et sa fiche fouident : un article sans principal — ou sans aucune ligne artfou1, comme le codein 504202 — était purement retiré de la liste. Les jointures deviennent facultatives et le fournisseur suit partout la même règle : le principal, sinon le fournisseur référencé le plus récent, sinon celui des données du réseau (cache Qlik, par code centrale), sinon « Sans fournisseur ». Même repli pour le CA par fournisseur, la dernière réception (cadencier), le Hit Parade, la recherche produit et les opportunités. Dans la recherche, ce repli ne masque plus le fournisseur que Qlik vient de renvoyer. Test d'intégration opt-in (FF_FOURNISSEUR_TEST_DATABASE_URL) qui rejoue les quatre cas ; il échoue sur l'ancien code (1 article listé sur 4). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DhSZx8CCuqoKrDw7unFqGq
This commit is contained in:
5 files changed
+166
-40
No files matched your search
@@ -13,6 +13,8 @@ pub mod routes;
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod fixture_tests;
|
mod fixture_tests;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests_fournisseur_db;
|
||||||
|
|
||||||
use crate::handler;
|
use crate::handler;
|
||||||
use crate::rpc::Handler;
|
use crate::rpc::Handler;
|
||||||
|
|||||||
+42
-36
@@ -1109,7 +1109,8 @@ fn push_filtre_deux_mois(qb: &mut QueryBuilder<'_, Postgres>, mois: &str, mois_n
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Retourne le CA TTC par fournisseur et site pour deux mois donnés (mois et mois N-1).
|
/// Retourne le CA TTC par fournisseur et site pour deux mois donnés (mois et mois N-1).
|
||||||
/// Attribution via artfou1.preference = true (fournisseur principal de l'article).
|
/// Attribution au fournisseur principal de l'article (artfou1.preference = 1),
|
||||||
|
/// sinon au fournisseur référencé le plus récent.
|
||||||
/// CA NET : SUM(-mntmvtttc) — les retours clients sont déduits (même convention
|
/// CA NET : SUM(-mntmvtttc) — les retours clients sont déduits (même convention
|
||||||
/// que pg_get_mensuel_by_fournisseur / pg_get_hit_parade). Fournisseur préféré résolu
|
/// que pg_get_mensuel_by_fournisseur / pg_get_hit_parade). Fournisseur préféré résolu
|
||||||
/// via une sous-requête artfou1 dédupliquée (DISTINCT ON) : un seul code par
|
/// via une sous-requête artfou1 dédupliquée (DISTINCT ON) : un seul code par
|
||||||
@@ -1128,8 +1129,7 @@ pub async fn pg_get_ca_by_fournisseur(mois: &str, mois_n1: &str) -> anyhow::Resu
|
|||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
SELECT DISTINCT ON (art_no_id) art_no_id, code
|
SELECT DISTINCT ON (art_no_id) art_no_id, code
|
||||||
FROM artfou1
|
FROM artfou1
|
||||||
WHERE preference = 1
|
ORDER BY art_no_id, (preference = 1) DESC NULLS LAST, no_id DESC
|
||||||
ORDER BY art_no_id, code
|
|
||||||
) af ON af.art_no_id = a.no_id
|
) af ON af.art_no_id = a.no_id
|
||||||
LEFT JOIN fouident fi ON fi.code = af.code
|
LEFT JOIN fouident fi ON fi.code = af.code
|
||||||
WHERE ",
|
WHERE ",
|
||||||
@@ -1231,7 +1231,8 @@ pub struct HitParadeRow {
|
|||||||
/// portant le même codein (ré-créations d'article), la plus récente d'abord.
|
/// portant le même codein (ré-créations d'article), la plus récente d'abord.
|
||||||
/// Exiger `preference = 1` laissait en « Sans fournisseur » les articles dont
|
/// Exiger `preference = 1` laissait en « Sans fournisseur » les articles dont
|
||||||
/// la préférence n'est pas renseignée dans FF ou n'est posée que sur une
|
/// la préférence n'est pas renseignée dans FF ou n'est posée que sur une
|
||||||
/// ancienne fiche du même codein.
|
/// ancienne fiche du même codein. Sans aucune ligne `artfou1`, on prend le
|
||||||
|
/// fournisseur des données du réseau (cache Qlik, par code centrale).
|
||||||
///
|
///
|
||||||
/// Cas vérifié (codein 487673, site 579, jan→juil 2026) : 31 ventes brutes,
|
/// Cas vérifié (codein 487673, site 579, jan→juil 2026) : 31 ventes brutes,
|
||||||
/// 7 retours clients → 24 net. L'ancien SUM(ABS(qtemvt)) affichait 38 (retours
|
/// 7 retours clients → 24 net. L'ancien SUM(ABS(qtemvt)) affichait 38 (retours
|
||||||
@@ -1272,12 +1273,13 @@ pub async fn pg_get_hit_parade(date_debut: &str, date_fin: &str) -> anyhow::Resu
|
|||||||
TRIM(a.codein::text) AS codein,
|
TRIM(a.codein::text) AS codein,
|
||||||
a.libelle1::text AS libelle,
|
a.libelle1::text AS libelle,
|
||||||
COALESCE(TRIM(af.reference::text), '') AS reference,
|
COALESCE(TRIM(af.reference::text), '') AS reference,
|
||||||
COALESCE(fi.nom, af.code, 'Sans fournisseur')::text AS fournisseur,
|
COALESCE(fi.nom, af.code, qn.fournisseur_reseau, 'Sans fournisseur')::text AS fournisseur,
|
||||||
COALESCE(n.code, '')::text AS nomenclature_code,
|
COALESCE(n.code, '')::text AS nomenclature_code,
|
||||||
COALESCE(n.libelle, 'Sans nomenclature')::text AS nomenclature
|
COALESCE(n.libelle, 'Sans nomenclature')::text AS nomenclature
|
||||||
FROM articles a
|
FROM articles a
|
||||||
LEFT JOIN fou af ON af.codein = TRIM(a.codein::text)
|
LEFT JOIN fou af ON af.codein = TRIM(a.codein::text)
|
||||||
LEFT JOIN fouident fi ON fi.code = af.code
|
LEFT JOIN fouident fi ON fi.code = af.code
|
||||||
|
LEFT JOIN qlik_network_metrics qn ON qn.code_centrale = TRIM(a.artcentrale::text)
|
||||||
LEFT JOIN nomenclature n ON n.no_id = a.nom_no_id
|
LEFT JOIN nomenclature n ON n.no_id = a.nom_no_id
|
||||||
WHERE TRIM(a.codein::text) IN (SELECT codein FROM ventes)
|
WHERE TRIM(a.codein::text) IN (SELECT codein FROM ventes)
|
||||||
ORDER BY TRIM(a.codein::text), a.no_id DESC
|
ORDER BY TRIM(a.codein::text), a.no_id DESC
|
||||||
@@ -1488,13 +1490,27 @@ fn last_entry_join(link: Option<MvtartFouLink>, artnoid_expr: &str, site_expr: &
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fournisseur d'un article `a`, en jointures FACULTATIVES : un article sans
|
||||||
|
/// fournisseur reste listé (en « Sans fournisseur ») au lieu de disparaître.
|
||||||
|
/// - `af` / `f` : le principal (`artfou1.preference = 1`) s'il existe, sinon
|
||||||
|
/// le fournisseur référencé le plus récent ;
|
||||||
|
/// - `qn` : le fournisseur indiqué par les données du réseau (cache Qlik),
|
||||||
|
/// pour l'article qu'aucune ligne `artfou1` ne rattache (ex. codein 504202).
|
||||||
|
const FOURNISSEUR_ARTICLE_JOIN: &str = "LEFT JOIN (
|
||||||
|
SELECT DISTINCT ON (art_no_id) art_no_id, code
|
||||||
|
FROM artfou1
|
||||||
|
ORDER BY art_no_id, (preference = 1) DESC NULLS LAST, no_id DESC
|
||||||
|
) af ON af.art_no_id = a.no_id
|
||||||
|
LEFT JOIN fouident f ON f.code = af.code
|
||||||
|
LEFT JOIN qlik_network_metrics qn ON qn.code_centrale = TRIM(a.artcentrale::text)";
|
||||||
|
|
||||||
/// Libellé fournisseur affiché : celui de la dernière entrée quand la base le
|
/// Libellé fournisseur affiché : celui de la dernière entrée quand la base le
|
||||||
/// permet, sinon repli documenté sur `artfou1.preference = 1`.
|
/// permet, sinon celui de `FOURNISSEUR_ARTICLE_JOIN`.
|
||||||
fn fournisseur_expr(link: Option<MvtartFouLink>) -> &'static str {
|
fn fournisseur_expr(link: Option<MvtartFouLink>) -> &'static str {
|
||||||
if link.is_some() {
|
if link.is_some() {
|
||||||
"COALESCE(flast.nom, lastentry.code, f.nom, af.code)"
|
"COALESCE(flast.nom, lastentry.code, f.nom, af.code, qn.fournisseur_reseau, 'Sans fournisseur')"
|
||||||
} else {
|
} else {
|
||||||
"COALESCE(f.nom, af.code)"
|
"COALESCE(f.nom, af.code, qn.fournisseur_reseau, 'Sans fournisseur')"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1604,13 +1620,7 @@ pub async fn pg_get_stock_sans_vente(site: Option<&str>) -> anyhow::Result<Vec<P
|
|||||||
MAX(m.datmvt)::text AS derniere_entree
|
MAX(m.datmvt)::text AS derniere_entree
|
||||||
FROM mvtart m
|
FROM mvtart m
|
||||||
JOIN articles a ON a.no_id = m.artnoid
|
JOIN articles a ON a.no_id = m.artnoid
|
||||||
JOIN (
|
{FOURNISSEUR_ARTICLE_JOIN}
|
||||||
SELECT DISTINCT ON (art_no_id) art_no_id, code
|
|
||||||
FROM artfou1
|
|
||||||
WHERE preference = 1
|
|
||||||
ORDER BY art_no_id, code
|
|
||||||
) af ON af.art_no_id = a.no_id
|
|
||||||
JOIN fouident f ON f.code = af.code
|
|
||||||
LEFT JOIN cube_stock cs ON cs.artnoid = a.no_id AND cs.site = m.site
|
LEFT JOIN cube_stock cs ON cs.artnoid = a.no_id AND cs.site = m.site
|
||||||
{}
|
{}
|
||||||
WHERE m.genremvt IN (1, 2)
|
WHERE m.genremvt IN (1, 2)
|
||||||
@@ -1663,13 +1673,7 @@ pub async fn pg_get_stock_negatif(site: Option<&str>) -> anyhow::Result<Vec<PgSt
|
|||||||
lastentry.datmvt::text AS derniereentree
|
lastentry.datmvt::text AS derniereentree
|
||||||
FROM cube_stock cs
|
FROM cube_stock cs
|
||||||
JOIN articles a ON a.no_id = cs.artnoid
|
JOIN articles a ON a.no_id = cs.artnoid
|
||||||
JOIN (
|
{FOURNISSEUR_ARTICLE_JOIN}
|
||||||
SELECT DISTINCT ON (art_no_id) art_no_id, code
|
|
||||||
FROM artfou1
|
|
||||||
WHERE preference = 1
|
|
||||||
ORDER BY art_no_id, code
|
|
||||||
) af ON af.art_no_id = a.no_id
|
|
||||||
JOIN fouident f ON f.code = af.code
|
|
||||||
{}
|
{}
|
||||||
WHERE cs.qte < 0
|
WHERE cs.qte < 0
|
||||||
AND cs.site IN ('292', '579')
|
AND cs.site IN ('292', '579')
|
||||||
@@ -1737,13 +1741,7 @@ pub async fn pg_get_sans_vente_6_mois(site: Option<&str>) -> anyhow::Result<Vec<
|
|||||||
lastentry.datmvt::text AS derniere_entree
|
lastentry.datmvt::text AS derniere_entree
|
||||||
FROM cube_stock cs
|
FROM cube_stock cs
|
||||||
JOIN articles a ON a.no_id = cs.artnoid
|
JOIN articles a ON a.no_id = cs.artnoid
|
||||||
JOIN (
|
{FOURNISSEUR_ARTICLE_JOIN}
|
||||||
SELECT DISTINCT ON (art_no_id) art_no_id, code
|
|
||||||
FROM artfou1
|
|
||||||
WHERE preference = 1
|
|
||||||
ORDER BY art_no_id, code
|
|
||||||
) af ON af.art_no_id = a.no_id
|
|
||||||
JOIN fouident f ON f.code = af.code
|
|
||||||
{}
|
{}
|
||||||
WHERE cs.site IN ('292', '579')
|
WHERE cs.site IN ('292', '579')
|
||||||
AND cs.qte > 0
|
AND cs.qte > 0
|
||||||
@@ -1995,7 +1993,8 @@ fn colonne_iso(row: &PgRow, col: &str) -> anyhow::Result<Option<String>> {
|
|||||||
///
|
///
|
||||||
/// Source : mvtart (genremvt IN (1, 2) = réceptions, voir pg_get_mensuel_by_fournisseur).
|
/// Source : mvtart (genremvt IN (1, 2) = réceptions, voir pg_get_mensuel_by_fournisseur).
|
||||||
/// La réception est rattachée à l'article ; on l'attribue au fournisseur
|
/// La réception est rattachée à l'article ; on l'attribue au fournisseur
|
||||||
/// principal de l'article (artfou1.preference = 1), comme partout ailleurs.
|
/// principal de l'article (artfou1.preference = 1), sinon au fournisseur
|
||||||
|
/// référencé le plus récent, comme partout ailleurs.
|
||||||
///
|
///
|
||||||
/// Clé de la Map : `${codefou}|${site}`. Map vide en cas d'erreur
|
/// Clé de la Map : `${codefou}|${site}`. Map vide en cas d'erreur
|
||||||
/// (l'UI affichera alors « échéance inconnue »).
|
/// (l'UI affichera alors « échéance inconnue »).
|
||||||
@@ -2014,7 +2013,11 @@ pub async fn pg_get_derniere_reception_par_fournisseur(codes: Option<&[String]>)
|
|||||||
MAX(m.datmvt) AS derniere
|
MAX(m.datmvt) AS derniere
|
||||||
FROM mvtart m
|
FROM mvtart m
|
||||||
JOIN articles a ON a.no_id = m.artnoid
|
JOIN articles a ON a.no_id = m.artnoid
|
||||||
JOIN artfou1 af ON af.art_no_id = a.no_id AND af.preference = 1
|
JOIN (
|
||||||
|
SELECT DISTINCT ON (art_no_id) art_no_id, code
|
||||||
|
FROM artfou1
|
||||||
|
ORDER BY art_no_id, (preference = 1) DESC NULLS LAST, no_id DESC
|
||||||
|
) af ON af.art_no_id = a.no_id
|
||||||
WHERE m.genremvt IN (1, 2)
|
WHERE m.genremvt IN (1, 2)
|
||||||
AND m.site IN ('292', '579')
|
AND m.site IN ('292', '579')
|
||||||
AND m.datmvt IS NOT NULL
|
AND m.datmvt IS NOT NULL
|
||||||
@@ -2066,7 +2069,8 @@ pub struct PgProduitSearchRow {
|
|||||||
pub codein: String,
|
pub codein: String,
|
||||||
pub code_centrale: String,
|
pub code_centrale: String,
|
||||||
pub libelle1: String,
|
pub libelle1: String,
|
||||||
/// Nom du fournisseur principal (artfou1.preference = 1), sinon son code.
|
/// Nom du fournisseur principal (artfou1.preference = 1), sinon d'un autre
|
||||||
|
/// fournisseur référencé, sinon celui des données du réseau.
|
||||||
pub fournisseur: String,
|
pub fournisseur: String,
|
||||||
pub codefou: String,
|
pub codefou: String,
|
||||||
pub nomenclature_code: String,
|
pub nomenclature_code: String,
|
||||||
@@ -2255,7 +2259,7 @@ pub async fn pg_search_produits(term: &str, limit: i64) -> Vec<PgProduitSearchRo
|
|||||||
m.codein,
|
m.codein,
|
||||||
m.code_centrale,
|
m.code_centrale,
|
||||||
m.libelle1,
|
m.libelle1,
|
||||||
COALESCE(fi.nom, af.code, 'Sans fournisseur')::text AS fournisseur,
|
COALESCE(fi.nom, af.code, q.fournisseur_reseau, 'Sans fournisseur')::text AS fournisseur,
|
||||||
COALESCE(af.code, '')::text AS codefou,
|
COALESCE(af.code, '')::text AS codefou,
|
||||||
COALESCE(n.code, '')::text AS nomenclature_code,
|
COALESCE(n.code, '')::text AS nomenclature_code,
|
||||||
COALESCE(n.libelle, '')::text AS nomenclature,
|
COALESCE(n.libelle, '')::text AS nomenclature,
|
||||||
@@ -2341,7 +2345,7 @@ pub async fn pg_get_produits_by_code_centrale(codes_centraux: &[String]) -> Hash
|
|||||||
m.codein,
|
m.codein,
|
||||||
m.code_centrale,
|
m.code_centrale,
|
||||||
m.libelle1,
|
m.libelle1,
|
||||||
COALESCE(fi.nom, af.code, 'Sans fournisseur')::text AS fournisseur,
|
COALESCE(fi.nom, af.code, q.fournisseur_reseau, 'Sans fournisseur')::text AS fournisseur,
|
||||||
COALESCE(af.code, '')::text AS codefou,
|
COALESCE(af.code, '')::text AS codefou,
|
||||||
COALESCE(n.code, '')::text AS nomenclature_code,
|
COALESCE(n.code, '')::text AS nomenclature_code,
|
||||||
COALESCE(n.libelle, '')::text AS nomenclature,
|
COALESCE(n.libelle, '')::text AS nomenclature,
|
||||||
@@ -2364,6 +2368,7 @@ pub async fn pg_get_produits_by_code_centrale(codes_centraux: &[String]) -> Hash
|
|||||||
FROM cube_stock cs
|
FROM cube_stock cs
|
||||||
WHERE cs.artnoid = m.no_id AND cs.site IN ('292', '579')
|
WHERE cs.artnoid = m.no_id AND cs.site IN ('292', '579')
|
||||||
) st ON TRUE
|
) st ON TRUE
|
||||||
|
LEFT JOIN qlik_network_metrics q ON q.code_centrale = m.code_centrale
|
||||||
",
|
",
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2880,7 +2885,8 @@ pub async fn pg_get_opportunites_famille(nom_no_id: i64, date_debut: &str, date_
|
|||||||
q.qte_reseau::float AS qte_reseau,
|
q.qte_reseau::float AS qte_reseau,
|
||||||
q.nb_magasins_reseau AS nb_magasins_reseau,
|
q.nb_magasins_reseau AS nb_magasins_reseau,
|
||||||
COALESCE(q.ca_reseau, 0)::float AS ca_reseau,
|
COALESCE(q.ca_reseau, 0)::float AS ca_reseau,
|
||||||
COALESCE(q.ca_par_magasin_reseau, 0)::float AS ca_par_magasin_reseau
|
COALESCE(q.ca_par_magasin_reseau, 0)::float AS ca_par_magasin_reseau,
|
||||||
|
q.fournisseur_reseau
|
||||||
FROM famille f
|
FROM famille f
|
||||||
JOIN qlik_network_metrics q ON q.code_centrale = f.code_centrale
|
JOIN qlik_network_metrics q ON q.code_centrale = f.code_centrale
|
||||||
WHERE q.nb_magasins_reseau > 0
|
WHERE q.nb_magasins_reseau > 0
|
||||||
@@ -2903,7 +2909,7 @@ pub async fn pg_get_opportunites_famille(nom_no_id: i64, date_debut: &str, date_
|
|||||||
r.codein,
|
r.codein,
|
||||||
r.libelle1,
|
r.libelle1,
|
||||||
r.code_centrale,
|
r.code_centrale,
|
||||||
COALESCE(fi.nom, af.code, 'Sans fournisseur')::text AS fournisseur,
|
COALESCE(fi.nom, af.code, r.fournisseur_reseau, 'Sans fournisseur')::text AS fournisseur,
|
||||||
r.qte_reseau,
|
r.qte_reseau,
|
||||||
r.nb_magasins_reseau,
|
r.nb_magasins_reseau,
|
||||||
r.ca_reseau,
|
r.ca_reseau,
|
||||||
|
|||||||
+10
-3
@@ -72,11 +72,18 @@ pub async fn test_db(_admin: AdminUser, Query(q): Query<HashMap<String, String>>
|
|||||||
r#"
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
a.codein::text AS codein,
|
a.codein::text AS codein,
|
||||||
COALESCE(fi.nom, af.code, 'Sans fournisseur')::text AS fournisseur
|
COALESCE(fi.nom, af.code, q.fournisseur_reseau, 'Sans fournisseur')::text AS fournisseur
|
||||||
FROM articles a
|
FROM articles a
|
||||||
LEFT JOIN artfou1 af ON af.art_no_id = a.no_id AND af.preference = 1
|
LEFT JOIN LATERAL (
|
||||||
|
SELECT af1.code
|
||||||
|
FROM artfou1 af1
|
||||||
|
WHERE af1.art_no_id = a.no_id
|
||||||
|
ORDER BY (af1.preference = 1) DESC NULLS LAST, af1.no_id DESC
|
||||||
|
LIMIT 1
|
||||||
|
) af ON TRUE
|
||||||
LEFT JOIN fouident fi ON fi.code = af.code
|
LEFT JOIN fouident fi ON fi.code = af.code
|
||||||
WHERE a.codein::text = $1
|
LEFT JOIN qlik_network_metrics q ON q.code_centrale = TRIM(a.artcentrale::text)
|
||||||
|
WHERE TRIM(a.codein::text) = $1
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.bind(&codein)
|
.bind(&codein)
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
//! Test d'intégration contre une vraie base PostgreSQL : rattachement d'un
|
||||||
|
//! article à son fournisseur quand aucun n'est marqué principal, ou quand
|
||||||
|
//! aucune ligne `artfou1` n'existe (cas du codein 504202).
|
||||||
|
//!
|
||||||
|
//! Désactivé par défaut. Pour le lancer (base JETABLE : les tables FF de test
|
||||||
|
//! y sont recréées) :
|
||||||
|
//!
|
||||||
|
//! ```sh
|
||||||
|
//! FF_FOURNISSEUR_TEST_DATABASE_URL=postgres://postgres:postgres@localhost/fftest cargo test ff::tests_fournisseur_db
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
use super::pg::*;
|
||||||
|
|
||||||
|
const SCHEMA: &str = r#"
|
||||||
|
DROP TABLE IF EXISTS articles, artfou1, fouident, mvtart, cube_stock, nomenclature;
|
||||||
|
CREATE TABLE articles (no_id bigint PRIMARY KEY, codein char(10), artcentrale char(20), libelle1 text, nom_no_id bigint, suspendu text);
|
||||||
|
CREATE TABLE artfou1 (no_id bigint PRIMARY KEY, art_no_id bigint, code text, preference int, reference text, pcb numeric);
|
||||||
|
CREATE TABLE fouident (code text PRIMARY KEY, nom text);
|
||||||
|
CREATE TABLE nomenclature (no_id bigint PRIMARY KEY, code text, libelle text);
|
||||||
|
CREATE TABLE mvtart (no_id bigint PRIMARY KEY, artnoid bigint, site text, genremvt int, datmvt date, qtemvt numeric, mntmvtttc numeric, margemvt numeric);
|
||||||
|
CREATE TABLE cube_stock (artnoid bigint, site text, qte numeric, dernierevente date);
|
||||||
|
|
||||||
|
INSERT INTO fouident VALUES ('F1', 'ALPHA'), ('F2', 'BETA');
|
||||||
|
-- 1 : aucune ligne artfou1, fournisseur connu du réseau (cas 504202)
|
||||||
|
-- 2 : un seul fournisseur, non marqué principal
|
||||||
|
-- 3 : un principal (F2) et un secondaire (F1)
|
||||||
|
-- 4 : aucun fournisseur nulle part
|
||||||
|
INSERT INTO articles VALUES
|
||||||
|
(1, '504202', 'CC504202', 'Article sans artfou1', NULL, NULL),
|
||||||
|
(2, '100001', 'CC100001', 'Article sans principal', NULL, NULL),
|
||||||
|
(3, '100002', 'CC100002', 'Article avec principal', NULL, NULL),
|
||||||
|
(4, '100003', 'CC100003', 'Article orphelin', NULL, NULL);
|
||||||
|
INSERT INTO artfou1 VALUES (10, 2, 'F1', 0, 'R2', 1), (11, 3, 'F1', 0, 'R3a', 1), (12, 3, 'F2', 1, 'R3b', 1);
|
||||||
|
INSERT INTO cube_stock VALUES (1, '292', -2, NULL), (2, '292', -3, NULL), (3, '292', -4, NULL), (4, '292', -5, NULL);
|
||||||
|
INSERT INTO mvtart VALUES
|
||||||
|
(100, 1, '292', 1, DATE '2020-01-10', 10, 0, 0),
|
||||||
|
(101, 2, '292', 1, DATE '2020-01-10', 10, 0, 0),
|
||||||
|
(102, 3, '292', 1, DATE '2020-01-10', 10, 0, 0),
|
||||||
|
(103, 4, '292', 1, DATE '2020-01-10', 10, 0, 0),
|
||||||
|
(110, 1, '292', 3, DATE '2026-03-05', -1, -10, 3),
|
||||||
|
(111, 2, '292', 3, DATE '2026-03-05', -1, -10, 3),
|
||||||
|
(112, 3, '292', 3, DATE '2026-03-05', -1, -10, 3),
|
||||||
|
(113, 4, '292', 3, DATE '2026-03-05', -1, -10, 3);
|
||||||
|
DELETE FROM qlik_network_metrics WHERE code_centrale LIKE 'CC%';
|
||||||
|
INSERT INTO qlik_network_metrics (code_centrale, fournisseur_reseau) VALUES ('CC504202', 'FOURNISSEUR RESEAU');
|
||||||
|
"#;
|
||||||
|
|
||||||
|
fn attendu(codein: &str) -> &'static str {
|
||||||
|
match codein {
|
||||||
|
"504202" => "FOURNISSEUR RESEAU",
|
||||||
|
"100001" => "ALPHA",
|
||||||
|
"100002" => "BETA",
|
||||||
|
"100003" => "Sans fournisseur",
|
||||||
|
autre => panic!("codein inattendu : {autre}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn article_sans_fournisseur_principal() {
|
||||||
|
let Ok(url) = std::env::var("FF_FOURNISSEUR_TEST_DATABASE_URL") else {
|
||||||
|
eprintln!("FF_FOURNISSEUR_TEST_DATABASE_URL absent : test d'intégration ignoré.");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let _verrou = crate::grid::rows_cache::TEST_LOCK.lock();
|
||||||
|
let dir = std::env::temp_dir().join(format!("ff-tests-{}", std::process::id()));
|
||||||
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
|
std::env::set_var("DATA_DIR", &dir);
|
||||||
|
std::env::set_var("DATABASE_URL", &url);
|
||||||
|
crate::db::refresh();
|
||||||
|
let pool = crate::db::pool().unwrap();
|
||||||
|
sqlx::raw_sql(include_str!("../../sql/init.sql")).execute(&pool).await.unwrap();
|
||||||
|
sqlx::raw_sql(SCHEMA).execute(&pool).await.unwrap();
|
||||||
|
|
||||||
|
// Stock négatif : les 4 articles sont listés, aucun n'est écarté faute de principal.
|
||||||
|
let neg = pg_get_stock_negatif(None).await.unwrap();
|
||||||
|
assert_eq!(neg.len(), 4, "{neg:?}");
|
||||||
|
for r in &neg {
|
||||||
|
assert_eq!(r.fournisseur, attendu(r.codein.trim()), "stock négatif {}", r.codein);
|
||||||
|
}
|
||||||
|
|
||||||
|
let hp = pg_get_hit_parade("2026-03-01", "2026-03-31").await.unwrap();
|
||||||
|
assert_eq!(hp.len(), 4, "{hp:?}");
|
||||||
|
for r in &hp {
|
||||||
|
assert_eq!(r.fournisseur, attendu(&r.codein), "hit parade {}", r.codein);
|
||||||
|
}
|
||||||
|
|
||||||
|
let ca = pg_get_ca_by_fournisseur("2026-03", "2025-03").await.unwrap();
|
||||||
|
let nom = |code: &str| ca.iter().find(|r| r.code == code).map(|r| r.nom.clone());
|
||||||
|
assert_eq!(nom("F1").as_deref(), Some("ALPHA"), "{ca:?}");
|
||||||
|
assert_eq!(nom("F2").as_deref(), Some("BETA"), "{ca:?}");
|
||||||
|
assert_eq!(nom("SANS_FOURNISSEUR").as_deref(), Some("Sans fournisseur"), "{ca:?}");
|
||||||
|
|
||||||
|
let receptions = pg_get_derniere_reception_par_fournisseur(None).await;
|
||||||
|
assert!(receptions.contains_key("F1|292"), "{receptions:?}");
|
||||||
|
|
||||||
|
let trouves = pg_search_produits("504202", 10).await;
|
||||||
|
assert_eq!(trouves.first().map(|r| r.fournisseur.as_str()), Some("FOURNISSEUR RESEAU"), "{trouves:?}");
|
||||||
|
|
||||||
|
let catalogue = pg_get_produits_by_code_centrale(&["CC504202".into(), "CC100001".into(), "CC100003".into()]).await;
|
||||||
|
assert_eq!(catalogue["CC504202"].fournisseur, "FOURNISSEUR RESEAU");
|
||||||
|
assert_eq!(catalogue["CC100001"].fournisseur, "ALPHA");
|
||||||
|
assert_eq!(catalogue["CC100003"].fournisseur, "Sans fournisseur");
|
||||||
|
assert!(catalogue["CC504202"].codefou.is_empty());
|
||||||
|
}
|
||||||
@@ -502,7 +502,14 @@ async fn executer_recherche(term: &str, on_etape: impl Fn(&str)) -> ProduitReche
|
|||||||
code_centrale: m.code_centrale.clone(),
|
code_centrale: m.code_centrale.clone(),
|
||||||
libelle: ou(ou(local.map_or("", |l| l.libelle1.as_str()), &m.libelle), &m.code_centrale).to_string(),
|
libelle: ou(ou(local.map_or("", |l| l.libelle1.as_str()), &m.libelle), &m.code_centrale).to_string(),
|
||||||
libelle_reseau: m.libelle.clone(),
|
libelle_reseau: m.libelle.clone(),
|
||||||
fournisseur: ou(local.map_or("", |l| l.fournisseur.as_str()), &m.fournisseur).to_string(),
|
// Fournisseur de notre catalogue, sinon celui que Qlik vient de
|
||||||
|
// renvoyer, sinon le repli du catalogue (cache réseau ou
|
||||||
|
// « Sans fournisseur ») : ce repli ne doit pas masquer Qlik.
|
||||||
|
fournisseur: ou(
|
||||||
|
ou(local.filter(|l| !l.codefou.is_empty()).map_or("", |l| l.fournisseur.as_str()), &m.fournisseur),
|
||||||
|
local.map_or("", |l| l.fournisseur.as_str()),
|
||||||
|
)
|
||||||
|
.to_string(),
|
||||||
codein: local.map(|l| l.codein.clone()),
|
codein: local.map(|l| l.codein.clone()),
|
||||||
nomenclature: local.map(|l| l.nomenclature.clone()).unwrap_or_default(),
|
nomenclature: local.map(|l| l.nomenclature.clone()).unwrap_or_default(),
|
||||||
stock_local: local.map(|l| if l.stock_total.is_nan() { 0.0 } else { l.stock_total }),
|
stock_local: local.map(|l| if l.stock_total.is_nan() { 0.0 } else { l.stock_total }),
|
||||||
|
|||||||
Reference in new issue
Block a user