mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-11 17:26:32 +02:00
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
This commit is contained in:
5 files changed
+44
-4
No files matched your search
@@ -93,10 +93,12 @@ export async function GET(req: NextRequest) {
|
||||
// Métriques Qlik et gamme serveur relues au moment de l'appel (voir api-enrich).
|
||||
const rows = q.enrich === "1" ? await enrichRows(result.rows) : result.rows;
|
||||
|
||||
const pagination = buildPagination(q.page, q.limit, result.total);
|
||||
|
||||
return ok(
|
||||
rows.map((r) => pickFields(r, q.fields)),
|
||||
{
|
||||
pagination: buildPagination(q.page, q.limit, result.total),
|
||||
pagination,
|
||||
meta: {
|
||||
fournisseur: q.fournisseur,
|
||||
computedAt: result.computedAt,
|
||||
@@ -105,6 +107,20 @@ export async function GET(req: NextRequest) {
|
||||
enrichi: q.enrich === "1",
|
||||
/** true = l'instantané n'existait pas et vient d'être calculé par cet appel. */
|
||||
computedOnDemand,
|
||||
/**
|
||||
* true = cette réponse contient **toutes** les lignes du fournisseur.
|
||||
* Dit explicitement à un appelant — en particulier un agent — s'il
|
||||
* peut s'arrêter là, au lieu de le laisser déduire d'une pagination
|
||||
* qu'il risque d'ignorer.
|
||||
*/
|
||||
complet: !pagination.hasMore,
|
||||
...(pagination.hasMore
|
||||
? {
|
||||
avertissement:
|
||||
`Réponse partielle : ${rows.length} lignes sur ${result.total}. `
|
||||
+ `Relancez avec page=${q.page + 1}, ou augmentez limit (5000 au maximum) pour tout obtenir en un appel.`,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -22,7 +22,7 @@ export async function GET(req: NextRequest) {
|
||||
|
||||
const paginationParams = [
|
||||
{ name: "page", in: "query", schema: { type: "integer", minimum: 1, default: 1 }, description: "Numéro de page." },
|
||||
{ name: "limit", in: "query", schema: { type: "integer", minimum: 1, maximum: 500, default: 100 }, description: "Nombre de lignes par page (500 maximum)." },
|
||||
{ name: "limit", in: "query", schema: { type: "integer", minimum: 1, maximum: 5000, default: 100 }, description: "Lignes par page. Jusqu'à 5000 sur /grid : un seul appel suffit pour un fournisseur entier. 500 maximum sur les autres endpoints." },
|
||||
];
|
||||
const sortParams = [
|
||||
{ name: "sort", in: "query", schema: { type: "string", enum: GRID_SORT_KEYS }, description: "Colonne de tri." },
|
||||
@@ -93,6 +93,7 @@ export async function GET(req: NextRequest) {
|
||||
limit: { type: "integer" },
|
||||
total: { type: "integer" },
|
||||
totalPages: { type: "integer" },
|
||||
hasMore: { type: "boolean", description: "true = il reste des pages. Sur /grid, meta.complet dit la même chose en positif." },
|
||||
},
|
||||
},
|
||||
Fournisseur: {
|
||||
|
||||
@@ -50,7 +50,7 @@ const ENDPOINTS: Array<{ method: string; path: string; desc: string }> = [
|
||||
];
|
||||
|
||||
const PARAMS: Array<{ name: string; desc: string }> = [
|
||||
{ name: "page, limit", desc: "Pagination (limit ≤ 500, défaut 100)" },
|
||||
{ name: "page, limit", desc: "Pagination. limit ≤ 5000 sur /grid — un seul appel suffit donc pour un fournisseur entier ; ≤ 500 ailleurs" },
|
||||
{ name: "sort, order", desc: "Tri, ex. sort=totalCa&order=desc" },
|
||||
{ name: "search", desc: "Libellé, codein, GTIN, référence ou code centrale" },
|
||||
{ name: "gamme, code1..code3", desc: "Filtres sur la gamme et la nomenclature" },
|
||||
@@ -122,6 +122,11 @@ export function ApiConnectionInfo() {
|
||||
<p className="text-[12px] font-semibold" style={{ color: "var(--text-primary)" }}>Exemples</p>
|
||||
<CodeLine>{`curl -H "X-API-Key: VOTRE_CLE" \\\n "${base}/products/search?q=tapis&limit=20"`}</CodeLine>
|
||||
<CodeLine>{`curl -H "X-API-Key: VOTRE_CLE" \\\n "${base}/grid?fournisseur=FOU001&fields=codein,libelle1,totalCa,codeGammeServeur"`}</CodeLine>
|
||||
<p className="text-[11px] pt-1" style={{ color: "var(--text-muted)" }}>
|
||||
Tout un fournisseur en un seul appel — <code className="font-mono">meta.complet</code> confirme
|
||||
qu'il ne reste rien à lire :
|
||||
</p>
|
||||
<CodeLine>{`curl -H "X-API-Key: VOTRE_CLE" \\\n "${base}/grid?fournisseur=FOU001&limit=5000"`}</CodeLine>
|
||||
</div>
|
||||
|
||||
{/* Branchement d'une IA externe (ChatGPT) */}
|
||||
|
||||
@@ -31,10 +31,15 @@ export interface Pagination {
|
||||
limit: number;
|
||||
total: number;
|
||||
totalPages: number;
|
||||
/** `true` s'il reste des pages à lire — évite d'avoir à comparer page et totalPages. */
|
||||
hasMore: boolean;
|
||||
}
|
||||
|
||||
export function buildPagination(page: number, limit: number, total: number): Pagination {
|
||||
return { page, limit, total, totalPages: Math.max(1, Math.ceil(total / limit)) };
|
||||
const totalPages = Math.max(1, Math.ceil(total / limit));
|
||||
// `hasMore` explicite : un agent qui doit comparer page et totalPages oublie
|
||||
// souvent de le faire et conclut à tort qu'il a tout récupéré.
|
||||
return { page, limit, total, totalPages, hasMore: page < totalPages };
|
||||
}
|
||||
|
||||
/** Réponse de succès. `meta` porte la fraîcheur de la donnée (`computedAt`). */
|
||||
|
||||
@@ -52,6 +52,19 @@ export const gridQuerySchema = z.object({
|
||||
compute: z.enum(["0", "1"]).default("1"),
|
||||
...sortShape,
|
||||
...paginationShape,
|
||||
/**
|
||||
* Plafond relevé à 5000 pour `/grid` : un lot fournisseur dépasse souvent 500
|
||||
* références, et l'objectif est qu'une app ou un agent récupère **tout** le
|
||||
* fournisseur en un seul appel plutôt que de paginer — ce que les agents font
|
||||
* mal, concluant à tort qu'ils ont tout lu.
|
||||
*
|
||||
* La borne reste à 500 sur la recherche transversale, qui elle n'est pas
|
||||
* bornée par un fournisseur.
|
||||
*
|
||||
* Réponse volumineuse : une ligne complète porte les séries mensuelles et les
|
||||
* ventilations par magasin. Combiner avec `fields` est vivement conseillé.
|
||||
*/
|
||||
limit: z.coerce.number().int().min(1).max(5000).default(100),
|
||||
});
|
||||
|
||||
/** `/api/v1/products/search` — `fournisseur` facultatif : c'est la recherche transversale. */
|
||||
|
||||
Reference in new issue
Block a user