feat(articles): enhance article endpoints with async support and validation

This commit is contained in:
Fanilo-Nantenaina 2025-12-11 12:01:54 +03:00
parent 963118641b
commit e56159268f

201
api.py
View file

@ -1086,108 +1086,207 @@ async def rechercher_articles(query: Optional[str] = Query(None)):
logger.error(f"Erreur recherche articles: {e}") logger.error(f"Erreur recherche articles: {e}")
raise HTTPException(500, str(e)) raise HTTPException(500, str(e))
@app.post("/articles", status_code=status.HTTP_201_CREATED, tags=["Articles"]) @app.post(
def creer_article(article: ArticleCreateRequest): "/articles",
response_model=ArticleResponse,
status_code=status.HTTP_201_CREATED,
tags=["Articles"]
)
async def creer_article(article: ArticleCreateRequest):
""" """
Création d'un article dans Sage Création d'un nouvel article dans Sage
**Usage**: Créer un article avec stock pour éviter l'erreur 2881 **Usage typique:** Créer un article avec stock pour éviter l'erreur 2881
**Erreurs possibles**: **Champs obligatoires:**
- `reference` (max 18 caractères) : Référence unique de l'article
- `designation` (max 69 caractères) : Désignation de l'article
**Champs optionnels mais recommandés:**
- `stock_reel` : Stock initial (important pour éviter erreurs de transformation)
- `prix_vente` : Prix de vente HT
- `unite_vente` : Unité de vente (défaut: "UN")
**Erreurs possibles:**
- 400: Article existe déjà ou données invalides - 400: Article existe déjà ou données invalides
- 500: Erreur Sage - 500: Erreur Sage (problème de connexion, champs mal formatés, etc.)
**Exemple:**
```json
{
"reference": "ART001",
"designation": "Article de test",
"prix_vente": 10.50,
"stock_reel": 100.0,
"stock_mini": 10.0,
"unite_vente": "UN",
"tva_code": "C20"
}
```
""" """
try: try:
resultat = sage_client.creer_article(article.dict(exclude_unset=True)) # Validation des données
if not article.reference or not article.designation:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Les champs 'reference' et 'designation' sont obligatoires"
)
logger.info(f"✅ Article créé: {resultat.get('reference')}") # ⚠️ CORRECTION: Ne pas utiliser exclude_none=True car on veut garder
# les valeurs par défaut (comme unite_vente="UN")
article_data = article.dict(exclude_unset=True)
return { logger.info(f"📝 Création article: {article.reference} - {article.designation}")
"message": "Article créé avec succès",
"article": resultat # Appel à la gateway Windows
} resultat = sage_client.creer_article(article_data)
logger.info(f"✅ Article créé: {resultat.get('reference')} (stock: {resultat.get('stock_reel', 0)})")
return ArticleResponse(**resultat)
except ValueError as e: except ValueError as e:
logger.warning(f"Erreur métier création article: {e}") # Erreur métier (ex: article existe déjà)
raise HTTPException(status.HTTP_400_BAD_REQUEST, str(e)) logger.warning(f"⚠️ Erreur métier création article: {e}")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=str(e)
)
except HTTPException:
raise
except Exception as e: except Exception as e:
logger.error(f"Erreur création article: {e}") # Erreur technique Sage
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, str(e)) logger.error(f"❌ Erreur technique création article: {e}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Erreur lors de la création de l'article: {str(e)}"
)
@app.put("/articles/{reference}", tags=["Articles"]) @app.put("/articles/{reference}", response_model=ArticleResponse, tags=["Articles"])
def modifier_article(reference: str, article: ArticleUpdateRequest): async def modifier_article(
reference: str = Path(..., description="Référence de l'article à modifier"),
article: ArticleUpdateRequest = Body(...)
):
""" """
Modification d'un article dans Sage Modification complète d'un article existant
**Usage critique**: Augmenter le stock pour résoudre l'erreur 2881 **Usage critique:** Augmenter le stock pour résoudre l'erreur 2881
**Example** - Résoudre l'erreur "L'état du stock ne permet pas de créer la ligne": **Erreur 2881 - "L'état du stock ne permet pas de créer la ligne"**
```bash
curl -X PUT "http://api.example.com/api/articles/ART001" \
-H "Content-Type: application/json" \
-d '{"stock_reel": 100.0}'
```
**Erreurs possibles**: Cette erreur survient lors de la transformation de documents (devis commande facture)
lorsque le stock de l'article est insuffisant.
**Solution:** Augmenter le `stock_reel` de l'article
**Exemple - Résoudre l'erreur 2881:**
```json
{
"stock_reel": 100.0
}
```
**Autres modifications possibles:**
- Prix de vente/achat
- Stock minimum
- Code EAN
- Description
**Erreurs possibles:**
- 404: Article introuvable - 404: Article introuvable
- 400: Données invalides - 400: Aucun champ à modifier ou données invalides
- 500: Erreur Sage - 500: Erreur Sage
**Note:** Seuls les champs fournis seront modifiés, les autres restent inchangés
""" """
try: try:
# Filtrer les champs None # ✅ CORRECTION: Utiliser exclude_unset=True au lieu de exclude_none=True
# Cela permet de distinguer entre:
# - Champ non fourni (exclu)
# - Champ fourni avec valeur None (inclus pour reset)
article_data = article.dict(exclude_unset=True) article_data = article.dict(exclude_unset=True)
if not article_data: if not article_data:
raise HTTPException( raise HTTPException(
status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
"Aucun champ à modifier" detail="Aucun champ à modifier. Fournissez au moins un champ à mettre à jour."
) )
logger.info(f"📝 Modification article {reference}: {list(article_data.keys())}")
# Appel à la gateway Windows
resultat = sage_client.modifier_article(reference, article_data) resultat = sage_client.modifier_article(reference, article_data)
logger.info(f"✅ Article {reference} modifié: {list(article_data.keys())}") # Log spécial pour modification de stock (important pour erreur 2881)
if "stock_reel" in article_data:
logger.info(
f"📦 Stock {reference} modifié: {article_data['stock_reel']} "
f"(peut résoudre erreur 2881)"
)
return { logger.info(f"✅ Article {reference} modifié ({len(article_data)} champs)")
"message": f"Article {reference} modifié avec succès",
"article": resultat, return ArticleResponse(**resultat)
"champs_modifies": list(article_data.keys())
}
except ValueError as e: except ValueError as e:
logger.warning(f"Erreur métier modification article: {e}") # Erreur métier (ex: article introuvable)
raise HTTPException(status.HTTP_404_NOT_FOUND, str(e)) logger.warning(f"⚠️ Erreur métier modification article: {e}")
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=str(e)
)
except HTTPException:
raise
except Exception as e: except Exception as e:
logger.error(f"Erreur modification article: {e}") # Erreur technique Sage
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, str(e)) logger.error(f"❌ Erreur technique modification article: {e}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Erreur lors de la modification de l'article: {str(e)}"
)
@app.get("/articles/{reference}", tags=["Articles"]) @app.get("/articles/{reference}", response_model=ArticleResponse, tags=["Articles"])
def lire_article(reference: str): async def lire_article(reference: str = Path(..., description="Référence de l'article")):
""" """
📄 Lecture d'un article par référence 📄 Lecture d'un article spécifique par référence
Retourne toutes les informations incluant le stock actuel **Retourne:**
- Toutes les informations de l'article
- Stock actuel (réel, réservé, disponible)
- Prix de vente et d'achat
- Famille, fournisseur principal
- Caractéristiques physiques (poids, volume)
**Source:** Cache mémoire (instantané)
""" """
try: try:
article = sage_client.lire_article(reference) article = sage_client.lire_article(reference)
if not article: if not article:
logger.warning(f"⚠️ Article {reference} introuvable")
raise HTTPException( raise HTTPException(
status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
f"Article {reference} introuvable" detail=f"Article {reference} introuvable"
) )
return {"article": article} logger.info(f"✅ Article {reference} lu: {article.get('designation', '')}")
return ArticleResponse(**article)
except HTTPException: except HTTPException:
raise raise
except Exception as e: except Exception as e:
logger.error(f"Erreur lecture article: {e}") logger.error(f"❌ Erreur lecture article {reference}: {e}", exc_info=True)
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, str(e)) raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Erreur lors de la lecture de l'article: {str(e)}"
)
@app.get("/articles/all") @app.get("/articles/all")
def lister_articles(filtre: str = ""): def lister_articles(filtre: str = ""):