23 KiB
Documentation API Foirfouille
Base URL : https://api.ffnancy.fr
Format : JSON
Méthode : GET uniquement (API lecture seule)
Données : Synchronisées depuis SQL Server chaque nuit à 3h00
Santé
GET /api/health
{ "status": "ok", "database": "foirfouille", "server": "postgres" }
Articles
Liste avec filtres
GET /api/articles
| Paramètre | Type | Description | Défaut |
|---|---|---|---|
search |
string | Recherche dans libellé | — |
codein |
string | Code interne (partiel) | — |
ean |
string | EAN / GTIN | — |
codefou |
string | Code fournisseur (partiel) | — |
actif |
1/0 |
1 = actif, 0 = suspendu |
— |
page |
int | Numéro de page | 1 |
limit |
int | Lignes par page (max 500) | 50 |
Réponse :
{
"page": 1,
"limit": 50,
"articles": [
{
"no_id": 12345,
"codein": "ABC123",
"libelle1": "MON ARTICLE",
"libelle2": "SOUS-LIBELLE",
"lib_ticket": "MON ART",
"tax_code": "A",
"ach_code": "X",
"utilisable": "O",
"actif": "O",
"suspendu": null,
"suividatecreation": "2020-01-15T00:00:00.000Z",
"suividatemodif": "2024-06-01T00:00:00.000Z",
"prix_vente_mini": 15.00,
"prix_vente_maxi": 25.00,
"eco_ttc": 0.10,
"on_web": "O",
"interdit_remise": null,
"nomphoto": "ABC123.jpg",
"datedebvente": null,
"datefinvente": null,
"pa": 10.50,
"gtin": "3760000000001",
"codefou_principal": "FOU01",
"nom_fou_principal": "MON FOURNISSEUR",
"ref_fou_principale": "REF-FOU-001",
"pcb_principal": 6
}
]
}
Détail d'un article
GET /api/articles/:id
Paramètre : id = no_id de l'article
Réponse :
{
"article": { "no_id": 12345, "codein": "...", "libelle1": "...", "pa": 10.50, "..." : "..." },
"gtins": [
{ "gtin": "3760000000001", "preferentiel": 1 }
],
"stock": [
{ "site": "001", "qte": 12, "prmp": 10.20, "valstock": 122.40, "pv": 18.50, "stockdispo": 10 }
],
"prix": [
{ "site": "001", "pv": 18.50 }
],
"fournisseurs": [
{
"codefou": "FOU01", "nom_fou": "MON FOURNISSEUR",
"reference": "REF-001", "ean13": "3760000000001",
"pcb": 6, "delai": 7, "preference": 1,
"prixachat": 10.50, "remise_promotion": 5.00
}
]
}
Fiche complète (référentiel)
GET /api/articles/:id/referentiel
Retourne toutes les informations consolidées de l'article :
{
"article": {
"no_id": 12345, "codein": "ABC123", "libelle1": "MON ARTICLE",
"prix_vente_mini": 15.00, "prix_vente_maxi": 25.00,
"eco_ht": 0.08, "eco_ttc": 0.10,
"on_web": "O", "pv_conseille": 19.90,
"nom_code": "RAY01", "nom_libelle": "RAYON MAISON", "nom_niveau": 1
},
"gtins": [ { "gtin": "3760000000001", "preferentiel": 1 } ],
"gammes": [
{ "gamme_code": "ETE2024", "gamme_libelle": "ÉTÉ 2024", "saison_code": "S24", "saison_libelle": "SAISON 2024" }
],
"stock": [
{ "site": "001", "qte": 12, "prmp": 10.20, "valstock": 122.40, "pv": 18.50, "stockdispo": 10 }
],
"stock_remballe": [],
"prix": {
"achat": 10.50,
"vente_par_site": [ { "site": "001", "pv": 18.50 } ]
},
"fournisseurs": [
{ "codefou": "FOU01", "nom_fou": "MON FOURNISSEUR", "ref_fou": "REF-001", "pcb": 6, "prixachat": 10.50 }
],
"performance": {
"derniere_entree": "2024-05-10T00:00:00.000Z",
"derniere_vente": "2024-06-15T00:00:00.000Z",
"qte_totale_vendue": 248,
"ca_ttc_total": 4588.00,
"marge_totale": 1020.50
},
"notes": []
}
Mouvements d'un article
GET /api/articles/:id/mouvements
| Paramètre | Type | Description | Défaut |
|---|---|---|---|
dateDebut |
date | Date début YYYY-MM-DD |
2000-01-01 |
dateFin |
date | Date fin YYYY-MM-DD |
2099-12-31 |
site |
string | Filtrer par site (partiel) | — |
page |
int | Numéro de page | 1 |
limit |
int | Lignes par page (max 1000) | 100 |
Réponse :
{
"page": 1, "limit": 100,
"mouvements": [
{
"datmvt": "2024-06-15T00:00:00.000Z",
"site": "001",
"libmvt": "VENTE",
"genremvt": 3,
"qtemvt": 2,
"mntmvtttc": 37.00,
"margemvt": 8.20,
"qtestock": 10
}
]
}
Valeurs GenreMvt : 1 = entrée stock, 3 = vente
Fournisseurs
Liste des fournisseurs
GET /api/fournisseurs?search=
| Paramètre | Type | Description |
|---|---|---|
search |
string | Recherche sur code/nom |
Réponse :
{
"count": 142,
"fournisseurs": [
{
"codefou": "FOU01",
"nom": "MON FOURNISSEUR SARL",
"adresse": "1 RUE DE LA PAIX",
"telephone": "0383000000",
"email": "contact@fournisseur.fr",
"nb_articles": 320,
"nb_actifs": 285,
"nb_suspendus": 35
}
]
}
Articles d'un fournisseur
GET /api/fournisseurs/:code/articles?page=&limit=
Réponse :
{
"code": "FOU01",
"page": 1, "limit": 50,
"articles": [
{
"no_id": 12345, "codein": "ABC123", "libelle1": "MON ARTICLE",
"ref_fou": "REF-FOU-001", "ean13": "3760000000001",
"pcb": 6, "delai": 7, "suspendu": null,
"prixachat": 10.50, "remise_promotion": 5.00,
"pa": 10.50, "pv_central": 18.50
}
]
}
Commandes en cours d'un fournisseur
GET /api/fournisseurs/:code/commandes
| Paramètre | Type | Description | Défaut |
|---|---|---|---|
dateDebut |
date | Date début YYYY-MM-DD |
2020-01-01 |
dateFin |
date | Date fin YYYY-MM-DD |
2099-12-31 |
page |
int | Numéro de page | 1 |
limit |
int | Max 1000 | 100 |
Stock
Stock global
GET /api/stock?site=&page=&limit=
| Paramètre | Type | Description | Défaut |
|---|---|---|---|
site |
string | Filtrer par site | tous |
page |
int | Numéro de page | 1 |
limit |
int | Max 1000 | 100 |
Stock d'un article (tous sites)
GET /api/stock/article/:id
Réponse :
{
"artnoid": 12345,
"stock": [
{
"site": "001",
"qte": 12,
"prmp": 10.20,
"valstock": 122.40,
"pv": 18.50,
"stockdispo": 10,
"stockmort": 0,
"stockcolis": 2,
"dernierevente": "2024-06-15T00:00:00.000Z",
"dernierereception": "2024-05-10T00:00:00.000Z",
"nbjoursdernierevente": 5,
"interditachat": null,
"codefou": "FOU01"
}
]
}
Stock d'un article à une date précise
GET /api/stock/article/:id/historique?date=&site=
Retourne le stock tel qu'il était à une date donnée, en utilisant le champ QteStock
enregistré dans chaque mouvement (valeur du stock au moment exact du mouvement).
| Paramètre | Type | Description | Défaut |
|---|---|---|---|
date |
date | Date cible YYYY-MM-DD |
aujourd'hui |
site |
string | Filtrer par site (partiel) | tous |
Réponse :
{
"artNoId": "12345",
"date": "2025-12-31",
"stock": [
{
"site": "001",
"qte": 28,
"prmp": 10.20,
"date_dernier_mouvement": "2025-12-29T00:00:00.000Z",
"libelle_dernier_mouvement": "VENTE"
}
]
}
Principe : on prend le dernier mouvement enregistré avant ou à la date demandée. Si aucun mouvement n'existe avant cette date, le site ne figure pas dans la réponse.
Stock + ventes d'un article sur une période
GET /api/stock/article/:id/periode?dateDebut=&dateFin=&site=
Endpoint principal pour analyser un article sur une période passée. Retourne en une seule requête : stock de début, stock de fin, ventes et réceptions.
| Paramètre | Type | Description | Défaut |
|---|---|---|---|
dateDebut |
date | Date début YYYY-MM-DD |
1er du mois |
dateFin |
date | Date fin YYYY-MM-DD |
aujourd'hui |
site |
string | Filtrer par site (partiel) | tous |
Réponse :
{
"artNoId": "12345",
"dateDebut": "2025-12-01",
"dateFin": "2025-12-31",
"data": [
{
"site": "001",
"stock_debut": {
"qte": 45,
"prmp": 10.20,
"au": "2025-11-30T00:00:00.000Z"
},
"stock_fin": {
"qte": 28,
"prmp": 10.20,
"au": "2025-12-29T00:00:00.000Z"
},
"ventes": {
"site": "001",
"nb_passages": 18,
"qte_vendue": 17,
"ca_ht": 274.00,
"ca_ttc": 315.10,
"marge": 72.00,
"taux_marge": 22.86,
"premiere_vente": "2025-12-02T00:00:00.000Z",
"derniere_vente": "2025-12-29T00:00:00.000Z"
},
"receptions": {
"site": "001",
"nb_receptions": 1,
"qte_recue": 0
}
}
]
}
Lecture : stock_debut = dernier mouvement avant dateDebut / stock_fin = dernier mouvement à ou avant dateFin. Les champs
ventesetreceptionssontnullsi aucun mouvement sur la période.
Stock d'un site (tous articles)
GET /api/stock/site/:site?page=&limit=
Valorisation du stock par site
GET /api/stock/valorisation
Réponse :
{
"valorisation": [
{
"site": "001",
"nb_articles": 4200,
"qte_totale": 85000,
"val_stock": 420000.00,
"val_pv_theorique": 890000.00,
"prmp_moyen": 12.50
}
]
}
Commandes
Commandes en cours
GET /api/commandes
| Paramètre | Type | Description | Défaut |
|---|---|---|---|
dateDebut |
date | Date début YYYY-MM-DD |
2020-01-01 |
dateFin |
date | Date fin YYYY-MM-DD |
2099-12-31 |
codefou |
string | Filtrer par fournisseur | — |
page |
int | Numéro de page | 1 |
limit |
int | Max 1000 | 100 |
Réponse :
{
"page": 1, "limit": 100,
"commandes": [
{
"no_commande": 98765,
"codefou": "FOU01",
"codein": "ABC123",
"libelle": "MON ARTICLE",
"ref_fou": "REF-001",
"qte_cde": 24,
"prix_brut": 12.00,
"remise": 5.00,
"prix_net": 11.40,
"montant": 273.60,
"qte_acceptee": 24,
"qte_annulee": 0,
"qte_attente": 0,
"qte_reliquat": 0,
"date_livraison_cible": "2024-07-01T00:00:00.000Z",
"date_commande": "2024-06-01T00:00:00.000Z"
}
]
}
Détail d'une commande
GET /api/commandes/:noCommande
Réponse :
{
"noCommande": "98765",
"nbLignes": 15,
"montantTotal": 4250.80,
"lignes": [ { "..." : "..." } ]
}
Liste des réceptions
GET /api/commandes/receptions/liste
| Paramètre | Type | Description | Défaut |
|---|---|---|---|
dateDebut |
date | Date début YYYY-MM-DD |
2020-01-01 |
dateFin |
date | Date fin YYYY-MM-DD |
2099-12-31 |
page |
int | Numéro de page | 1 |
limit |
int | Max 1000 | 100 |
Mouvements
Types de mouvements disponibles
GET /api/mouvements/types
Retourne tous les codes GenreMvt et libellés présents dans la base, avec leur nombre d'occurrences.
À appeler en premier pour connaître les codes de régularisation propres à votre magasin.
{
"types": [
{ "genremvt": 1, "libmvt": "RECEPTION", "nb_occurrences": "142580" },
{ "genremvt": 3, "libmvt": "VENTE", "nb_occurrences": "2130137" },
{ "genremvt": 2, "libmvt": "RETOUR CLIENT", "nb_occurrences": "8420" },
{ "genremvt": 5, "libmvt": "REGULARISATION", "nb_occurrences": "3210" },
{ "genremvt": 6, "libmvt": "INVENTAIRE", "nb_occurrences": "1850" }
]
}
Les codes exacts dépendent de votre paramétrage Mercalys. Consultez cet endpoint pour connaître la liste réelle.
Mouvements articles (tous types)
GET /api/mouvements/articles
| Paramètre | Type | Description | Défaut |
|---|---|---|---|
dateDebut |
date | Date début YYYY-MM-DD |
2024-01-01 |
dateFin |
date | Date fin YYYY-MM-DD |
2099-12-31 |
site |
string | Filtrer par site | — |
codefou |
string | Filtrer par fournisseur | — |
genremvt |
int | Filtrer par type (ex: 1, 3, 5...) |
tous |
page |
int | Numéro de page | 1 |
limit |
int | Max 1000 | 100 |
Types principaux GenreMvt :
| Code | Type |
|---|---|
1 |
Entrée stock |
3 |
Vente |
| autres | Régularisations (voir /api/mouvements/types) |
Entrées en stock
GET /api/mouvements/entrees
Toutes les réceptions / entrées en stock (GenreMvt = 1).
Inclut la date de création de l'article pour retrouver la première entrée.
| Paramètre | Type | Description | Défaut |
|---|---|---|---|
dateDebut |
date | Date début YYYY-MM-DD |
2024-01-01 |
dateFin |
date | Date fin YYYY-MM-DD |
2099-12-31 |
site |
string | Filtrer par site | — |
artNoId |
int | Filtrer sur un article précis | — |
page |
int | Numéro de page | 1 |
limit |
int | Max 1000 | 100 |
Réponse :
{
"page": 1, "limit": 100,
"entrees": [
{
"date_entree": "2025-12-10T00:00:00.000Z",
"site": "001",
"artnoid": 12345,
"codein": "ABC123",
"libelle1": "MON ARTICLE",
"date_creation_article": "2020-03-15T00:00:00.000Z",
"libelle_mouvement": "RECEPTION",
"qte_entree": 24,
"prmp": 10.20,
"stock_apres_entree": 36,
"codefou": "FOU01"
}
]
}
Pour trouver la première entrée d'un article : filtrez par
artNoIdavecdateDebut=2000-01-01et triez par date croissante (la première ligne = première réception).
Régularisations de stock
GET /api/mouvements/regularisations
Tous les mouvements hors ventes et hors réceptions : inventaires, corrections, démarques, transferts, retours, etc.
| Paramètre | Type | Description | Défaut |
|---|---|---|---|
dateDebut |
date | Date début YYYY-MM-DD |
2024-01-01 |
dateFin |
date | Date fin YYYY-MM-DD |
2099-12-31 |
site |
string | Filtrer par site | — |
artNoId |
int | Filtrer sur un article précis | — |
page |
int | Numéro de page | 1 |
limit |
int | Max 1000 | 100 |
Réponse :
{
"page": 1, "limit": 100,
"regularisations": [
{
"date_mouvement": "2025-12-05T00:00:00.000Z",
"site": "001",
"artnoid": 12345,
"codein": "ABC123",
"libelle1": "MON ARTICLE",
"genremvt": 6,
"libelle_mouvement": "INVENTAIRE",
"qte": -3,
"valeur": -30.60,
"stock_apres": 42,
"prmp": 10.20
}
]
}
Une
qtenégative = sortie de stock (correction à la baisse, démarque…). Uneqtepositive = entrée hors réception (correction à la hausse, retour…).
Synthèse par jour et site
GET /api/mouvements/synthese?dateDebut=&dateFin=&site=
Réponse :
{
"synthese": [
{
"jour": "2024-06-15",
"site": "001",
"genremvt": 3,
"nb_mvt": 152,
"qte_totale": 312,
"mnt_ht": 4200.00,
"mnt_ttc": 4830.00,
"marge": 1050.00
}
]
}
Performance
Chiffre d'affaires
GET /api/performance/ca
| Paramètre | Type | Description | Défaut |
|---|---|---|---|
dateDebut |
date | Date début YYYY-MM-DD |
2024-01-01 |
dateFin |
date | Date fin YYYY-MM-DD |
2099-12-31 |
site |
string | Filtrer par site | tous |
groupBy |
jour / mois |
Granularité | jour |
Réponse :
{
"groupBy": "mois",
"dateDebut": "2024-01-01",
"dateFin": "2024-12-31",
"site": "tous",
"ca": [
{
"periode": "2024-06",
"site": "001",
"nb_ventes": 4820,
"qte_vendue": 9640,
"ca_ht": 85200.00,
"ca_ttc": 97980.00,
"marge": 21300.00,
"taux_marge": 21.74
}
]
}
Hit-parade articles
GET /api/performance/hitparade
| Paramètre | Type | Description | Défaut |
|---|---|---|---|
dateDebut |
date | Date début YYYY-MM-DD |
2024-01-01 |
dateFin |
date | Date fin YYYY-MM-DD |
2099-12-31 |
site |
string | Filtrer par site | tous |
limit |
int | Nombre d'articles (max 500) | 50 |
groupBy |
ca / qte / marge |
Critère de classement | ca |
Réponse :
{
"classementPar": "ca",
"hitparade": [
{
"codein": "ABC123",
"libelle1": "MON ARTICLE",
"site": "001",
"nb_passages": 320,
"qte_vendue": 640,
"ca_ht": 5760.00,
"ca_ttc": 6624.00,
"marge": 1440.00,
"taux_marge": 21.74,
"derniere_vente": "2024-06-15T00:00:00.000Z"
}
]
}
CA par nomenclature (rayon)
GET /api/performance/ca/nomenclature?dateDebut=&dateFin=&site=&niveau=1
| Paramètre | Type | Description |
|---|---|---|
niveau |
int | Niveau de nomenclature (1 = racine) |
Réponse :
{
"ca_par_nomenclature": [
{
"code_nomen": "RAY01",
"libelle": "RAYON MAISON",
"niveau": 1,
"nb_articles": 420,
"qte_vendue": 8400,
"ca_ht": 95000.00,
"ca_ttc": 109250.00,
"marge": 23750.00
}
]
}
CA par gamme
GET /api/performance/ca/gamme?dateDebut=&dateFin=&site=
Réponse :
{
"ca_par_gamme": [
{
"gamme": "ETE2024",
"libelle_gamme": "ÉTÉ 2024",
"nb_articles": 85,
"qte_vendue": 1700,
"ca_ttc": 28900.00,
"marge": 6450.00
}
]
}
Synchronisation
État de la sync
GET /api/sync/status
Réponse :
{
"sync": [
{
"table_name": "articles",
"last_sync": "2026-03-18T03:00:00.000Z",
"rows_synced": 42062,
"status": "ok",
"error_msg": null
}
]
}
Tables synchronisées :
| Table | Mode | Fréquence |
|---|---|---|
articles |
Delta (SUIVIDATEMODIF) | Nuit |
article_infosup |
Delta | Nuit |
art_gtin |
Upsert complet | Nuit |
artfou1 |
Delta | Nuit |
artfou2 |
Upsert complet | Nuit |
fouadr1 |
Refresh complet | Nuit |
cube_stock |
Refresh complet | Nuit |
cube_pa |
Refresh complet | Nuit |
cube_pv |
Refresh complet | Nuit |
mvtart |
Append (DatMvt >= hier) | Nuit |
nomenclature |
Refresh complet | Nuit |
gammes |
Refresh complet | Nuit |
art_gamme_saison |
Refresh complet | Nuit |
saisons |
Refresh complet | Nuit |
cdefou_vivant |
Refresh complet | Nuit |
cdefou_reception |
Delta | Nuit |
cdefou_receplig |
Delta | Nuit |
Exemples d'intégration JavaScript
const API_BASE = 'https://api.ffnancy.fr';
// Rechercher des articles
async function rechercherArticles(search, page = 1) {
const params = new URLSearchParams({ search, page, limit: 50 });
const res = await fetch(`${API_BASE}/api/articles?${params}`);
return res.json();
}
// Fiche complète d'un article
async function getFicheArticle(id) {
const res = await fetch(`${API_BASE}/api/articles/${id}/referentiel`);
return res.json();
}
// CA du mois en cours par jour
async function getCaMoisEnCours() {
const now = new Date();
const debut = new Date(now.getFullYear(), now.getMonth(), 1).toISOString().slice(0, 10);
const fin = new Date(now.getFullYear(), now.getMonth() + 1, 0).toISOString().slice(0, 10);
const params = new URLSearchParams({ dateDebut: debut, dateFin: fin, groupBy: 'jour' });
const res = await fetch(`${API_BASE}/api/performance/ca?${params}`);
return res.json();
}
// Top 10 articles par CA sur 30 jours
async function getTop10() {
const fin = new Date().toISOString().slice(0, 10);
const debut = new Date(Date.now() - 30 * 86400000).toISOString().slice(0, 10);
const params = new URLSearchParams({ dateDebut: debut, dateFin: fin, limit: 10, groupBy: 'ca' });
const res = await fetch(`${API_BASE}/api/performance/hitparade?${params}`);
return res.json();
}
// Stock d'un article
async function getStock(artNoId) {
const res = await fetch(`${API_BASE}/api/stock/article/${artNoId}`);
return res.json();
}
// Valorisation globale du stock
async function getValorisationStock() {
const res = await fetch(`${API_BASE}/api/stock/valorisation`);
return res.json();
}
// Commandes en cours d'un fournisseur
async function getCommandesFournisseur(codeFou) {
const res = await fetch(`${API_BASE}/api/fournisseurs/${codeFou}/commandes`);
return res.json();
}
Codes d'erreur HTTP
| Code | Description |
|---|---|
200 |
Succès |
404 |
Ressource introuvable (article, commande…) |
500 |
Erreur serveur (détail dans le champ error) |
Format erreur :
{ "error": "Article introuvable" }
Notes importantes
- Les données sont en lecture seule — aucun endpoint d'écriture
- Les données sont décalées d'au plus 24h (sync à 3h00)
- Les champs dates sont au format ISO 8601 UTC
- La pagination commence à
page=1 - Le champ
no_idest l'identifiant interne SQL Server de l'article - Les codes sites (
001,002…) correspondent aux différents points de vente