diff --git a/api.py b/api.py index cb8c80f..03f22e7 100644 --- a/api.py +++ b/api.py @@ -771,7 +771,32 @@ 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,7 +1086,127 @@ 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): """📝 Création de devis via gateway Windows""" diff --git a/sage_client.py b/sage_client.py index d03e009..9c1b2c4 100644 --- a/sage_client.py +++ b/sage_client.py @@ -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()