mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-21 05:58:10 +02:00
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:
2 files changed
+56
-23
No files matched your search
+43
-18
@@ -780,8 +780,11 @@ async fn enrichir_article_dashboard(base: &str, codein: &str, out: &mut Enrichis
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let art_data: Value = art_res.json().await?;
|
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") {
|
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(()),
|
_ => return Ok(()),
|
||||||
};
|
};
|
||||||
let no_id = match get(&first, "no_id") {
|
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_ref = format!("{base}/api/articles/{}/referentiel", encode(&no_id));
|
||||||
let url_dernier = format!("{base}/api/articles/{}/dernier-fournisseur", 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 ref_res = ref_res?;
|
||||||
let dernier_res = dernier_res?;
|
|
||||||
|
|
||||||
// Stock (toujours depuis referentiel)
|
// Stock (toujours depuis referentiel)
|
||||||
let mut ref_fournisseur_fallback = String::new();
|
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)
|
// Fournisseur réel : /dernier-fournisseur (dernière entrée en stock),
|
||||||
// Fallback : premier non-dépôt du référentiel
|
// sinon le fournisseur du référentiel.
|
||||||
if dernier_res.status().is_success() {
|
match dernier? {
|
||||||
let dernier_data: Value = dernier_res.json().await?;
|
Some(f) => out.fournisseur = Some(f),
|
||||||
// Prendre le fournisseur du premier site disponible
|
None if !ref_fournisseur_fallback.is_empty() => out.fournisseur = Some(ref_fournisseur_fallback),
|
||||||
let first = arr(&dernier_data, "fournisseurs_par_site")
|
None => {}
|
||||||
.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);
|
|
||||||
}
|
}
|
||||||
Ok(())
|
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
|
// Fournisseur manquant — repli sur l'API FF en direct
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ async fn article_sans_fournisseur_principal() {
|
|||||||
let mut hp = hp;
|
let mut hp = hp;
|
||||||
completer_fournisseurs(&mut hp, |r| (r.codein.as_str(), &mut r.fournisseur)).await;
|
completer_fournisseurs(&mut hp, |r| (r.codein.as_str(), &mut r.fournisseur)).await;
|
||||||
for r in &hp {
|
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);
|
assert_eq!(r.fournisseur, voulu, "hit parade complété {}", r.codein);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,13 +126,21 @@ async fn fausse_api() -> String {
|
|||||||
_ => Json(json!({ "articles": [] })),
|
_ => Json(json!({ "articles": [] })),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn referentiel(Path(_id): Path<String>) -> Json<Value> {
|
// Comme pour le codein 503996 : fiche sans fournisseur, et seule la
|
||||||
Json(json!({ "stock": [], "fournisseurs": [{ "codefou": "F9", "nom_fou": " FOURNISSEUR API ", "preference": 0 }] }))
|
// 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()
|
let app = axum::Router::new()
|
||||||
.route("/api/articles", get(articles))
|
.route("/api/articles", get(articles))
|
||||||
.route("/api/articles/{id}/referentiel", get(referentiel))
|
.route("/api/articles/{id}/referentiel", get(|| async { Json(json!({ "stock": [], "fournisseurs": [] })) }))
|
||||||
.route("/api/articles/{id}/dernier-fournisseur", get(|| async { Json(json!({ "fournisseurs_par_site": [] })) }));
|
.route("/api/articles/{id}/dernier-fournisseur", get(dernier));
|
||||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||||
let addr = listener.local_addr().unwrap();
|
let addr = listener.local_addr().unwrap();
|
||||||
tokio::spawn(async move { axum::serve(listener, app).await.unwrap() });
|
tokio::spawn(async move { axum::serve(listener, app).await.unwrap() });
|
||||||
|
|||||||
Reference in new issue
Block a user