diff --git a/server/src/ff/pg.rs b/server/src/ff/pg.rs index 0ef6be4..3fe24e9 100644 --- a/server/src/ff/pg.rs +++ b/server/src/ff/pg.rs @@ -780,8 +780,11 @@ async fn enrichir_article_dashboard(base: &str, codein: &str, out: &mut Enrichis return Ok(()); } let art_data: Value = art_res.json().await?; + // L'article dont le codein est bien celui demandé, sinon le premier renvoyé. let first = match get(&art_data, "articles") { - Some(Value::Array(a)) if !a.is_empty() => a[0].clone(), + Some(Value::Array(a)) if !a.is_empty() => { + a.iter().find(|x| str_field(x, "codein").trim() == codein.trim()).unwrap_or(&a[0]).clone() + } _ => return Ok(()), }; let no_id = match get(&first, "no_id") { @@ -791,9 +794,8 @@ async fn enrichir_article_dashboard(base: &str, codein: &str, out: &mut Enrichis let url_ref = format!("{base}/api/articles/{}/referentiel", encode(&no_id)); let url_dernier = format!("{base}/api/articles/{}/dernier-fournisseur", encode(&no_id)); - let (ref_res, dernier_res) = tokio::join!(ff_get(&url_ref), ff_get(&url_dernier)); + let (ref_res, dernier) = tokio::join!(ff_get(&url_ref), fournisseur_derniere_entree(&url_dernier)); let ref_res = ref_res?; - let dernier_res = dernier_res?; // Stock (toujours depuis referentiel) let mut ref_fournisseur_fallback = String::new(); @@ -832,25 +834,48 @@ async fn enrichir_article_dashboard(base: &str, codein: &str, out: &mut Enrichis } } - // Fournisseur réel : /dernier-fournisseur (dernière entrée en stock par site) - // Fallback : premier non-dépôt du référentiel - if dernier_res.status().is_success() { - let dernier_data: Value = dernier_res.json().await?; - // Prendre le fournisseur du premier site disponible - let first = arr(&dernier_data, "fournisseurs_par_site") - .iter() - .find(|e| get(e, "nom_fournisseur").map(truthy).unwrap_or(false)); - if let Some(e) = first { - out.fournisseur = Some(str_field(e, "nom_fournisseur").trim().to_string()); - } else if !ref_fournisseur_fallback.is_empty() { - out.fournisseur = Some(ref_fournisseur_fallback); - } - } else if !ref_fournisseur_fallback.is_empty() { - out.fournisseur = Some(ref_fournisseur_fallback); + // Fournisseur réel : /dernier-fournisseur (dernière entrée en stock), + // sinon le fournisseur du référentiel. + match dernier? { + Some(f) => out.fournisseur = Some(f), + None if !ref_fournisseur_fallback.is_empty() => out.fournisseur = Some(ref_fournisseur_fallback), + None => {} } Ok(()) } +/// Fournisseur de la dernière entrée en stock d'un article, tous magasins +/// confondus : la plus récente (`date_derniere_entree`), sinon la première. +/// +/// Sans `?site=`, l'API ne renvoie pas toujours tous les magasins : pour le +/// codein 503996, seule `?site=579` donne l'entrée (P024 PUCKATOR BV, reçue +/// à Houdemont). On interroge donc aussi chaque magasin. +async fn fournisseur_derniere_entree(url: &str) -> anyhow::Result> { + let urls = [url.to_string(), format!("{url}?site=292"), format!("{url}?site=579")]; + let reponses = futures::future::join_all(urls.iter().map(|u| ff_get(u))).await; + let mut meilleur: Option<(String, String)> = None; // (date, fournisseur) + for res in reponses { + let res = res?; + if !res.status().is_success() { + continue; + } + let data: Value = res.json().await?; + // Entrées sous `fournisseurs_par_site`, ou l'objet lui-même. + let entrees = arr(&data, "fournisseurs_par_site").iter().chain(std::iter::once(&data)); + for e in entrees { + let nom = str_field(e, "nom_fournisseur").trim().to_string(); + if nom.is_empty() { + continue; + } + let date = str_field(e, "date_derniere_entree"); + if meilleur.as_ref().is_none_or(|(d, _)| date > *d) { + meilleur = Some((date, nom)); + } + } + } + Ok(meilleur.map(|(_, f)| f)) +} + // --------------------------------------------------------------------------- // Fournisseur manquant — repli sur l'API FF en direct // --------------------------------------------------------------------------- diff --git a/server/src/ff/tests_fournisseur_db.rs b/server/src/ff/tests_fournisseur_db.rs index b3e6da0..9674829 100644 --- a/server/src/ff/tests_fournisseur_db.rs +++ b/server/src/ff/tests_fournisseur_db.rs @@ -108,7 +108,7 @@ async fn article_sans_fournisseur_principal() { let mut hp = hp; completer_fournisseurs(&mut hp, |r| (r.codein.as_str(), &mut r.fournisseur)).await; for r in &hp { - let voulu = if r.codein == "100003" { "FOURNISSEUR API" } else { attendu(&r.codein) }; + let voulu = if r.codein == "100003" { "PUCKATOR BV" } else { attendu(&r.codein) }; assert_eq!(r.fournisseur, voulu, "hit parade complété {}", r.codein); } } @@ -126,13 +126,21 @@ async fn fausse_api() -> String { _ => Json(json!({ "articles": [] })), } } - async fn referentiel(Path(_id): Path) -> Json { - Json(json!({ "stock": [], "fournisseurs": [{ "codefou": "F9", "nom_fou": " FOURNISSEUR API ", "preference": 0 }] })) + // Comme pour le codein 503996 : fiche sans fournisseur, et seule la + // dernière entrée de Houdemont (`?site=579`) donne le fournisseur. + async fn dernier(Path(_id): Path, Query(q): Query>) -> Json { + match q.get("site").map(|s| s.as_str()) { + Some("579") => Json(json!({ "fournisseurs_par_site": [{ + "site": "579", "codefou": "P024", "nom_fournisseur": "PUCKATOR BV ", + "date_derniere_entree": "2026-09-29", "qte_entree": 240, "prmp": 1.16 + }] })), + _ => Json(json!({ "fournisseurs_par_site": [] })), + } } let app = axum::Router::new() .route("/api/articles", get(articles)) - .route("/api/articles/{id}/referentiel", get(referentiel)) - .route("/api/articles/{id}/dernier-fournisseur", get(|| async { Json(json!({ "fournisseurs_par_site": [] })) })); + .route("/api/articles/{id}/referentiel", get(|| async { Json(json!({ "stock": [], "fournisseurs": [] })) })) + .route("/api/articles/{id}/dernier-fournisseur", get(dernier)); let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap(); tokio::spawn(async move { axum::serve(listener, app).await.unwrap() });