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
Quand l'app Qlik ne peut pas se charger faute de RAM serveur (code 6
'Not enough memory to load file' / code 3002 'File corrupted' / 'Out of
memory'), le job affiche un message clair ('Serveur Qlik saturé…') au
lieu de l'erreur technique. Ce n'est pas un bug CollectFlow : le sync
échoue à l'ouverture de l'app, avant toute requête.
- 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.
La requête Hit Parade divergeait du calcul canonique de la Grille :
- SUM(ABS(qtemvt)) additionnait les retours clients comme des ventes au
lieu de les déduire → quantités et CA gonflés sur le magasin ayant des
retours/avoirs sur la période (visible sur 579).
- ABS(SUM(mntmvtttc)) inversait le signe d'un CA net négatif.
- GROUP BY no_id + jointure artfou1 preference=1 non dédupliquée pouvait
produire plusieurs lignes par (codein, site), écrasées par le pivot.
Corrections :
- Hit Parade : ventes nettes SUM(-qtemvt) / SUM(-mntmvtttc), agrégation
par (codein, site), fournisseur préféré via LATERAL ... LIMIT 1,
stock agrégé par codein ; pivot en accumulation (+=) par sécurité.
- Analytics (même classe de bug) : pgGetCaByFournisseur et
pgGetCaByNomenclature passent en CA net ; jointure fournisseur
dédupliquée (LATERAL LIMIT 1) pour éviter le double comptage.
- Gestion de stock : pgGetStockNegatif et pgGetSansVente6Mois dédupliquent
le fournisseur préféré (LATERAL LIMIT 1) pour éviter les lignes en double.
Audit complet du mapping par magasin (292/579) côté client : aucun champ
inversé (hit-parade, analytics, dashboard, grid, heatmap). Build OK.
The Qlik proxy refuses a raw server-side websocket (403, even internally), so
extraction now runs through headless Chromium (NTLM via httpCredentials, like
the hermes agent) and opens the Engine websocket in-page — same origin, which
the proxy accepts. Efficient: selects the supplier's article codes on the
"Article Code" field so the hypercube returns only those rows.
- qlik-playwright.ts: browser singleton, in-page hypercube extraction (paginated)
- /api/qlik/sync: use the Playwright extractor
- Dockerfile: install chromium + headless deps, PLAYWRIGHT_CHROMIUM_PATH, copy playwright-core
- deps: playwright-core (lockfiles synced)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a "Qlik Sense — Données réseau" section in Paramètres (host/user/password
+ Save & Test buttons), stored in data/.db-config.json. qlik-client reads this
file first (env fallback), so no env vars needed for the sync.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le build Docker échouait sur le fetch de la police Inter via next/font/google
(accès réseau/TLS indisponible pendant 'next build').
- layout.tsx : charge Inter via <link> côté navigateur (plus de fetch au build),
avec fallback système ; --font-sans défini dans globals.css (@theme).
- /commandes-auto : export dynamic = 'force-dynamic' (données live DB + API,
jamais prérendues au build).
- listCadences : résilient si la DB est injoignable (retourne []).
Ajoute un onglet "Cadencier / Alertes" dans la page Commandes auto.
Depuis la liste des fournisseurs, on active un fournisseur en alerte de
commande sur un magasin (292/579) avec une fréquence en X semaines,
gérée indépendamment par magasin.
L'échéance est calculée à partir de la dernière réception réelle
(mvtart, genremvt 1/2, fournisseur principal artfou1.preference=1) +
X semaines. Statut: à commander / bientôt / OK, avec temps restant.
- Table commande_cadences (schema + db-init)
- pgGetDerniereReceptionParFournisseur (mvtart par fournisseur/site)
- Server actions CRUD (list/upsert/toggle/remove)
- UI à onglets + cadencier (ajout, édition intervalle, activer/supprimer)
Exports the currently filtered and sorted table (including totals row) to an .xlsx file named with the selected date range. Uses the xlsx library.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Enrich DashboardTopItem with stock and fournisseur fields via PostgreSQL,
then display them under each article in the dashboard top CA/Qte/Marge tables.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Exclude products with a stock entry in the last 6 months (NOT EXISTS subquery)
- Add derniere_entree subquery to SQL (last genremvt 1/2 movement date)
- Display derniere_entree column in table and Excel export
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New SQL query pgGetSansVente6Mois returns products with available
stock (qte > 0) whose last sale was over 6 months ago or never.
Third tab added with filtering by supplier, search, sort, and Excel export.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace SQL tools with call_api tool hitting FF Nancy REST endpoints
- Add Google AI Studio provider support alongside OpenRouter
- Load AI provider config (openrouter/google) from saved settings
- Add model selector loaded dynamically from OpenRouter and Google AI APIs
- Add HTML-to-Markdown conversion for models that return HTML
- Enrich system prompt with full FF Nancy API docs, PA/PRMP field locations
- Fix streaming: single generateContentStream pass for Google AI (no double call)
- Increase max_tokens to 8192 and tool loop to 10 rounds
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>