DISTINCT ON with ORDER BY no_id ASC was picking the oldest supplier entry
(BAZAR 5000 historically), attributing CA to a supplier the store no longer
uses. Changing to DESC picks the most recently inserted artfou1 row, which
represents the current active supplier for each article.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
artfou1 has multiple rows per article (multi-supplier), causing each mvtart
movement to be counted N times in the JOIN. Use a CTE with DISTINCT ON
(art_no_id) ORDER BY no_id to pick one canonical supplier per article,
matching the same pattern used in pgGetArticlesByFournisseur.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- page.tsx: move formatCA into client component (functions not serializable
across server→client boundary in Next.js — root cause of 500)
- client.tsx: remove unused BarChart2 import
- client.tsx: replace useSearchParams() with direct URL construction to
avoid Suspense boundary requirement
- pg-ff-client: use OR conditions instead of IN() for Drizzle compatibility
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- pg-ff-client: replace ANY(ARRAY[...]) with IN(...) for Drizzle compatibility
- sidebar: change Analytics icon from BarChart2 (duplicate) to TrendingUp
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
EOF
- pg-ff-client: add pgGetCaByFournisseur() and pgGetCaByNomenclature()
returning CA TTC per site and month for monthly N vs N-1 comparison
- analytics/page.tsx: server component with pivot logic to reshape raw
query data into store-separated columns (292/579/total) with evolution %
- analytics/client.tsx: client controls (mode dropdown, month selector)
and responsive table with color-coded evolution badges
- sidebar: add "Analytics" link to navigation
Features:
- Switch between "Fournisseur" and "Nomenclature" views
- Select any month for comparison vs same month prior year
- Columns split by store: Frouard/Nancy (292), Houdemont (579), Total
- Each store shows: CA this month, CA N-1, evolution %
- Footer totals row matching sum of detail rows
- FR locale currency formatting (€)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- score-engine: add hard guard (wQty=0 && wCa=0 → score=0) before any
calculation to prevent edge cases (residual margin, percentile rank)
from producing non-zero scores on unsold articles
- score-engine: set inactivity=24 when product has never sold, so the
regularityMultiplier penalty (0.3) is correctly applied instead of 1
- pg-ff-client: remove erroneous negation on qte_recue — entry movements
have positive qtemvt in the DB, negating them produced negative values
that failed the > 0 check, hiding all entrées in tooltips and modals
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>