feat(articles): add CRUD operations for articles management

This commit is contained in:
Fanilo-Nantenaina 2025-12-10 17:01:49 +03:00
parent 44354ec9bd
commit 428093306a
2 changed files with 198 additions and 0 deletions

145
api.py
View file

@ -771,6 +771,31 @@ class FactureUpdateRequest(BaseModel):
}
}
class ArticleCreateRequest(BaseModel):
"""Schéma pour création d'article"""
reference: str = Field(..., max_length=18, description="Référence article")
designation: str = Field(..., max_length=69, description="Désignation")
famille: Optional[str] = Field(None, max_length=18, description="Code famille")
prix_vente: Optional[float] = Field(None, ge=0, description="Prix vente HT")
prix_achat: Optional[float] = Field(None, ge=0, description="Prix achat HT")
stock_reel: Optional[float] = Field(None, ge=0, description="Stock initial")
stock_mini: Optional[float] = Field(None, ge=0, description="Stock minimum")
code_ean: Optional[str] = Field(None, max_length=13, description="Code-barres")
unite_vente: Optional[str] = Field("UN", max_length=4, description="Unité")
tva_code: Optional[str] = Field(None, max_length=5, description="Code TVA")
description: Optional[str] = Field(None, description="Description")
class ArticleUpdateRequest(BaseModel):
"""Schéma pour modification d'article"""
designation: Optional[str] = Field(None, max_length=69)
prix_vente: Optional[float] = Field(None, ge=0)
prix_achat: Optional[float] = Field(None, ge=0)
stock_reel: Optional[float] = Field(None, ge=0, description="⚠️ Critique pour erreur 2881")
stock_mini: Optional[float] = Field(None, ge=0)
code_ean: Optional[str] = Field(None, max_length=13)
description: Optional[str] = Field(None)
# =====================================================
# SERVICES EXTERNES (Universign)
@ -1061,6 +1086,126 @@ async def rechercher_articles(query: Optional[str] = Query(None)):
logger.error(f"Erreur recherche articles: {e}")
raise HTTPException(500, str(e))
@router.post("/articles", status_code=status.HTTP_201_CREATED, tags=["Articles"])
def creer_article(article: ArticleCreateDTO):
"""
Création d'un article dans Sage
**Usage**: Créer un article avec stock pour éviter l'erreur 2881
**Erreurs possibles**:
- 400: Article existe déjà ou données invalides
- 500: Erreur Sage
"""
try:
resultat = sage_client.creer_article(article.dict(exclude_none=True))
logger.info(f"✅ Article créé: {resultat.get('reference')}")
return {
"message": "Article créé avec succès",
"article": resultat
}
except ValueError as e:
logger.warning(f"Erreur métier création article: {e}")
raise HTTPException(status.HTTP_400_BAD_REQUEST, str(e))
except Exception as e:
logger.error(f"Erreur création article: {e}")
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, str(e))
@router.put("/articles/{reference}", tags=["Articles"])
def modifier_article(reference: str, article: ArticleUpdateDTO):
"""
Modification d'un article dans Sage
**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":
```bash
curl -X PUT "http://api.example.com/api/articles/ART001" \
-H "Content-Type: application/json" \
-d '{"stock_reel": 100.0}'
```
**Erreurs possibles**:
- 404: Article introuvable
- 400: Données invalides
- 500: Erreur Sage
"""
try:
# Filtrer les champs None
article_data = article.dict(exclude_none=True)
if not article_data:
raise HTTPException(
status.HTTP_400_BAD_REQUEST,
"Aucun champ à modifier"
)
resultat = sage_client.modifier_article(reference, article_data)
logger.info(f"✅ Article {reference} modifié: {list(article_data.keys())}")
return {
"message": f"Article {reference} modifié avec succès",
"article": resultat,
"champs_modifies": list(article_data.keys())
}
except ValueError as e:
logger.warning(f"Erreur métier modification article: {e}")
raise HTTPException(status.HTTP_404_NOT_FOUND, str(e))
except Exception as e:
logger.error(f"Erreur modification article: {e}")
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, str(e))
@router.get("/articles/{reference}", tags=["Articles"])
def lire_article(reference: str):
"""
📄 Lecture d'un article par référence
Retourne toutes les informations incluant le stock actuel
"""
try:
article = sage_client.lire_article(reference)
if not article:
raise HTTPException(
status.HTTP_404_NOT_FOUND,
f"Article {reference} introuvable"
)
return {"article": article}
except HTTPException:
raise
except Exception as e:
logger.error(f"Erreur lecture article: {e}")
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, str(e))
@router.get("/articles/all")
def lister_articles(filtre: str = ""):
"""
📋 Liste tous les articles avec filtre optionnel
"""
try:
articles = sage_client.lister_articles(filtre)
return {
"articles": articles,
"total": len(articles)
}
except Exception as e:
logger.error(f"Erreur liste articles: {e}")
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, str(e))
@app.post("/devis", response_model=DevisResponse, status_code=201, tags=["Devis"])
async def creer_devis(devis: DevisRequest):

View file

@ -611,5 +611,58 @@ class SageGatewayClient:
raise
def creer_article(self, article_data: Dict) -> Dict:
"""
Création d'un article
Args:
article_data: Dictionnaire contenant:
- reference (str, obligatoire): Référence article
- designation (str, obligatoire): Désignation
- prix_vente (float, optionnel): Prix vente HT
- stock_reel (float, optionnel): Stock initial
- ... (voir ArticleCreateRequest dans main.py)
Returns:
Article créé
Example:
>>> article = sage_client.creer_article({
... "reference": "ART001",
... "designation": "Article test",
... "prix_vente": 10.0,
... "stock_reel": 100.0
... })
"""
return self._post("/sage/articles/create", article_data).get("data", {})
def modifier_article(self, reference: str, article_data: Dict) -> Dict:
"""
Modification d'un article
**Usage critique**: Augmenter le stock pour résoudre l'erreur 2881
Args:
reference: Référence de l'article à modifier
article_data: Dictionnaire contenant les champs à modifier:
- stock_reel (float, optionnel): Nouveau stock
- prix_vente (float, optionnel): Nouveau prix
- ... (seuls les champs présents seront mis à jour)
Returns:
Article modifié
Example - Résoudre erreur de stock:
>>> # L'erreur 2881 indique un stock insuffisant
>>> sage_client.modifier_article("ART001", {
... "stock_reel": 100.0 # Augmenter le stock
... })
"""
return self._post(
"/sage/articles/update",
{"reference": reference, "article_data": article_data}
).get("data", {})
# Instance globale
sage_client = SageGatewayClient()