From 204b7920154594064cc6018e6e7dffa1726876be Mon Sep 17 00:00:00 2001 From: Fanilo-Nantenaina Date: Mon, 8 Dec 2025 08:47:48 +0300 Subject: [PATCH] Integrate create and update for livraison --- api.py | 254 +++++++++++++++++++++++++++++++++++++++++++++++++ sage_client.py | 17 ++++ 2 files changed, 271 insertions(+) diff --git a/api.py b/api.py index 6908139..33a79ad 100644 --- a/api.py +++ b/api.py @@ -347,6 +347,64 @@ class CommandeUpdateRequest(BaseModel): } +class LigneLivraison(BaseModel): + """Ligne de livraison""" + 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 LivraisonCreateRequest(BaseModel): + """Création d'une livraison""" + client_id: str + date_livraison: Optional[date] = None + lignes: List[LigneLivraison] + reference: Optional[str] = None + + class Config: + json_schema_extra = { + "example": { + "client_id": "CLI000001", + "date_livraison": "2024-01-15", + "reference": "BL-EXT-001", + "lignes": [ + { + "article_code": "ART001", + "quantite": 10.0, + "prix_unitaire_ht": 50.0, + "remise_pourcentage": 5.0 + } + ] + } + } + + +class LivraisonUpdateRequest(BaseModel): + """Modification d'une livraison existante""" + date_livraison: Optional[date] = None + lignes: Optional[List[LigneLivraison]] = 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": 15.0, + "prix_unitaire_ht": 45.0 + } + ], + "statut": 2 + } + } + # ===================================================== # SERVICES EXTERNES (Universign) # ===================================================== @@ -2448,6 +2506,202 @@ async def lire_livraison(numero: str): logger.error(f"Erreur lecture livraison: {e}") raise HTTPException(500, str(e)) +@app.post("/livraisons", status_code=201, tags=["Livraisons"]) +async def creer_livraison( + livraison: LivraisonCreateRequest, + session: AsyncSession = Depends(get_session) +): + """ + ➕ Création d'une nouvelle livraison (Bon de livraison) + + **Workflow typique:** + 1. Création d'une commande → transformation en livraison (automatique) + 2. OU création directe d'une livraison (cette route) + + **Champs obligatoires:** + - `client_id`: Code du client + - `lignes`: Liste des lignes (min 1) + + **Champs optionnels:** + - `date_livraison`: Date de la livraison (par défaut: aujourd'hui) + - `reference`: Référence externe (ex: numéro de commande client) + """ + try: + # Vérifier que le client existe + client = sage_client.lire_client(livraison.client_id) + if not client: + raise HTTPException(404, f"Client {livraison.client_id} introuvable") + + # Préparer les données pour la gateway + livraison_data = { + "client_id": livraison.client_id, + "date_livraison": ( + livraison.date_livraison.isoformat() + if livraison.date_livraison + else None + ), + "reference": livraison.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 livraison.lignes + ], + } + + # Appel à la gateway Windows + resultat = sage_client.creer_livraison(livraison_data) + + logger.info(f"✅ Livraison créée: {resultat.get('numero_livraison')}") + + return { + "success": True, + "message": "Livraison créée avec succès", + "data": { + "numero_livraison": resultat["numero_livraison"], + "client_id": livraison.client_id, + "date_livraison": resultat["date_livraison"], + "total_ht": resultat["total_ht"], + "total_ttc": resultat["total_ttc"], + "nb_lignes": resultat["nb_lignes"], + "reference": livraison.reference + } + } + + except HTTPException: + raise + except Exception as e: + logger.error(f"Erreur création livraison: {e}") + raise HTTPException(500, str(e)) + + +@app.put("/livraisons/{id}", tags=["Livraisons"]) +async def modifier_livraison( + id: str, + livraison_update: LivraisonUpdateRequest, + session: AsyncSession = Depends(get_session) +): + """ + ✏️ Modification d'une livraison existante + + **Champs modifiables:** + - `date_livraison`: Nouvelle date + - `lignes`: Nouvelles lignes (remplace toutes les lignes existantes) + - `statut`: Nouveau statut + - `reference`: Référence externe + + **Restrictions:** + - Une livraison transformée (statut=5) ne peut plus être modifiée + - Une livraison annulée (statut=6) ne peut plus être modifiée + """ + try: + # Vérifier que la livraison existe + livraison_existante = sage_client.lire_livraison(id) + + if not livraison_existante: + raise HTTPException(404, f"Livraison {id} introuvable") + + # Vérifier le statut + statut_actuel = livraison_existante.get("statut", 0) + + if statut_actuel == 5: + raise HTTPException( + 400, + f"La livraison {id} a déjà été transformée et ne peut plus être modifiée" + ) + + if statut_actuel == 6: + raise HTTPException( + 400, + f"La livraison {id} est annulée et ne peut plus être modifiée" + ) + + # Construire les données de mise à jour + update_data = {} + + if livraison_update.date_livraison: + update_data["date_livraison"] = livraison_update.date_livraison.isoformat() + + if livraison_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 livraison_update.lignes + ] + + if livraison_update.statut is not None: + update_data["statut"] = livraison_update.statut + + if livraison_update.reference is not None: + update_data["reference"] = livraison_update.reference + + # Appel à la gateway Windows + resultat = sage_client.modifier_livraison(id, update_data) + + logger.info(f"✅ Livraison {id} modifiée avec succès") + + return { + "success": True, + "message": f"Livraison {id} modifiée avec succès", + "livraison": resultat + } + + except HTTPException: + raise + except Exception as e: + logger.error(f"Erreur modification livraison {id}: {e}") + raise HTTPException(500, str(e)) + + +@app.post("/workflow/livraison/{id}/to-facture", tags=["US-A2"]) +async def livraison_vers_facture(id: str, session: AsyncSession = Depends(get_session)): + """ + 🔧 Transformation Livraison → Facture + ✅ Utilise les VRAIS types Sage (30 → 60) + """ + try: + resultat = sage_client.transformer_document( + numero_source=id, + type_source=settings.SAGE_TYPE_BON_LIVRAISON, # = 30 + type_cible=settings.SAGE_TYPE_FACTURE, # = 60 + ) + + workflow_log = WorkflowLog( + id=str(uuid.uuid4()), + document_source=id, + type_source=TypeDocument.BON_LIVRAISON, + document_cible=resultat.get("document_cible", ""), + type_cible=TypeDocument.FACTURE, + nb_lignes=resultat.get("nb_lignes", 0), + date_transformation=datetime.now(), + succes=True, + ) + + session.add(workflow_log) + await session.commit() + + logger.info( + f"✅ Transformation: Livraison {id} → Facture {resultat['document_cible']}" + ) + + return { + "success": True, + "document_source": id, + "document_cible": resultat["document_cible"], + "nb_lignes": resultat["nb_lignes"], + } + + except Exception as e: + logger.error(f"Erreur transformation: {e}") + raise HTTPException(500, str(e)) + @app.get("/debug/users", response_model=List[UserResponse], tags=["Debug"]) async def lister_utilisateurs_debug( diff --git a/sage_client.py b/sage_client.py index ed51bf0..47944e8 100644 --- a/sage_client.py +++ b/sage_client.py @@ -443,6 +443,23 @@ class SageGatewayClient: "numero": numero, "commande_data": commande_data }).get("data", {}) + + + def creer_livraison(self, livraison_data: Dict) -> Dict: + """ + ➕ Création d'une nouvelle livraison (Bon de livraison) + """ + return self._post("/sage/livraisons/create", livraison_data).get("data", {}) + + + def modifier_livraison(self, numero: str, livraison_data: Dict) -> Dict: + """ + ✏️ Modification d'une livraison existante + """ + return self._post("/sage/livraisons/update", { + "numero": numero, + "livraison_data": livraison_data + }).get("data", {})