- 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 (navigateur)
- « Rafraîchir » ne passe plus par l'URL : le paramètre _refresh restait
mémorisé et chaque retour par le menu forçait un recalcul serveur complet
(migration du store pour nettoyer les valeurs déjà enregistrées).
- Lignes reçues regroupées toutes les 300 ms au lieu de reconstruire tout le
tableau à chaque paquet de 150 (chargement quadratique).
- Sélection indexée par code article (getRowId) : après un filtre, l'action
groupée visait d'autres produits. Seules les lignes affichées comptent.
- Recherche : un passage par ligne au lieu d'un par colonne ; formateurs de
nombres partagés (lib/format.ts) ; colonnes mensuelles mémorisées ;
plus de transition-all sur les lignes positionnées par transform.
- exceljs, jspdf et xlsx chargés au clic seulement.
Grille (serveur)
- Requêtes Qlik et snapshot lancées en parallèle de la phase SQL.
- Cache mémoire borné (LRU) ; calcul en cours réutilisé même en forcé ;
flux interrompu quand le client part.
- Après un enregistrement, les gammes sont reportées dans le cache et dans
grid_rows au lieu d'invalider (recalcul de ~40 s évité).
Données et API
- lib/ff-cache.ts : cache mémoire 30 min des lectures FF (stock, hit-parade,
CA mensuel, fournisseurs, dernière réception, publicités), vidé en fin de
synchro nocturne ; erreurs jamais gardées.
- Pool PostgreSQL unique (globalThis), réglage sans workers parallèles posé
une fois par connexion au lieu d'une transaction autour de chaque requête.
- Délai maximal sur tous les appels à l'API FF.
- Index session_snapshots ; commandes : plus de double rechargement ;
synchro : saisies regroupées ; paramètres : configuration lue une fois ;
journal de capture en ajout seul, rien de formaté sans capture ouverte.
Corrections
- Historique : l'échec de chargement s'affiche (au lieu de « Aucun snapshot »).
- Sessions et validations enregistrent le magasin affiché, plus « TOTAL ».
- Publicités : libellé du statut « passées ».
Plan complet : docs/plan-optimisation-webui.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcA129WzGTH6zxPHFBGWmn
Deux problèmes distincts, dont un mal diagnostiqué au départ.
1. FORMAT DE RÉPONSE — cause réelle du « test ne donne rien ». Le serveur
fonctionne (vérifié : HTTP 200, synchro du jour, 33 tables), mais il renvoie
{ sync: [{ table_name, last_sync, rows_synced, status, error_msg }] } alors
que l'application lisait { lastSync, tables: [{ nom, derniereSync, nbLignes }] }.
Les champs n'existaient pas : le panneau s'affichait vide sans erreur, la
requête HTTP ayant réussi. normalizeSyncStatus() traduit désormais les deux
formes, et le tableau expose l'état par table (une table en erreur est
justement l'information qu'on vient chercher).
2. URL NON CONFIGURABLE. FF_API_BASE était figée au chargement du module depuis
process.env, sans champ dans l'interface : impossible de corriger l'adresse
sans redéployer. Elle est maintenant résolue à l'appel — réglage enregistré,
puis variable d'environnement, puis défaut — avec un champ, un bouton
Enregistrer et un test dans Paramètres.
Le test distingue désormais DNS, connexion refusée, délai dépassé et code HTTP,
et affiche l'adresse réellement appelée : un « HTTP 503 » nu ne permettait pas
de séparer une mauvaise URL d'une panne. /api/ff-status renvoie le même détail.
Ajout au passage du paramètre `compute` dans la doc API des Paramètres.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y26nRZxTR57K7h8yqsF675
The ranking bulk endpoint is capped at 500 by the API server, making
it useless for full supplier matching. Per-article ranking (codein filter)
works correctly and is now called only for articles WITH sales data.
getProductRows restructured:
- Phase 2A: mensuel + commandes in parallel (all articles)
- Phase 2B: referentiel + ranking in parallel, only for articlesWithData
(articles that appear in mensuelMap)
For a 45060-article supplier with 5446 with sales:
- Referentiel: 45060 → 5446 calls (88% reduction)
- Ranking: 45060 → 5446 calls (88% reduction)
totalRankedProducts fetched from GET /api/ranking?limit=500 count.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The bulk ranking response (GET /api/ranking?limit=30000) may use
'gencod' instead of 'codein' as the article identifier. Added:
- gtin→codein lookup map built from article gtin fields
- Match entries by entry.codein first, then by entry.gencod→article.gtin
- Diagnostic logging of the first entry's keys and values to identify
the correct identifier field in the API response
- Increased fallback per-article batchSize from 20 to 50
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove actif/suspendu filter that caused wrong stats (893€ CA, 249 units)
by incorrectly excluding products with sales. The parallel pagination
and bulk ranking fixes are sufficient to handle large suppliers.
- Increase getMensuelByArticles and getReferentielByArticles batchSize
from 20 to 50, reducing sequential batch iterations by ~2.5x.
- Filter gamme Y products with zero 12-month sales from returned rows.
Reduces grid size (e.g. S053: 14775→fewer rows) without affecting
products that actually sold. Y products with sales are preserved.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Article pagination:
- fetchAllPages fallback now fetches 10 pages in parallel instead of
sequentially. Stops as soon as any page returns < pageSize items.
For S053 (30 pages), reduces sequential time by ~10x.
Ranking:
- getRankingByArticles now fetches the entire network ranking in one
call (GET /api/ranking?limit=30000) and matches by codein.
Replaces foucentrale bulk (which returned 0) and per-article fallback
(which would be 8889 calls for S053).
- totalRankedProducts = actual number of entries returned by the API.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
getRankingByArticles now uses GET /api/ranking?foucentrale=<code>
to fetch all rankings for a supplier in a single request instead of
making one request per article. For a 500-article supplier this goes
from ~500 HTTP calls to 2 (count + bulk), eliminating the timeout
that caused the client-side crash on large suppliers.
Falls back to per-article requests if no codeFournisseur is provided
or if the bulk endpoint fails.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
api-ff-client.ts:
- FfArticle: add reference (ref_fou_principale) and gtin fields
- getArticlesByFournisseur: map ref_fou_principale → reference, gtin → gtin
get-product-rows.ts:
- ProductRow seeded with reference and gtin from API articles
- Phase 6: read codeGamme, codeGammeInit, code3, libelle3 from ventesProduits
(read-only, DISTINCT ON codein ordered by updated_at DESC)
- Phase 7 snapshot still overrides DB gammes when a snapshot exists
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces bulk mouvements pagination + per-article stock N+1 with a single
batched call to the new mensuel endpoint (confirmed working via diag).
api-ff-client.ts:
- Remove getStockByFournisseur (no longer needed)
- Add FfMensuelEntry/Ventes/Receptions interfaces
- Add getMensuelByArticles(articles, dateDebut, dateFin, batchSize=20)
→ Map<codein, FfMensuelEntry[]> using no_id as API identifier
get-product-rows.ts:
- Phase 2: getMensuelByArticles + getCommandesByFournisseur in parallel
- Phase 5: aggregate mois×site entries → sales12m, stock12m, workingStores
- qte_vendue / ca_ht are negative → Math.abs()
- stock carry-forward for months with no data
- pa from prmp_fin_mois
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- extractList now explicitly checks "mouvements", "articles", "fournisseurs",
"ventes", "stocks", "commandes" wrapper keys before generic array fallback
- Diag endpoint auto-fetches first real codefou from fournisseurs instead
of hardcoded "AUXENCE" (which was supplier nom, not actual codefou)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>