Le Hit Parade, la recherche produit et les opportunités n'acceptaient que
artfou1.preference = 1 : un article dont la préférence n'est pas renseignée
dans FF (ou posée sur une ancienne fiche du même codein) s'affichait en
« Sans fournisseur ». On prend désormais le principal s'il existe, sinon le
fournisseur référencé le plus récent ; le Hit Parade cherche sur toutes les
fiches du codein.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WT49YnjumAVBjCB4EUXNL1
- /api/produits/search (POST démarre un job, GET suit l'avancement) : Qlik
d'abord, repli catalogue FF (source: "db"), cache mémoire 10 min des seuls
résultats Qlik exploitables, une recherche Qlik à la fois.
- /api/produits/opportunites : produits de la même famille, local vs réseau.
- Page produit-fiche (?codein= ou ?cc=) : getProduitFiche /
getProduitFicheByCodeCentrale, report du stock de fin de mois.
- normalizeMargePct (compare-reseau) et types JSON identiques au client.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LG2LLJuswrrTTPKJmunEVf
- product_rows.rs : getProductRows complet (7 requêtes FF en parallèle,
métriques réseau Qlik, report du dernier snapshot, filtre gamme Y,
rattrapage d'un magasin par l'API FF), branché sur rows_cache (calculs en
cours partagés, compteur d'enregistrements, compléments par magasin),
instantané grid_rows resservi s'il date du mois (rafraîchi au-delà de 20 h),
persistance non bloquante ; getStorePatch ; get_product_rows_for_sync pour la
synchro nocturne et le préchauffage.
- store_patch.rs : buildStorePatchEntries.
- enrich.rs (api-enrich) et api_gammes.rs (affecterGammes, lireCorpsJson).
- Routes /api/grid/rows (NDJSON), /api/grid/rows/store-patch, /api/v1/grid
(diffusion complète sans limit), /products/search, /products/{codein},
/products/{codein}/gamme, /gammes, /fournisseurs, /network/{codeCentrale}.
- store.rs : upsert_grid_rows_shared, écriture par lots depuis les lignes
partagées du cache (sans copie intégrale).
Écart volontaire : après un calcul à la demande, /api/v1 attend la fin de
l'écriture de l'instantané avant de le relire (le TS ne l'attendait pas et
pouvait répondre à tort « aucun article »).
Test d'intégration sur la base ff_fixture (GRID_FF_TEST_DATABASE_URL) : calcul,
cache, instantané, complément magasin via une fausse API FF, enrichissement et
toutes les routes (session et clé d'API).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LG2LLJuswrrTTPKJmunEVf
Portage de tout ce qui, dans la Grille, ne dépend ni de la base FF ni de Qlik :
- types.rs : ProductRow et types associés, JSON identique à JSON.stringify
(camelCase, champs facultatifs omis, entiers sans décimale, NaN → null) et
relecture sans perte des payloads grid_rows écrits par la version Node ;
- store.rs : grid_rows (upsert par lots de 200 + purge, instantané, report des
gammes, requêtes filtrées/triées/paginées via QueryBuilder et liste blanche
de tri, fiche, fournisseurs, fraîcheur, nomenclatures) ;
- rows_cache.rs : cache mémoire 10 min des lignes calculées (clé, LRU,
patchGridRowsCache, invalidation, calculs en cours, compléments par magasin) ;
- network_trend.rs, months.rs, store_patch.rs : logique pure testée ;
- gammes_a_valider.rs, enregistrer.rs, snapshots.rs : actions getSnapshots,
deleteSnapshot, saveSnapshot, saveDraftChanges, getGammesAValider,
rejeterGammesAValider (validation zod reproduite, mêmes messages) ;
- exports.rs : /api/export/excel et /api/export/modified-gammes
(rust_xlsxwriter, mêmes feuilles, styles, largeurs et formats) ;
- api_schemas.rs, zod.rs : schémas /api/v1, issues zod 4 à l'identique ;
- api_v1.rs, openapi.rs : /api/v1/nomenclatures et /api/v1/openapi.json
(document identique octet pour octet à celui de la version Node).
Écart volontaire : listGridNomenclatures groupe par position (GROUP BY 1) ; la
requête drizzle répétait left(code3, $n) avec un autre paramètre et échouait
systématiquement sous PostgreSQL.
Test d'intégration (grid_rows, snapshots, gammes à valider) activé par
GRID_TEST_DATABASE_URL.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LG2LLJuswrrTTPKJmunEVf
Le serveur étant réécrit en Rust, l'interface devient une application
React 19 côté navigateur, compilée par Vite dans dist/ et servie par le
serveur Rust (STATIC_DIR). Composants, styles et logique cliente
inchangés : seule la plomberie Next.js est remplacée.
- Outillage : Vite 8, @vitejs/plugin-react, Tailwind 4 via PostCSS,
React Router 7 (mode déclaratif), index.html, src/main.tsx, alias @/,
proxy /api → localhost:5643 en développement, ESLint sans plugin Next.
- Adaptations (src/shims) : next/link, next/navigation (useRouter avec
refresh, usePathname/useSearchParams suivant aussi les replaceState de
l'interface), next-auth/react (session lue sur /api/auth/session,
déconnexion par /api/auth/logout).
- Actions serveur → POST /api/actions/{nom} (src/lib/rpc.ts) : mêmes noms,
mêmes signatures, mêmes types. La connexion passe par /api/auth/login.
- Pages → table des routes (src/router.tsx) : données lues sur
/api/pages/{nom} avec Suspense (loading.tsx au premier chargement,
page conservée pendant une navigation ou un router.refresh()),
error.tsx en frontière d'erreur, not-found.tsx, titres d'onglet,
pages d'administration réservées aux administrateurs.
- Suppression du code serveur Next.js (routes /api, accès base, Qlik,
authentification, middleware…) ; types utiles à l'interface recopiés
dans src/types/ff.ts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LG2LLJuswrrTTPKJmunEVf
- ff::pg : portage complet de pg-ff-client.ts (mêmes requêtes SQL, fragments
dynamiques via QueryBuilder, lectures tolérantes, caches process de la
colonne parent de nomenclature et du lien mvtart → fournisseur).
- ff::api : client de l'API FF Nancy (URL résolue Paramètres → env → défaut,
mémorisée 30 s ; délai FF_API_TIMEOUT_MS), pagination, mensuel, référentiel,
commandes, normalisation du statut de synchro.
- ff::cache : getFournisseursCached / getDerniereReceptionCached.
- ff::diagnostic : diagnostiquer_api_ff (DNS, refus, délai, code HTTP).
- Routes /api/ff-status, /api/diag, /api/diag/stock-fournisseur, /api/test-db,
/api/commandes-auto ; actions de la Grille et du cadencier ; pages dashboard,
analytics, hit-parade, stock-negatif, commandes-auto, publicites.
- Test d'intégration (FF_TEST_DATABASE_URL) avec fausse API FF : toutes les
requêtes et tous les appels HTTP joués sur une base de test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LG2LLJuswrrTTPKJmunEVf
Le logiciel de mise à jour des gammes refuse les fichiers trop gros
(ex. 28 590 lignes). L'export est désormais découpé en plusieurs
fichiers (_partie_1_sur_6, …) à intégrer un par un.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011jxxh5X1FHMUPFu2S2JpyT
Sur un écran très large, la colonne de libellé absorbait tout l'espace en
trop : le nom du fournisseur se retrouvait à un millier de pixels de ses
chiffres. Le tableau prend désormais la largeur de son contenu (défilement
au-delà de l'écran) et l'option « grow » devient « label » : largeur
naturelle, retour à la ligne au-delà de 28rem.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FaniLFvegAZNXFrFFSStqa
DataTable gagne quatre options de colonne :
- grow : la désignation prend la largeur restante, les chiffres restent
groupés au lieu de s'étirer sur un écran large ;
- sticky : Code et Désignation restent visibles au défilement horizontal
(désignation bornée à 45 % de la largeur sur mobile) ;
- group : en-tête commun par magasin et trait de séparation entre groupes ;
- highlight : fond teinté pour le groupe « Total nos magasins ».
Toutes les lignes alternent désormais de couleur, survol teinté.
Appliqué à Meilleures ventes (Référence déplacée après la Désignation),
Ventes par mois et Publicités.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FaniLFvegAZNXFrFFSStqa
- API /api/v1 : PUT /products/:codein/gamme et POST /gammes déposent une
proposition (table gammes_a_valider) au lieu d'enregistrer. La Grille la
charge comme modification non enregistrée (badge « API ») : Enregistrer
la valide, Annuler la rejette. Lecture : champ gammeAValider, statut
a_valider dans la réponse ; OpenAPI mise à jour.
- Meilleures ventes, Ventes par mois, Publicités : plus de largeur max
centrée, les pages occupent toute la largeur.
- Meilleures ventes : colonne Référence (référence fournisseur), recherche
et export Excel compris.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FaniLFvegAZNXFrFFSStqa
Nouveau bouton « Nomenclatures » dans la barre d'outils de la Grille : il
ouvre une modale qui regroupe les produits affichés par nomenclature (code3)
et donne, sur les 12 mois de la Grille, le CA de chaque magasin, le CA de nos
magasins, le CA réseau, le poids de la nomenclature chez nous et dans le
réseau (écart en points) et l'indice de chaque magasin face au magasin moyen
du réseau. Tri par colonne, filtre texte, ligne de total et export Excel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KvNrtCGyjYgPHFoiUkJzhM
Deux endpoints d'écriture dans /api/v1 :
- PUT /products/{codein}/gamme { gamme, fournisseur? } pour un article ;
- POST /gammes { fournisseur, changes[] } pour plusieurs articles d'un
fournisseur, en tout ou rien.
Ils passent par le même enregistrement que la Grille (extrait de
saveDraftChanges dans enregistrerGammes) : snapshot du fournisseur, cache
de la Grille et instantané grid_rows. Gamme validée contre la liste des
gammes (A, B, C, Y, Z) ; un article déjà dans la gamme demandée n'est pas
réécrit. Spec OpenAPI et page de connexion à l'API mises à jour ; la
légende des gammes de la spec, qui citait une gamme D inexistante, suit
désormais lib/gammes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VEXT61tGbsXHUDNBeghCXQ
Les CA, marges et valeurs de stock étaient arrondis à l'euro (fmtEur0,
fmtEntier suivi de « € », Math.round dans les exports). Tous les montants
sont désormais affichés avec deux décimales : accueil, grille, fiche
produit, hit-parade, publicités, analytics, snapshots, export PDF et
export Excel des manques d'assortiment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VEXT61tGbsXHUDNBeghCXQ
Fiabilité des données
- Contrôle de plausibilité avant d'écrire le cache (lib/qlik-validation.ts) :
extraction refusée, cache inchangé, si le dernier mois pèse moins de 40 %
des 3 précédents dans les 10 premiers jours du mois (mois pas encore
chargé dans Qlik), ou si plus de la moitié des séries vendues ont la même
valeur 12 mois (mesure insensible au mois).
- Valeurs impossibles corrigées (NaN, infini, nombre de magasins négatif).
Récupération
- Chromium relancé s'il est tombé : un navigateur déconnecté faisait échouer
toutes les extractions jusqu'au redémarrage du serveur.
- Une seule session Qlik à la fois (extraction de nuit, synchro manuelle,
recherche produit) : en parallèle, le serveur Qlik abandonnait les deux.
- Planificateur : un échec est retenté la nuit suivante (au lieu de 7 jours)
et les données d'avant le mois en cours sont rafraîchies sans attendre le
délai de fraîcheur. Page Synchronisation mise à jour.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015MouZgPPAuEZjHXXifW7bm
- Phrase de lecture en tête : la tendance du réseau avec ses deux chiffres
(qté par magasin, 4 derniers mois contre 4 premiers), puis notre
situation (ventes 12 mois, stock, couverture, commandes en cours).
- Nouvelle section « Nos magasins » : stock fin de mois, ventes 12 mois,
rythme mensuel, couverture, dernière entrée et écart au magasin moyen
du réseau, par magasin et pour l'ensemble.
- Réseau : période affichée en clair, tuile CA réseau 12 mois, part du
réseau dans la tuile des magasins vendeurs.
- Modal sorti de heatmap-grid.tsx (network-monthly-modal.tsx).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015MouZgPPAuEZjHXXifW7bm
- Grille servie depuis l'instantané (lot 4) : les colonnes réseau sont
relues dans le cache Qlik, comme le calcul en direct. Elles restaient
figées à la date du calcul de l'instantané.
- Tendance : si la fenêtre du jour est incomplète mais que les 12 mois
précédents le sont (cache Qlik pas encore resynchronisé depuis le
changement de mois), la tendance porte sur ces 12 mois au lieu de
disparaître. Le modal le signale.
- Courbe des magasins vendeurs alignée sur les mois de la tendance
(modal Grille et fiche produit).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015MouZgPPAuEZjHXXifW7bm
scripts/diagnostic-ff.js (lecture seule) relève les réglages PostgreSQL, la
taille des tables, les types des colonnes de jointure, les index, les codes
article avec espaces et les plans d'exécution de six requêtes représentatives
(EXPLAIN, ou EXPLAIN ANALYZE avec --analyze). Il prépare les réécritures SQL
qui demandent la vraie base. Mode d'emploi dans docs/plan-optimisation-webui.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcA129WzGTH6zxPHFBGWmn
- Grille : réglages écrits dans le localStorage au plus toutes les 500 ms
(immédiatement à la fermeture), tendance réseau et texte de recherche
calculés une fois par ligne, courbe de tendance mémorisée, barre du bas
abonnée au seul nombre de modifications.
- Commandes : appels « franco » 6 par 6 ; dernière réception limitée aux
fournisseurs du cadencier (résultats identiques sur la base de test).
- Dépendances inutilisées retirées (react-query, @google/genai, ai,
react-markdown, remark-gfm, xlsx, @radix-ui/react-dropdown-menu) ;
pnpm-lock.yaml supprimé (npm est utilisé par Docker et le README).
- Code mort et journaux de build retirés.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcA129WzGTH6zxPHFBGWmn
Grille
- Corrige le lot 4 : au changement de magasin, les chiffres et le tri
affichés suivent tout de suite (nouvelle liste), la sélection est gardée.
- Lignes toujours chargées « Nos 2 magasins » ; changement de magasin sans
aller-retour serveur (URL mise à jour par l'historique du navigateur).
- Complément de l'API FF par magasin servi à part (/api/grid/rows/store-patch),
appliqué à son arrivée, calcul identique à l'ancien (lib/store-patch.ts).
- La recherche ne reconstruit plus toutes les lignes à chaque frappe ; la
sélection n'est remise à zéro qu'au changement de fournisseur.
Sécurité
- lib/authz.ts : session relue en base (cache 1 min), rôle de la base
prioritaire ; variantes pour actions, routes et pages.
- Paramètres réservés aux administrateurs ; session vérifiée dans les actions
Grille, Commandes, Historique et les routes ; diagnostics réservés admin.
- Mots de passe et clés d'IA plus envoyés au navigateur ; champ vide = mot de
passe conservé ; plus de mot de passe dans le localStorage ; l'enregistrement
de la base n'efface plus les clés d'IA.
- admin/admin créé seulement si aucun utilisateur ; fichier de secours utilisé
uniquement si la base est injoignable ; data/ ignoré par git.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcA129WzGTH6zxPHFBGWmn
- Grille : lecture de l'instantané grid_rows (gamme INIT et dernières gammes
enregistrées reportées), recalcul en arrière-plan au-delà de 20 h, version de
format dans chaque ligne pour écarter les anciens instantanés.
- Navigateur : lignes réutilisées au retour sur la Grille (< 10 min), Grille
conservée pendant un changement de magasin ou une actualisation, React.memo
sur la Grille, les filtres et la barre du bas, en-tête mémorisé.
- Fiche produit : toutes les lectures en parallèle.
- Ventes par mois et stock sans vente : plages de dates au lieu de TO_CHAR.
- Accueil : vagues de 12 appels, cache de 30 min.
- API v1 : comptage et page en parallèle, last_used_at au plus une fois par
minute, version interne non exposée.
- Plan : suivi du lot 4, avec ce qui reste à valider sur la base réelle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcA129WzGTH6zxPHFBGWmn
Chaque page reprend le socle du lot 2 : en-tête avec une phrase
d'explication, couleurs du thème (plus de pages figées en blanc), textes
de 12 px minimum, noms de magasins, infobulles sur le jargon, retours par
toast / confirmation, états vides et d'erreur expliqués.
- Accueil : CA, tickets et panier moyen d'hier vs N-1, par magasin, top 10
avec lien vers la fiche produit.
- Révision d'assortiment : un seul endroit pour enregistrer et exporter
(menu Export unique, mêmes droits pour tous), annulation confirmée, sens
des gammes affiché partout, magasin choisi en un clic, colonnes nommées
en clair, bandeau de progression au lieu de la fenêtre floue, droits lus
côté serveur, ligne sélectionnée opaque sur les colonnes figées.
- Recherche produit, Stocks à surveiller (export FF inchangé), Meilleures
ventes, Ventes par mois (dernier mois complet par défaut), Publicités,
Commandes fournisseurs, Historique, Paramètres (onglets), Synchronisation
et Connexion migrés sur DataTable / Tabs / Card.
- Le nom de l'utilisateur est transmis à la session (affiché dans
l'en-tête).
- Composants devenus inutiles supprimés (anciennes fenêtres modales,
export-dropdown, financial-cell, supplier-selection).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcA129WzGTH6zxPHFBGWmn
Thème
- Clair par défaut ; les classes dark: suivent le bouton de thème (et non
plus le réglage du système) ; animations tw-animate-css enfin importées.
- Couleurs shadcn branchées sur les variables (survols des menus visibles),
libellés gris au contraste AA dans les deux thèmes, fond plein pour le
menu et l'en-tête.
Composants partagés (src/components/ui)
- PageHeader, Card/Field, StatCard, Badge/DeltaBadge/StoreBadge/GammeBadge,
Tabs/Segmented/useUrlTab, Select/SearchInput, EmptyState/ErrorState/
Skeleton, Tooltip/Terme (glossaire), toast et confirmer, Pagination,
DataTable (recherche, filtres, tri, pagination, export, totaux).
- Sources uniques : magasins, gammes (avec leur sens), glossaire, seuils de
marge, export Excel.
Navigation
- Menu groupé (Au quotidien / Analyses / Suivi / Administration), déplié
par défaut, libellés en français.
- En-tête : nom de la page, thème, menu du compte avec le vrai nom, la
hauteur des lignes de la Grille et la déconnexion. La recherche globale,
qui ne filtrait que la Grille, est retirée.
- Page Historique (sessions + exports) ; /snapshots et /exports y
redirigent.
- Squelettes de chargement au lieu de la fenêtre plein écran, pages
d'erreur et « introuvable » en français, arrivée sur l'Accueil après
connexion.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcA129WzGTH6zxPHFBGWmn