Commit Graph
7 Commits
Author SHA1 Message Date
Claude 0a57a9a707 fix(api): nomenclatures dérivées du préfixe de code3, pas de code1/code2
Les codes de nomenclature FF font 6 chiffres et sont hiérarchiques par préfixe
(31xxxx à 40xxxx) : « 32 » univers, « 3202 » famille, « 320211 » sous-famille.

L'endpoint groupait sur les colonnes code1/code2, qui ne sont renseignées que
si pgGetNomenclatureByFournisseur a su remonter la hiérarchie — ce qui dépend
d'une détection heuristique de la colonne parent de la table `nomenclature`.
Quand elle échoue, seul code3 est rempli et l'endpoint aurait renvoyé une liste
vide aux niveaux 1 et 2, sans rien signaler.

Les niveaux sont désormais dérivés de left(code3, 2|4|6), toujours disponible.

En conséquence, /grid gagne un filtre `nomenclature` par préfixe, qui fonctionne
aux trois niveaux et ne dépend pas non plus de code1/code2 : c'est lui que
meta.filtreGrid désigne. Les filtres code1..code3 exacts restent inchangés.

Les libellés viennent toujours du payload et peuvent être null aux niveaux 1 et
2 pour la même raison ; codes et compteurs, eux, sont exacts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y26nRZxTR57K7h8yqsF675
2026-08-07 06:08:02 +00:00
Claude 8fe294924b fix(api): tient les très gros fournisseurs (132k articles)
Le journal du fournisseur D005 montre 132 388 lignes persistées pour un seul
fournisseur. À cette échelle, /api/v1/grid sans limit ne tenait pas.

Deux plantages francs, indépendants de la taille de la réponse :
- getNetworkMetricsByCodeCentrale : inArray avec 132 000 valeurs, soit autant
  de paramètres liés, alors que le protocole PostgreSQL en accepte 65535.
- pgGetGammesByCodeins : même problème sur son IN (…).
Les deux lectures sont désormais découpées par lots de 2000. Le bug existait
pour tout appel dépassant ~65 000 codes, indépendamment de l'API.

Volume : charger 132k lignes complètes puis les sérialiser d'un bloc représente
plusieurs centaines de Mo en mémoire. Sans `limit`, la réponse est maintenant
diffusée en flux, par lots internes de 1000, à mémoire constante. La forme
{ data, pagination, meta } est inchangée — un client existant ne voit que des
octets arrivant progressivement.

Une erreur survenant après les premiers octets ne peut plus changer le code
HTTP : le document est alors fermé avec meta.erreur et meta.complet=false, pour
qu'une réponse tronquée ne passe pas pour complète.

`fields` reste le levier décisif à cette échelle : une ligne complète porte les
séries mensuelles et les ventilations par magasin.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y26nRZxTR57K7h8yqsF675
2026-08-07 05:45:49 +00:00
Claude 33b30dc8bd fix(api): supprime le plafond de 500 lignes, qui tronquait en silence
Le passage à limit=5000 du commit précédent était sans effet : queryGridRows
re-plafonnait à 500 en dur (Math.min(500, …)). Pire, la pagination était
calculée sur le limit *demandé*, donc hasMore et meta.complet annonçaient une
réponse complète alors qu'elle était tronquée — exactement la troncature
silencieuse que ces champs devaient éliminer.

- Plus aucun plafond. Sur /grid, omettre `limit` renvoie toutes les lignes du
  fournisseur : plus de pagination à dérouler, ce que les agents font mal.
- La pagination reflète le mode sans limite (une page couvrant le total), donc
  hasMore et meta.complet disent la vérité.
- Le défaut reste 100 sur les endpoints de recherche, non bornés par un
  fournisseur — mais sans maximum imposé.
- meta.avertissement explique désormais que la troncature vient du `limit`
  fourni, et qu'il suffit de l'omettre.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y26nRZxTR57K7h8yqsF675
2026-08-06 11:46:32 +00:00
Claude 73accd6d60 feat(api): un seul appel suffit pour tout un fournisseur
Le plafond de pagination était de 500 lignes. Un lot fournisseur dépasse
souvent ce seuil, si bien qu'une app ou un agent ne récupérait qu'une partie
des données sans forcément s'en rendre compte — et les agents paginent mal,
concluant volontiers qu'ils ont tout lu.

- /grid accepte désormais limit jusqu'à 5000. La borne reste à 500 sur la
  recherche transversale, qui n'est pas bornée par un fournisseur.
- pagination.hasMore, et meta.complet sur /grid : l'appelant sait s'il a tout,
  sans avoir à comparer page et totalPages.
- Réponse partielle : meta.avertissement indique le nombre de lignes obtenues
  sur le total et comment obtenir le reste. Pas de troncature silencieuse.
- Doc des Paramètres et openapi.json mis à jour, avec un exemple curl qui
  récupère un fournisseur entier.

`fields` reste vivement conseillé : une ligne complète porte les séries
mensuelles et les ventilations par magasin.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y26nRZxTR57K7h8yqsF675
2026-08-06 11:30:25 +00:00
Claude 3a825866d2 feat(api): /api/v1/grid calcule le fournisseur à la demande
L'API lisait uniquement la table grid_rows, écrite en effet de bord quand un
humain ouvre la page Grille. Un fournisseur jamais ouvert renvoyait donc
202 not_ready : une app ou un agent externe ne pouvait consulter que ce qui
avait déjà été parcouru dans l'interface, ce qui vide l'API de son intérêt.

/api/v1/grid déclenche désormais getProductRows() quand l'instantané manque.
Le calcul est déjà protégé en amont (cache 10 min + verrou anti-concurrence)
et persiste l'instantané, donc seul le premier appel paie le coût ; les
suivants repassent par le chemin SQL rapide.

- compute=1 (défaut) : calcul à la demande, meta.computedOnDemand signale
  quand il a eu lieu. compute=0 : comportement strict d'avant.
- Fournisseur inconnu ou sans article : 404 explicite, distinct d'une panne
  (500) et de « pas encore calculé » (202).
- /api/v1/products/{codein} fait de même lorsque `fournisseur` est fourni,
  le calcul se faisant par lot fournisseur.
- openapi.json mis à jour : c'est ce document que lit un agent externe, il
  annonçait « aucun endpoint ne déclenche de recalcul ».

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y26nRZxTR57K7h8yqsF675
2026-08-06 10:29:26 +00:00
Claude a45e09777b feat(api): métriques Qlik + gamme serveur à jour, et doc de connexion dans Paramètres
L'instantané grid_rows fige la ligne au moment du calcul, mais deux données
évoluent indépendamment et doivent refléter l'état courant :

- Métriques réseau Qlik : qlik_network_metrics est alimentée par les syncs et
  les recherches réseau, hors du calcul de grille. Relues à chaque appel et
  exposées dans `network` (null quand le produit n'en a pas). Les colonnes
  caReseau/qteReseau/... de la ligne sont réalignées dessus.
- Gamme serveur : codeGamme peut être surchargée par un snapshot de session.
  L'API expose `codeGammeServeur`, la gamme NON modifiée telle qu'elle est en
  base PostgreSQL, relue à chaque appel. codeGammeInit est gardé aligné.

Ajout de pgGetGammesByCodeins() : variante sans jointure artfou1, nécessaire
car la recherche de l'API est transversale (pas de fournisseur connu).

Ce n'est pas un recalcul : deux lectures indexées bornées à la page courante
(500 lignes max). Mesuré à ~20 ms, soit le même coût que sans enrichissement.
Paramètre enrich=0 pour servir l'instantané brut.

Paramètres → nouvelle section « API CollectFlow — Connexion » : URL de base
déduite de l'origine, en-têtes d'authentification, liste des endpoints et des
paramètres, exemples curl copiables, lien vers openapi.json.

Vérifié sur PostgreSQL local avec un instantané volontairement périmé :
gamme snapshot A → serveur C, caReseau 111 → 990000, produit sans données
réseau → network null, produit sans gamme → codeGammeServeur null, enrich=0
redonnant bien les valeurs figées, et toujours zéro recalcul.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y26nRZxTR57K7h8yqsF675
2026-08-05 10:14:21 +00:00
Claude e79d7e51da feat(api): API CollectFlow /api/v1 — lecture de la grille et recherche
Les données de la grille (ventes 12 mois, stock, marges, gammes, métriques
réseau Qlik) n'étaient accessibles par aucun moyen programmatique, et
getProductRows() exige un fournisseur : chercher un produit sans le connaître
était impossible.

Contrainte de conception : l'API ne recalcule jamais rien. La grille était
reconstruite en direct et gardée seulement 10 min en mémoire ; une API qui
appellerait getProductRows() serait lente et imprévisible. On persiste donc
le résultat d'un calcul qui a déjà lieu, et on le sert.

- Table grid_rows : colonnes scalaires (filtre/tri/recherche en SQL) +
  payload jsonb du ProductRow complet. Remplie en effet de bord NON bloquant
  par getProductRows(), purge des articles disparus via computed_at. Survit
  aux redémarrages, contrairement au cache mémoire.
- Endpoints /api/v1 : fournisseurs, grid, products/search (transversale, tous
  fournisseurs), products/:codein, network/:codeCentrale, openapi.json.
  Pagination, tri sur liste blanche, projection de champs, validation zod.
  202 not_ready si un fournisseur n'a pas encore d'instantané.
- Authentification double : clé d'API (X-API-Key ou Bearer, SHA-256 en base,
  révocable) ou session existante. Le middleware exempte /api/v1 — sans quoi
  un script recevait une redirection 302 vers /login au lieu d'un 401 JSON.
- Gestion des clés dans /settings (server actions, clé affichée une seule fois).

Vérifié contre une base PostgreSQL locale : 401 JSON sans clé, 401 sur clé
révoquée, recherche renvoyant plusieurs fournisseurs, upsert + purge, et
24 appels /api/v1 sans déclencher un seul recalcul (l'ancienne route
/api/grid/rows en déclenche un à chaque appel).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y26nRZxTR57K7h8yqsF675
2026-08-05 10:14:21 +00:00