Fournisseur : interroger la dernière entrée de chaque magasin

Le repli sur l'API FF n'a rien trouvé pour 503996 : /dernier-fournisseur
appelé sans magasin ne renvoie pas l'entrée de Houdemont. Seul
`?site=579` donne P024 PUCKATOR BV (entrée du 29/09/2026), et la fiche
de l'article n'a aucun fournisseur.

On interroge désormais l'appel sans magasin, `?site=292` et `?site=579`,
et on retient l'entrée la plus récente. On choisit aussi, parmi les
articles renvoyés par /api/articles?codein=, celui dont le codein est
bien celui demandé. Le tableau de bord en profite (même fonction).

Le test reproduit la réponse de l'API pour 503996 ; il échoue sur
l'ancien code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhSZx8CCuqoKrDw7unFqGq
This commit is contained in:
Claude committed 2026-10-19 20:25:22 +00:00
1 parent d3624510eb
commit 5c7efa5540
2 files changed
+56 -23

No files matched your search

+43 -18
View File
@@ -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<Option<String>> {
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
// ---------------------------------------------------------------------------
+13 -5
View File
@@ -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<String>) -> Json<Value> {
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<String>, Query(q): Query<HashMap<String, String>>) -> Json<Value> {
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() });