feat(produits): recherche Qlik d'abord + fiche réseau complète, tendance 12 mois glissants

Recherche produit
- La page /produits interroge désormais Qlik Sense EN PREMIER
  (src/lib/qlik-search.ts) : recherche globale Engine sur les champs article
  (code + libellé détectés dynamiquement via FieldList), sélection des valeurs
  trouvées, puis liste des Article Code correspondants.
- Les métriques réseau des articles trouvés sont extraites sur 12 mois
  glissants et mises en cache (qlik_network_metrics), puis rapprochées du
  catalogue FF Nancy par code centrale (pgGetProduitsByCodeCentrale).
- Repli propre sur la recherche catalogue local si Qlik est injoignable, signalé
  dans l'UI. Résultats mis en cache mémoire 10 min, requête dédupliquée.
- Nouvelle route GET /api/produits/search (runtime nodejs, 300 s) ; la liste est
  chargée côté client avec état de chargement, l'extraction Qlik prenant
  plusieurs secondes.

Informations affichées
- Résultats : magasins vendeurs (+ % du réseau), quantité réseau, quantité par
  magasin, prix moyen réseau, CA par magasin, marge %, sparkline de tendance,
  fournisseur, et présence ou non au catalogue Nancy.
- Fiche : carte « Performance réseau · 12 mois glissants » en tête (8 indicateurs
  + détail mensuel qté / magasins / qté par magasin / CA / prix moyen / marge %),
  puis carte « Fournisseur » dédiée, puis identité, stock, nos ventes 12 mois.
- La fiche s'ouvre aussi par code centrale (?cc=) pour les produits que le réseau
  travaille et que Nancy ne référence pas — le libellé et le fournisseur Qlik
  sont persistés dans le cache réseau pour ce cas.

Tendance réseau
- computeNetworkTrend reconstruit sa fenêtre depuis la date du jour : 12 mois
  complets, mois en cours EXCLU (il est partiel et écrasait la pente). Les mois
  absents du cache valent 0 ; ceux antérieurs à la première extraction réelle
  sont écartés au lieu d'être inventés à 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
This commit is contained in:
Claude committed 2026-07-29 05:04:34 +00:00
1 parent abc482c370
commit 203c96f6c9
16 files changed
+1550 -329

No files matched your search

+43
View File
@@ -0,0 +1,43 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { rechercherProduits } from "@/features/produits/api/search-produits";
// L'extraction Qlik (Playwright + Engine) prend plusieurs secondes, parfois
// plus d'une minute quand le serveur Qlik est chargé.
export const maxDuration = 300;
// Playwright/Chromium exige le runtime Node.js (pas edge).
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
/**
* GET /api/produits/search?q=<terme>[&force=1]
*
* Recherche produit **Qlik d'abord** : les articles du réseau qui correspondent
* au terme, leurs métriques réseau sur 12 mois glissants, puis le rapprochement
* avec le catalogue FF Nancy.
*
* `force=1` ignore le cache mémoire de 10 min (bouton « relancer »).
*
* Le middleware Next ne protège pas `/api/*` : contrôle de session explicite.
*/
export async function GET(req: NextRequest) {
const session = await auth();
if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const q = (req.nextUrl.searchParams.get("q") ?? "").trim();
if (q.length < 3) {
return NextResponse.json({ error: "Saisissez au moins 3 caractères" }, { status: 400 });
}
const force = req.nextUrl.searchParams.get("force") === "1";
try {
const result = await rechercherProduits(q, { force });
return NextResponse.json({ success: true, ...result });
} catch (e) {
const message = (e as Error).message ?? String(e);
console.error("[api/produits/search]", message.slice(0, 300));
return NextResponse.json({ error: message.slice(0, 300) }, { status: 500 });
}
}