Commit Graph
197 Commits
Author SHA1 Message Date
Claude abc704a363 Carte tendance : deuxieme courbe du nombre de magasins vendeurs
Une quantite qui monte parce que le produit est diffuse dans plus de
magasins ne raconte pas la meme histoire qu'une quantite qui monte a
diffusion constante. La courbe des quantites seule ne permettait pas de
distinguer un succes produit d'un simple elargissement.

- computeStoresSeries() : serie « magasins vendeurs » sur la MEME fenetre
  12 mois que la tendance, avec la meme regle du tout ou rien — les 12
  cles doivent etre presentes, sinon null. Un mois non extrait ne doit
  pas se lire comme « zero magasin », ce qui simulerait un arret de
  diffusion.
- nbMagReseauByMonth remonte du cache (metricsByMonth, deja stocke par la
  sync) jusqu'a la ligne de grille.
- NetworkLineChart accepte une serie `stores` optionnelle.

DEUX ECHELLES INDEPENDANTES : les quantites se comptent en milliers, les
magasins plafonnent a ~270. Sur une echelle commune la courbe des
magasins serait ecrasee sur l'axe et ne montrerait rien. Chaque serie est
normalisee sur son propre maximum, rappele dans la legende — c'est la
FORME des deux courbes qu'on compare, pas leurs hauteurs. La courbe des
magasins est pointillee et indigo, pour ne jamais entrer en collision
avec le vert/rouge/gris de la tendance.

Branchee sur la carte de la Grille et sur celle de la fiche produit. La
sparkline de la colonne reste a une seule courbe : 46x18 px, deux traces
y seraient illisibles.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
2026-08-01 09:07:39 +00:00
Claude 5f3760f03e merge: piloter Période + garde-fou d'intégrité + sélection par dimension Mois
Fusion de deux lignes de travail parallèles sur main, complémentaires et non
concurrentes :

  cd2e77e  selectionnerFenetreViaDimensionMois() — sélectionne les 12 mois par
           qElemNumber sur la dimension maître Mois. C'est ce qui marche : les
           champs Date exposés sont dissociés des faits (100 % du total avant
           comme après), la dimension maître porte les vraies valeurs.
  4ef0e9f  garde-fou d'intégrité — refuse d'écrire le cache si la fenêtre n'est
           pas entièrement couverte. C'est pourquoi le cache est resté inchangé
           au lieu d'être corrompu.
  726fb84  choisirPeriode() — le maillon manquant.

Le dump des expressions donne la cause de fond :

  « Quantité N »    = Sum({<Type_Cal={'N'}>} quantite)
  « Quantité COMP » = Sum({<Type_Cal={'$(vPeriod_comp)'}…>} quantite)

Un pont de périodes duplique les lignes de faits ; Type_Cal marque la période
analysée (N) ou de comparaison (COMP), et le champ Période (4 valeurs) décide
de quelle période il s'agit. Sélectionner correctement les mois ne suffit donc
pas : sous la période par défaut, « Quantité N » rend 0 sur tout mois hors
période courante, même parfaitement sélectionné.

Orchestration fusionnée :
  1. choisirPeriode() essaie chaque valeur de Période et MESURE, sur un cube
     [Mois] × Quantité N, combien de mois de la fenêtre elle rend disponibles.
     Aucun libellé n'est deviné.
  2. Si une période couvre les 12 mois → master measures (exactes sur toute la
     fenêtre). Sinon → agrégation directe des faits, en secours.
  3. Dans les deux cas, la couverture des 12 mois est revérifiée avant
     persistance ; sinon l'extraction est refusée et le cache laissé intact.

La tendance conserve la règle stricte de 4ef0e9f (les 12 clés explicitement
présentes, ou pas de tendance), qui écarte aussi le « +4100 % » calculé sur deux
points pour un article vendu depuis mai.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
2026-07-31 11:27:51 +00:00
Claude 726fb8418d fix(qlik): piloter le champ Période du modèle au lieu de lutter contre lui
Le dump des expressions donne enfin la cause de fond :

  « Quantité N »    = Sum({<Type_Cal={'N'}>} quantite)
  « Quantité COMP » = Sum({<Type_Cal={'$(vPeriod_comp)'}…>} quantite)
  « Magasin Ventes Nb N » = Count({<Type_Cal={'N'}>} Distinct ventes_code_site)

L'app n'est pas un modèle « faits + calendrier ». Un pont de périodes duplique
les lignes de faits : Type_Cal='N' marque celles de la période analysée, 'COMP'
celles de la période de comparaison, et le champ Période (4 valeurs, une
sélectionnée) décide de quelle période il s'agit. Tout en découle :

  - sélectionner Date ne change rien (mesuré : 100 % du total avant comme après),
    le périmètre venant du pont de périodes et non du champ date ;
  - « Quantité N » ne couvre que la période courante, d'où août→décembre vides ;
  - « Quantité COMP » ne couvre que les mois ayant un comparable, d'où
    janvier→juillet de l'année précédente ;
  - Sum(quantite) brut additionne les lignes N ET COMP : il double compte, il ne
    pouvait pas servir de substitut.

choisirPeriode() essaie donc chaque valeur de Période, mesure sur un cube
[Mois] × Quantité N combien de mois de la fenêtre elle rend réellement
disponibles, et garde la meilleure. Aucun libellé n'est deviné : c'est la
couverture mesurée qui décide, et elle est tracée. Si aucune valeur ne couvre
les 12 mois, les mois manquants restent absents du cache.

Aussi :
- Les mois réellement couverts reçoivent un zéro EXPLICITE quand l'article n'a
  pas vendu ; la tendance ne retient que les mois présents comme clés. Un article
  vendu depuis mai voyait sa tendance calculée sur deux points (« +4100 % »).
- Les diagnostics du chemin d'extraction sont répétés en fin de sync
  ([qlik-pw][diag]) : ils étaient au début d'un log de plusieurs milliers de
  lignes.
- QLIK_USE_EXPR passe à désactivé par défaut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
2026-07-31 11:22:56 +00:00
Michael 4ef0e9fdeb fix Qlik rolling 12-month integrity 2026-07-31 10:28:29 +02:00
Claude b2149ad0a6 fix(ui): message clair quand l'onglet est périmé après un déploiement
« Server Action "…" was not found on the server » n'est pas une panne du
snapshot : Next.js identifie chaque Server Action par un hash calculé à la
compilation, et un onglet ouvert avant un nouveau build continue d'envoyer les
anciens identifiants. La seule issue est de recharger la page.

- src/lib/stale-action.ts : détection de cette erreur + message utilisateur.
- Grille et Snapshots : au lieu d'afficher l'erreur technique brute, on propose
  « Recharger la page ». Les brouillons de la Grille sont persistés
  (localStorage), recharger ne perd rien.
- SuccessModal accepte `variant="error"` : icône d'alerte rouge au lieu de la
  coche verte, et surtout plus de fermeture automatique au bout de 3 s — un
  message d'échec doit rester lisible. Les erreurs s'affichaient jusqu'ici sous
  une coche de succès.
- SuccessModal accepte une action secondaire optionnelle, et joue son animation
  d'entrée en CSS au lieu d'un setState synchrone dans un effet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
2026-07-29 12:40:16 +00:00
Claude 203c96f6c9 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
2026-07-29 05:04:34 +00:00
Claude eeaae09282 feat(produits): page de recherche produit + fiche 360° local/réseau
Nouvelle page /produits : recherche par libellé ou par code centrale, puis
fiche complète d'un produit — identité, prix, stock par magasin, ventes sur
12 mois glissants, et l'intégralité des métriques réseau Qlik.

Jusqu'ici les données produit n'étaient accessibles que par fournisseur
(la Grille) ou par classement (Hit Parade) : aucune route n'acceptait un
codein. Les données réseau Qlik n'étaient visibles que dans six colonnes
de la grille d'arbitrage.

Deux analyses ajoutées :
- comparatif local vs réseau, ramené au magasin moyen (indice de
  performance sur les quantités, seule base commune fiable — le CA local
  est TTC et la base du CA Qlik n'est pas documentée, donc indicatif) ;
- opportunités de la même famille : références que le réseau vend bien et
  que nous vendons peu, écart calculé par magasin.

Détail mensuel réseau enrichi : le cube Qlik renvoyait déjà CA, nb de
magasins, CA/magasin et marge % par (article, mois) mais seule la quantité
était conservée. Ces mesures sont désormais stockées dans une nouvelle
colonne metrics_by_month, sans requête Qlik supplémentaire. qte_by_month
reste inchangée — la colonne « Tendance / Réseau » de la Grille en dépend.

L'extraction Qlik peut désormais cibler un seul code centrale
(/api/qlik/sync?codeCentrale=…), ouverte à tout utilisateur connecté et
plafonnée à 3 extractions simultanées ; le mode fournisseur reste
réservé aux admins.

Refactor sans changement de comportement : les helpers de mois, le calcul
de tendance et les graphes SVG quittent heatmap-grid.tsx pour des modules
partagés, et le polling des jobs Qlik devient le hook useQlikSyncJob,
réutilisé par la Grille et par la fiche.

Les calculs de ventes reprennent le SQL canonique de la Grille (genremvt=3
avec quantités niées pour déduire les retours, stock de fin de mois pris
sur le dernier mouvement, report sur les mois sans mouvement), afin que
les chiffres des deux pages soient identiques par construction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XR8cMbsdjXWChX8seL9NCP
2026-07-27 14:26:08 +00:00
Claude 99097b6eca fix(qlik): dérive les mois N-1 depuis Quantité COMP (vrais 12 mois)
Le cube [Article Code, Mois] ne renvoie que les mois de l'année N (les
mesures 'N' sont nulles ailleurs → lignes supprimées). Le code cherchait
des lignes d'année N-1 qui n'existent pas, donc 'Quantité COMP' n'était
jamais exploitée → seulement ~7 mois affichés.

'Quantité COMP' étant le comparable N-1, à la ligne Mois=YYYYMM elle donne
la quantité du MÊME mois l'année précédente. On dérive donc deux points
par ligne : YYYY-MM (Quantité N) et (YYYY-1)-MM (Quantité COMP) → 12 mois
sans ligne supplémentaire ni requête en plus.

UI : l'en-tête du modal affiche le nombre RÉEL de mois disponibles au lieu
de '12 derniers mois' en dur.
2026-07-25 21:31:15 +00:00
Claude 2af7c42b2b fix(grille): tooltip tendance (régression 12 mois) + lots Qlik plus petits (OOM)
- Corrige le tooltip erroné 'X% (4 derniers mois vs 4 précédents)' →
  'Tendance X% sur 12 mois (régression)'. Le calcul est déjà une
  régression linéaire sur 12 mois ; seul le texte était resté l'ancien.
- Réduit la taille des lots du cube [Article Code, Mois] (800→300, replis
  150/75) pour alléger la mémoire moteur Qlik et limiter les 'Out of
  memory / File corrupted' (3002) observés sur le sync.
2026-07-24 07:41:41 +00:00
Claude 5b5e69eed1 feat(grille): modal en courbe unique + retrait Quantité COMP (2025)
- Modal : les 12 barres remplacées par UNE seule courbe SVG des ventes
  réseau mensuelles (points + quantités affichées + axe des mois).
- Sync : retrait de la mesure 'Quantité COMP' (année N-1) — trop lent à
  récupérer. Retour au cube [Article Code, Mois] à 5 mesures (Quantité N).
  Le détail mensuel couvre donc les mois de l'année en cours ; la tendance
  (régression) se calcule sur ces mois.
2026-07-23 20:40:24 +00:00
Claude 6d349e33b4 feat(grille): modal sur colonne Tendance + tendance par régression 12 mois + Quantité COMP
UI:
- La colonne Tendance (renommée 'Réseau · 12 m') ouvre le modal au clic
  (au lieu de la colonne Nb magasins, remise en simple affichage).
- Tendance calculée par RÉGRESSION LINÉAIRE (moindres carrés) sur les 12
  derniers mois : robuste au bruit, % = pente×durée/moyenne. Libellé
  Forte hausse/Hausse/Stable/Baisse/Forte baisse (seuils ±8% / ±25%).

Sync:
- Ajout de la mesure 'Quantité COMP' (année N-1) au cube [Article Code,
  Mois] → 12 mois glissants : mois de l'année N via Quantité N, mois de
  l'année N-1 via Quantité COMP. Cube 8 colonnes, PAGEM 1200 (<10000
  cellules). Total réseau reste en année N (non gonflé).
  Configurable : QLIK_MEAS_QTE_COMP_ID.
2026-07-23 19:53:20 +00:00
Claude 71563500a0 feat(grille): tendance réseau (sparkline) + modal ventes réseau par mois
Le sync Qlik calculait déjà les ventes réseau mois par mois mais les
sommait en un total. On conserve désormais le détail mensuel.

- Sync : accumulation additive qteByMonth { code: { 'YYYY-MM': qté } }
  in-page (chemins rapide + par lots), rattachée au métrique réseau.
  Reset propre en cas de repli code 15. Total inchangé.
- Stockage : colonne jsonb qte_by_month sur qlik_network_metrics
  (schema + db-init ALTER), upsert + lecture.
- ProductRow.qteReseauByMonth propagé jusqu'à la grille.
- UI grille :
  · colonne '% Présence réseau' remplacée par 'Tendance réseau' :
    sparkline 12 mois teintée + flèche + variation % (moyenne 4 derniers
    mois vs 4 précédents), triable ;
  · cellule 'Nb magasins' rendue cliquable → modal des ventes réseau
    mois par mois (barres, 12 derniers mois).

Nécessite un nouveau Sync Qlik pour peupler le détail mensuel (les
données déjà en cache n'ont que le total → modal 'pas encore de détail').
2026-07-22 14:17:57 +00:00
Claude 2e25bbc5b3 feat(grille): bouton "Rafraîchir" pour forcer le rechargement serveur
Ajoute un bouton dans l'en-tête de la Grille qui force le rechargement
des données depuis le serveur en ignorant le cache mémoire de 10 min
(via le paramètre URL _refresh → refresh=1 → forceRefresh). Utile pour
remonter immédiatement l'état serveur courant, notamment la colonne INIT.
Spinner pendant le chargement, désactivé si un chargement est en cours.
2026-07-21 21:11:28 +00:00
Claude f0fdab465c fix(grille): colonne INIT rechargée à chaque chargement (même sur cache)
La colonne Gamme conserve la valeur du snapshot (comportement voulu). Mais
la colonne INIT ne se mettait pas à jour : getProductRows sert les lignes
depuis un cache mémoire de 10 min, et le client n'envoie 'refresh=1' que
sur le bouton de rafraîchissement manuel. Sur une navigation normale dans
les 10 min, codeGammeInit restait donc figé.

Correctif : sur un hit de cache, on re-requête la gamme serveur courante
(pgGetGammesByFournisseur, 1 requête légère) et on met à jour uniquement
codeGammeInit sur les lignes cachées. codeGamme (snapshot) n'est pas
touché ; les données lourdes (ventes/stock/réseau) restent cachées.

Résultat : la colonne INIT reflète toujours l'état serveur à chaque
chargement, et le marqueur 'modifié' (Gamme ≠ INIT) se résorbe dès que
le serveur rattrape la modification. La révision précédente (reconcilier
codeGamme) est annulée : la colonne Gamme reste le snapshot tel quel.
2026-07-21 15:05:02 +00:00
Claude e9ac5bc0a5 fix(grille): la grille reflète l'état serveur courant des gammes (INIT rechargé)
La grille rechargeait bien le gamme INIT depuis l'état live du serveur à
chaque chargement (Phase 6), mais la Phase 9 réappliquait ensuite
AVEUGLÉMENT le 'after' du dernier snapshot sur codeGamme — même quand la
modification avait déjà été appliquée sur le serveur (ou que la gamme
serveur avait évolué depuis). Résultat : la grille affichait l'ancienne
cible du snapshot au lieu de l'état serveur réel, et une modif 'en
attente' fantôme.

Désormais on ne réapplique une modif du snapshot QUE si elle est encore
réellement en attente côté serveur (change.before === gamme live). Sinon
on garde l'état serveur courant. La grille montre donc toujours où en
sont les gammes sur le serveur, et une modif disparaît du 'en attente'
dès qu'elle est appliquée côté serveur — le suivi reste juste.

Note : les brouillons locaux (localStorage) restent gérés côté client
comme du travail non sauvegardé.
2026-07-21 15:00:40 +00:00
Claude b7de347a99 fix(grid,hit-parade): convention de signe nette + lint React Compiler
- Grille (fallback API mensuel) : Math.abs remplacé par la négation sur
  qte_vendue/ca_ht (négatifs côté API) — les retours clients restent
  déduits au lieu d'être inversés en positif, même convention que le SQL.
- Hit Parade (client) : ColHeader extrait hors du composant (erreur
  'Cannot create components during render'), useMemo déplacés avant
  exportToExcel pour préserver la mémoïsation React Compiler, variable
  idx inutilisée supprimée. ESLint 0 erreur, build OK.
2026-07-06 20:28:03 +00:00
R0m1k3 32f9a27563 Fix Qlik network column sorting 2026-06-22 12:12:48 +00:00
R0m1k3 850852912f fix(qlik): track async sync job status 2026-06-21 19:08:04 +00:00
MichaelandClaude Opus 4.8 de8a435c14 fix(qlik): batch SelectValues to avoid Engine abort + drop couv stock metric
Large supplier (4816 codes) triggered Qlik Engine "Request aborted"
(code 15): a single SelectValues over all codes forced the hypercube to
calculate too many rows at once. Now select in 1200-code batches and
accumulate, keeping each calc small. 1417 codes worked before, 4816 did
not - chunking scales to any supplier.

Also remove the "Couverture de stock" network column end-to-end (grid,
types, Qlik clients, cache, schema, db-init), shifting the marge measure
index from 6 to 5 and the hypercube width from 7 to 6.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 10:55:03 +02:00
MichaelandClaude Opus 4.8 8cc95cb387 fix(grid): align Sync Qlik button with summary-bar actions
The button was a two-line flex-col (button + date below), making it
taller than the single-line Export button next to it, so its button sat
visibly higher in the items-center row. Lay it out inline instead: the
"MAJ Qlik" date sits to the left (hidden under lg, kept in the tooltip),
the button keeps the 36px btn-action height and now aligns with siblings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 09:41:28 +02:00
MichaelandClaude Opus 4.8 5f364f7d52 feat(grid): refine grid page chrome (refined dense-pro, theme kept)
Redesign the grid page shell while preserving the Slate/teal Apple theme:
- header reworked into a branded bar: teal accent marker + uppercase
  eyebrow + supplier name as the hero, with "Magasin" and "Références"
  metric pills
- loading/error state shown as a teal (or error-red) accent chip
- "Valider" save button restyled with the brand gradient + soft shadow
- grid container radius/shadow aligned to the apple-card system

No change to table cell logic or the network columns.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 09:38:14 +02:00
MichaelandClaude Opus 4.8 524faf4a7a feat(qlik): drop network rupture% metric and the "Sans vente 6 mois" grid tab
Stock Rupture % N saturated at 100% for high-presence products (degenerate
at the article x network grain), so remove it end to end: config id,
NetworkMetric, hypercube (width 8->7), cache, schema, enrich and grid
column. db-init drops the column on existing DBs.

Also remove the "Sans vente 6 mois" tab from the grid page: the grid now
renders directly without the tab switcher; delete the NoSalesTab component.
(The separate Stock negatif page keeps its own sans-vente tabs.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 09:32:03 +02:00
MichaelandClaude Opus 4.8 ae1deedd19 chore(qlik): log measure expressions to diagnose rupture%=100%
3 of 4 new measures now resolve to engine qIds and return varied values
(CA/mag, couverture, marge%). Stock Rupture % N resolves but returns a
constant 1.0 (100%) per article — a measure-semantics issue. Log the
resolved rupture & couverture expressions to understand the definition,
and make the enrich sample pick a non-zero match (the previous sample hit
a no-sales code and looked all-zero).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 09:01:51 +02:00
MichaelandClaude Opus 4.8 8bd45518f2 chore(qlik): log raw sample of 4 new metrics to isolate empty columns
Adds a sample log of the first extracted rows (all 8 hypercube columns)
and a sample of the network metrics matched from cache in getProductRows,
to pinpoint whether the new measures return null from Qlik, fail to
persist, or fail to render.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 08:39:35 +02:00
MichaelandClaude Opus 4.8 bd9bc78f4c feat(qlik): extract 4 decision metrics (CA/mag, couverture, marge%, rupture%)
Adds CA par Magasin, Couverture stock Qte, Marge % and Stock Rupture %
network measures to the Qlik hypercube extraction (in-page Playwright ws
+ legacy server ws), persists them in qlik_network_metrics, joins them in
getProductRows and renders 4 new grid columns with buy-signal coloring
(low coverage / high margin = green; high rupture = rose).

- qlik-client: config IDs (env-overridable, master GUIDs of app 9872ee6e),
  NetworkMetric fields, hypercube width 4->8, page height 2500->1250
- qlik-playwright: same 4 measures in the in-page hypercube + row mapping
- schema + db-init: 4 numeric columns (ALTER ADD COLUMN IF NOT EXISTS for
  existing DBs)
- cache + grid types + enrich: plumb the new fields end to end

Note: percent measures stored as raw Qlik ratio; UI renders x100 when |v|<=1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 08:34:54 +02:00
MichaelandClaude Opus 4.8 0b9846f55d fix(qlik): auto-refresh grid after sync without manual reload
Restore the _refresh searchParam bump so grid-client refetches
/api/grid/rows with refresh=1 (forceRefresh, bypassing the 10-min
getProductRows cache) and re-renders the network columns from the
store. Also call router.refresh() to update the server-rendered
MAJ Qlik timestamp on the button.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 08:23:34 +02:00
MichaelandClaude Opus 4.8 aee968d30f feat(grid): replace ranking/AI/score with Qlik network data
Remove ranking, AI analysis (routes + ai-copilot module), and score-engine
from the grid. Add Qlik Sense network metrics (CA, Qte, nb magasins per
product) joined by code centrale (articles.artcentrale).

- qlik-client.ts: NTLM + ticket SSO auth, QIX hypercube extraction (master items)
- qlik-network-cache.ts + qlik_network_metrics table (db-init): cache layer
- POST /api/qlik/sync?fournisseur=: per-supplier sync into cache
- SyncQlikButton: per-supplier sync, grid reload, last-update date display
- get-product-rows Phase 8: enrich rows with cached network metrics
- heatmap-grid: CA reseau / Qte reseau / Magasins (/270) / % presence columns
- discovery: app 9872ee6e, master items CA N / Quantite N / Magasin Ventes Nb N
- deps: httpntlm, ws

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 22:52:39 +02:00
Michael cd7b87aef8 feat: date de derniere entree en stock par magasin dans le tooltip des badges 2026-05-26 15:57:16 +02:00
Michael 1b5c260813 feat(grid): add premium semi-blocking glassmorphism loading overlay with real-time stream progression 2026-05-26 14:00:16 +02:00
Michael d75102fb18 perf(grid): optimize state store with O(1) row indexing and debounced summary calculations 2026-05-26 13:53:51 +02:00
Michael e6f40d3d81 feat(grid): implement product streaming with JSON chunks for grid client 2026-05-26 13:31:26 +02:00
MichaelandClaude Sonnet 4.6 dc16beeda4 feat: add no-sales-6-months tab to stock management page
Adds a second tab "Sans vente 6 mois" on the grid page listing all
products with no sale in the last 6 months, sorted by oldest last
sale first. Resolves merge conflicts from upstream refactor.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 12:49:13 +02:00
Michael 6492f93bf4 feat: add NoSalesTab component to display products without sales in the last 6 months 2026-04-14 12:45:21 +02:00
Michael a2c4a065dd feat: add ExportDropdown component with Excel and PDF export functionality for grid data 2026-04-10 13:25:19 +02:00
MichaelandClaude Sonnet 4.6 6b26676836 feat: flag products whose last registered supplier differs from current view
Instead of unreliable preference=1, use artfou1.no_id DESC (most recently
added artfou1 entry) to identify the last supplier for each product.
Shows an amber warning icon in the Désignation column when the last
supplier ≠ current supplier view, with tooltip showing who to attribute
the sales to.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 11:55:42 +02:00
MichaelandClaude Sonnet 4.6 6ef23de317 feat: restrict AI analysis to admin role only
- FloatingSummaryBar: hide BulkAiAnalyzer button for non-admins
- AiInsightBlock: hide individual Analyser button for non-admins
- Both use useSession() to check role === "admin"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 11:43:48 +02:00
MichaelandClaude Sonnet 4.6 bbb04a4d2a fix: immediate AI loading feedback + auto-fallback for deprecated model
- BulkAiAnalyzer: set isAnalyzing=true immediately on click (before
  context fetch), so the spinner appears instantly instead of after
  1-2 seconds of silence
- analyze/route.ts: if configured model returns 400/404 (e.g., deprecated
  google/gemini-flash-1.5), automatically retry with google/gemini-2.0-flash-001
- batch-analyze/route.ts: same fallback logic

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 11:34:14 +02:00
MichaelandClaude Sonnet 4.6 2aa9cbecaa revert: restore grid to pre-infinite-scroll state
Remove infinite grid loading, useInfiniteGrid hook, and server-side
pagination API route. Revert grid components to the simpler version
that avoids 2MB+ cache failures from unstable_cache.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 11:26:13 +02:00
Michael b39e010903 feat: implement infinite grid loading with server-side pagination and client-side integration 2026-04-02 18:39:29 +02:00
Michael 49928e9c08 feat: implement useInfiniteGrid hook for paginated data fetching and state management 2026-04-02 17:53:09 +02:00
Michael 4dfdcc1144 feat: implement infinite scrolling and background data loading for the product grid component 2026-04-02 17:47:10 +02:00
Michael f70249555e feat: implement high-performance virtualized heatmap grid with granular Zustand state management 2026-04-02 17:36:42 +02:00
Michael 43644a7d1d feat: implement server-side product row data fetching with caching and aggregation logic 2026-04-02 17:28:23 +02:00
Michael 88a3121c92 feat: implement grid state management, data fetching, and infinite scroll infrastructure 2026-04-02 17:22:41 +02:00
Michael 2893114fca feat: implement infinite grid pagination with server-side data fetching and custom hook 2026-04-02 17:17:32 +02:00
Michael 4db7faa41f feat: implement server-side product data aggregation and caching for grid view 2026-04-02 17:00:21 +02:00
Michael 557e3ed8e0 feat: implement server-side data loader for product grid rows with caching and aggregation logic 2026-04-02 16:55:46 +02:00
Michael 7e4b370267 feat: implement PostgreSQL client for bulk data retrieval to replace per-article HTTP requests and configure Claude permissions 2026-04-02 16:31:11 +02:00
MichaelandClaude Sonnet 4.6 ead53db496 feat: restrict gamme/export/exports-page to admin; fix create user button style
- Non-admin users see gamme as read-only badge (no select)
- Export button hidden for non-admin in grid
- Save drafts button hidden for non-admin
- Exports page hidden in sidebar for non-admin
- Create user button: explicit blue style (was unreadable with apple-btn-primary)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 13:29:52 +02:00
Michael 8ecba594cf feat: introduce virtualized heatmap grid component with dynamic monthly data and optimized cell rendering. 2026-03-23 16:20:17 +01:00