mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-19 13:08:11 +02:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e93564d35c |
No files matched your search
@@ -13,6 +13,8 @@ pub mod routes;
|
||||
|
||||
#[cfg(test)]
|
||||
mod fixture_tests;
|
||||
#[cfg(test)]
|
||||
mod tests_fournisseur_db;
|
||||
|
||||
use crate::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).
|
||||
/// 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
|
||||
/// 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
|
||||
@@ -1128,8 +1129,7 @@ pub async fn pg_get_ca_by_fournisseur(mois: &str, mois_n1: &str) -> anyhow::Resu
|
||||
LEFT JOIN (
|
||||
SELECT DISTINCT ON (art_no_id) art_no_id, code
|
||||
FROM artfou1
|
||||
WHERE preference = 1
|
||||
ORDER BY art_no_id, code
|
||||
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 fi ON fi.code = af.code
|
||||
WHERE ",
|
||||
@@ -1231,7 +1231,8 @@ pub struct HitParadeRow {
|
||||
/// 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
|
||||
/// 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,
|
||||
/// 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,
|
||||
a.libelle1::text AS libelle,
|
||||
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.libelle, 'Sans nomenclature')::text AS nomenclature
|
||||
FROM articles a
|
||||
LEFT JOIN fou af ON af.codein = TRIM(a.codein::text)
|
||||
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
|
||||
WHERE TRIM(a.codein::text) IN (SELECT codein FROM ventes)
|
||||
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
|
||||
/// permet, sinon repli documenté sur `artfou1.preference = 1`.
|
||||
/// permet, sinon celui de `FOURNISSEUR_ARTICLE_JOIN`.
|
||||
fn fournisseur_expr(link: Option<MvtartFouLink>) -> &'static str {
|
||||
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 {
|
||||
"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
|
||||
FROM mvtart m
|
||||
JOIN articles a ON a.no_id = m.artnoid
|
||||
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
|
||||
{FOURNISSEUR_ARTICLE_JOIN}
|
||||
LEFT JOIN cube_stock cs ON cs.artnoid = a.no_id AND cs.site = m.site
|
||||
{}
|
||||
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
|
||||
FROM cube_stock cs
|
||||
JOIN articles a ON a.no_id = cs.artnoid
|
||||
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
|
||||
{FOURNISSEUR_ARTICLE_JOIN}
|
||||
{}
|
||||
WHERE cs.qte < 0
|
||||
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
|
||||
FROM cube_stock cs
|
||||
JOIN articles a ON a.no_id = cs.artnoid
|
||||
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
|
||||
{FOURNISSEUR_ARTICLE_JOIN}
|
||||
{}
|
||||
WHERE cs.site IN ('292', '579')
|
||||
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).
|
||||
/// 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
|
||||
/// (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
|
||||
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.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)
|
||||
AND m.site IN ('292', '579')
|
||||
AND m.datmvt IS NOT NULL
|
||||
@@ -2066,7 +2069,8 @@ pub struct PgProduitSearchRow {
|
||||
pub codein: String,
|
||||
pub code_centrale: 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 codefou: String,
|
||||
pub nomenclature_code: String,
|
||||
@@ -2255,7 +2259,7 @@ pub async fn pg_search_produits(term: &str, limit: i64) -> Vec<PgProduitSearchRo
|
||||
m.codein,
|
||||
m.code_centrale,
|
||||
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(n.code, '')::text AS nomenclature_code,
|
||||
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.code_centrale,
|
||||
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(n.code, '')::text AS nomenclature_code,
|
||||
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
|
||||
WHERE cs.artnoid = m.no_id AND cs.site IN ('292', '579')
|
||||
) 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.nb_magasins_reseau AS nb_magasins_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
|
||||
JOIN qlik_network_metrics q ON q.code_centrale = f.code_centrale
|
||||
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.libelle1,
|
||||
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.nb_magasins_reseau,
|
||||
r.ca_reseau,
|
||||
|
||||
+10
-3
@@ -72,11 +72,18 @@ pub async fn test_db(_admin: AdminUser, Query(q): Query<HashMap<String, String>>
|
||||
r#"
|
||||
SELECT
|
||||
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
|
||||
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
|
||||
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)
|
||||
|
||||
@@ -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(),
|
||||
libelle: ou(ou(local.map_or("", |l| l.libelle1.as_str()), &m.libelle), &m.code_centrale).to_string(),
|
||||
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()),
|
||||
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 }),
|
||||
|
||||
Reference in new issue
Block a user