From c15ae79c6a956f85fb3962210207eaa25d28b3d1 Mon Sep 17 00:00:00 2001 From: Fanilo-Nantenaina Date: Mon, 8 Dec 2025 09:18:15 +0300 Subject: [PATCH] Added create and update for avoir --- api.py | 229 +++++++++++++++++++++++++++++++++++++++++++++++++ sage_client.py | 41 +++++++++ 2 files changed, 270 insertions(+) diff --git a/api.py b/api.py index 33a79ad..2b944c6 100644 --- a/api.py +++ b/api.py @@ -405,6 +405,64 @@ class LivraisonUpdateRequest(BaseModel): } } +class LigneAvoir(BaseModel): + """Ligne d'avoir""" + article_code: str + quantite: float + prix_unitaire_ht: Optional[float] = None + remise_pourcentage: Optional[float] = 0.0 + + @field_validator("article_code", mode="before") + def strip_insecables(cls, v): + return v.replace("\xa0", "").strip() + + +class AvoirCreateRequest(BaseModel): + """Création d'un avoir""" + client_id: str + date_avoir: Optional[date] = None + lignes: List[LigneAvoir] + reference: Optional[str] = None + + class Config: + json_schema_extra = { + "example": { + "client_id": "CLI000001", + "date_avoir": "2024-01-15", + "reference": "AV-EXT-001", + "lignes": [ + { + "article_code": "ART001", + "quantite": 5.0, + "prix_unitaire_ht": 50.0, + "remise_pourcentage": 0.0 + } + ] + } + } + + +class AvoirUpdateRequest(BaseModel): + """Modification d'un avoir existant""" + date_avoir: Optional[date] = None + lignes: Optional[List[LigneAvoir]] = None + statut: Optional[int] = Field(None, ge=0, le=6) + reference: Optional[str] = None + + class Config: + json_schema_extra = { + "example": { + "lignes": [ + { + "article_code": "ART001", + "quantite": 10.0, + "prix_unitaire_ht": 45.0 + } + ], + "statut": 2 + } + } + # ===================================================== # SERVICES EXTERNES (Universign) # ===================================================== @@ -2475,7 +2533,178 @@ async def lire_avoir(numero: str): logger.error(f"Erreur lecture avoir: {e}") raise HTTPException(500, str(e)) +@app.post("/avoirs", status_code=201, tags=["Avoirs"]) +async def creer_avoir( + avoir: AvoirCreateRequest, + session: AsyncSession = Depends(get_session) +): + """ + ➕ Création d'un avoir (Bon d'avoir) + + **Workflow typique:** + 1. Retour marchandise → création d'un avoir + 2. Geste commercial → création directe d'un avoir (cette route) + + **Champs obligatoires:** + - `client_id`: Code du client + - `lignes`: Liste des lignes (min 1) + + **Champs optionnels:** + - `date_avoir`: Date de l'avoir (par défaut: aujourd'hui) + - `reference`: Référence externe (ex: numéro de retour) + + **Note:** Les montants des avoirs sont généralement négatifs (crédits) + + Args: + avoir: Données de l'avoir à créer + + Returns: + Avoir créé avec son numéro et ses totaux + """ + try: + # Vérifier que le client existe + client = sage_client.lire_client(avoir.client_id) + if not client: + raise HTTPException(404, f"Client {avoir.client_id} introuvable") + + # Préparer les données pour la gateway + avoir_data = { + "client_id": avoir.client_id, + "date_avoir": ( + avoir.date_avoir.isoformat() + if avoir.date_avoir + else None + ), + "reference": avoir.reference, + "lignes": [ + { + "article_code": l.article_code, + "quantite": l.quantite, + "prix_unitaire_ht": l.prix_unitaire_ht, + "remise_pourcentage": l.remise_pourcentage, + } + for l in avoir.lignes + ], + } + + # Appel à la gateway Windows + resultat = sage_client.creer_avoir(avoir_data) + + logger.info(f"✅ Avoir créé: {resultat.get('numero_avoir')}") + + return { + "success": True, + "message": "Avoir créé avec succès", + "data": { + "numero_avoir": resultat["numero_avoir"], + "client_id": avoir.client_id, + "date_avoir": resultat["date_avoir"], + "total_ht": resultat["total_ht"], + "total_ttc": resultat["total_ttc"], + "nb_lignes": resultat["nb_lignes"], + "reference": avoir.reference + } + } + + except HTTPException: + raise + except Exception as e: + logger.error(f"Erreur création avoir: {e}") + raise HTTPException(500, str(e)) + +@app.put("/avoirs/{id}", tags=["Avoirs"]) +async def modifier_avoir( + id: str, + avoir_update: AvoirUpdateRequest, + session: AsyncSession = Depends(get_session) +): + """ + ✏️ Modification d'un avoir existant + + **Champs modifiables:** + - `date_avoir`: Nouvelle date + - `lignes`: Nouvelles lignes (remplace toutes les lignes existantes) + - `statut`: Nouveau statut + - `reference`: Référence externe + + **Restrictions:** + - Un avoir transformé (statut=5) ne peut plus être modifié + - Un avoir annulé (statut=6) ne peut plus être modifié + + **Note importante:** + Si `lignes` est fourni, TOUTES les lignes existantes seront remplacées + + Args: + id: Numéro de l'avoir à modifier + avoir_update: Champs à mettre à jour + + Returns: + Avoir modifié avec ses nouvelles valeurs + """ + try: + # Vérifier que l'avoir existe + avoir_existant = sage_client.lire_avoir(id) + + if not avoir_existant: + raise HTTPException(404, f"Avoir {id} introuvable") + + # Vérifier le statut + statut_actuel = avoir_existant.get("statut", 0) + + if statut_actuel == 5: + raise HTTPException( + 400, + f"L'avoir {id} a déjà été transformé et ne peut plus être modifié" + ) + + if statut_actuel == 6: + raise HTTPException( + 400, + f"L'avoir {id} est annulé et ne peut plus être modifié" + ) + + # Construire les données de mise à jour + update_data = {} + + if avoir_update.date_avoir: + update_data["date_avoir"] = avoir_update.date_avoir.isoformat() + + if avoir_update.lignes is not None: + update_data["lignes"] = [ + { + "article_code": l.article_code, + "quantite": l.quantite, + "prix_unitaire_ht": l.prix_unitaire_ht, + "remise_pourcentage": l.remise_pourcentage, + } + for l in avoir_update.lignes + ] + + if avoir_update.statut is not None: + update_data["statut"] = avoir_update.statut + + if avoir_update.reference is not None: + update_data["reference"] = avoir_update.reference + + # Appel à la gateway Windows + resultat = sage_client.modifier_avoir(id, update_data) + + logger.info(f"✅ Avoir {id} modifié avec succès") + + return { + "success": True, + "message": f"Avoir {id} modifié avec succès", + "avoir": resultat + } + + except HTTPException: + raise + except Exception as e: + logger.error(f"Erreur modification avoir {id}: {e}") + raise HTTPException(500, str(e)) + + # ===================================================== # ENDPOINTS - LIVRAISONS # ===================================================== diff --git a/sage_client.py b/sage_client.py index 47944e8..6a07591 100644 --- a/sage_client.py +++ b/sage_client.py @@ -460,6 +460,47 @@ class SageGatewayClient: "numero": numero, "livraison_data": livraison_data }).get("data", {}) + + def creer_avoir(self, avoir_data: Dict) -> Dict: + """ + ➕ Création d'un avoir (Bon d'avoir) + + Args: + avoir_data: Dictionnaire contenant: + - client_id (str): Code du client + - date_avoir (str, optional): Date au format ISO + - reference (str, optional): Référence externe + - lignes (List[Dict]): Liste des lignes avec: + - article_code (str) + - quantite (float) + - prix_unitaire_ht (float, optional) + - remise_pourcentage (float, optional) + + Returns: + Avoir créé avec son numéro et ses totaux + """ + return self._post("/sage/avoirs/create", avoir_data).get("data", {}) + + + def modifier_avoir(self, numero: str, avoir_data: Dict) -> Dict: + """ + ✏️ Modification d'un avoir existant + + Args: + numero: Numéro de l'avoir à modifier + avoir_data: Dictionnaire contenant les champs à modifier: + - date_avoir (str, optional): Nouvelle date + - lignes (List, optional): Nouvelles lignes + - statut (int, optional): Nouveau statut + - reference (str, optional): Nouvelle référence + + Returns: + Avoir modifié avec totaux recalculés + """ + return self._post("/sage/avoirs/update", { + "numero": numero, + "avoir_data": avoir_data + }).get("data", {})