From 18699a86736f6ce79746c02a12462f01b628d922 Mon Sep 17 00:00:00 2001 From: Fanilo-Nantenaina Date: Sun, 28 Dec 2025 21:20:15 +0300 Subject: [PATCH] Added contact handling --- api.py | 167 +++++++++++++++++++++++++++++++++++++++++++++++-- sage_client.py | 58 +++++++++++++++++ 2 files changed, 220 insertions(+), 5 deletions(-) diff --git a/api.py b/api.py index 764cc97..f04ccd2 100644 --- a/api.py +++ b/api.py @@ -130,8 +130,8 @@ class Contact(BaseModel): Tous les champs de F_CONTACTT """ - ct_num: Optional[str] = Field(None, description="Code du tiers parent (CT_Num)") - ct_no: Optional[int] = Field(None, description="Numéro unique du contact (CT_No)") + numero: Optional[str] = Field(None, description="Code du tiers parent (CT_Num)") + contact_numero: Optional[int] = Field(None, description="Numéro unique du contact (CT_No)") n_contact: Optional[int] = Field(None, description="Numéro de référence contact (N_Contact)") civilite: Optional[str] = Field(None, description="Civilité : M., Mme, Mlle (CT_Civilite)") @@ -150,6 +150,11 @@ class Contact(BaseModel): linkedin: Optional[str] = Field(None, description="Profil LinkedIn (CT_LinkedIn)") skype: Optional[str] = Field(None, description="Identifiant Skype (CT_Skype)") + est_defaut: Optional[bool] = Field( + False, + description="True si ce contact est le contact par défaut du client" + ) + civilite_map: ClassVar[dict] = { 0: "M.", 1: "Mme", @@ -168,8 +173,8 @@ class Contact(BaseModel): class Config: json_schema_extra = { "example": { - "ct_num": "CLI000001", - "ct_no": 1, + "numero": "CLI000001", + "contact_numero": 1, "n_contact": 1, "civilite": "M.", "nom": "Dupont", @@ -2578,6 +2583,67 @@ class MouvementStockResponse(BaseModel): nb_lignes: int = Field(..., description="Nombre de lignes") +class ContactCreate(BaseModel): + """Données pour créer ou modifier un contact""" + numero: str = Field(..., description="Code du client parent (obligatoire)") + + civilite: Optional[str] = Field(None, description="M., Mme, Mlle, Société") + nom: str = Field(..., description="Nom de famille (obligatoire)") + prenom: Optional[str] = Field(None, description="Prénom") + fonction: Optional[str] = Field(None, description="Fonction/Titre") + + est_defaut: Optional[bool] = Field(False, description="Définir comme contact par défaut du client") + + service_code: Optional[int] = Field(None, description="Code du service") + + telephone: Optional[str] = Field(None, description="Téléphone fixe") + portable: Optional[str] = Field(None, description="Téléphone mobile") + telecopie: Optional[str] = Field(None, description="Fax") + email: Optional[str] = Field(None, description="Email") + + facebook: Optional[str] = Field(None, description="URL Facebook") + linkedin: Optional[str] = Field(None, description="URL LinkedIn") + skype: Optional[str] = Field(None, description="Identifiant Skype") + + @validator("civilite") + def validate_civilite(cls, v): + if v and v not in ["M.", "Mme", "Mlle", "Société"]: + raise ValueError("Civilité doit être: M., Mme, Mlle ou Société") + return v + + class Config: + json_schema_extra = { + "example": { + "numero": "CLI000001", + "civilite": "M.", + "nom": "Dupont", + "prenom": "Jean", + "fonction": "Directeur Commercial", + "telephone": "0123456789", + "portable": "0612345678", + "email": "j.dupont@exemple.fr", + "linkedin": "https://linkedin.com/in/jeandupont", + "est_defaut": True + } + } + + +class ContactUpdate(BaseModel): + """Données pour modifier un contact (tous champs optionnels)""" + civilite: Optional[str] = None + nom: Optional[str] = None + prenom: Optional[str] = None + fonction: Optional[str] = None + service_code: Optional[int] = None + telephone: Optional[str] = None + portable: Optional[str] = None + telecopie: Optional[str] = None + email: Optional[str] = None + facebook: Optional[str] = None + linkedin: Optional[str] = None + skype: Optional[str] = None + est_defaut: Optional[bool] = None + templates_signature_email = { "demande_signature": { "id": "demande_signature", @@ -3218,7 +3284,7 @@ app.include_router(auth_router) @app.get("/clients", response_model=List[ClientDetails], tags=["Clients"]) -async def rechercher_clients(query: Optional[str] = Query(None)): +async def obtenir_clients(query: Optional[str] = Query(None)): try: clients = sage_client.lister_clients(filtre=query or "") return [ClientDetails(**c) for c in clients] @@ -5926,6 +5992,97 @@ async def get_document_pdf( +@app.post("/clients/{numero}/contacts", response_model=Contact, tags=["Contacts"]) +async def creer_contact(numero: str, contact: ContactCreate): + try: + try: + sage_client.obtenir_client(numero) + except: + raise HTTPException(404, f"Client {numero} non trouvé") + + if contact.numero != numero: + contact.numero = numero + + resultat = sage_client.creer_contact(contact.dict()) + return Contact(**resultat) + + except HTTPException: + raise + except Exception as e: + logger.error(f"Erreur création contact: {e}") + raise HTTPException(500, str(e)) + + +@app.get("/clients/{numero}/contacts", response_model=List[Contact], tags=["Contacts"]) +async def lister_contacts_client(numero: str): + try: + contacts = sage_client.lister_contacts(numero) + return [Contact(**c) for c in contacts] + except Exception as e: + logger.error(f"Erreur liste contacts: {e}") + raise HTTPException(500, str(e)) + + +@app.get("/clients/{numero}/contacts/{contact_numero}", response_model=Contact, tags=["Contacts"]) +async def obtenir_contact(numero: str, contact_numero: int): + try: + contact = sage_client.obtenir_contact(numero, contact_numero) + if not contact: + raise HTTPException(404, f"Contact {contact_numero} non trouvé pour client {numero}") + return Contact(**contact) + except HTTPException: + raise + except Exception as e: + logger.error(f"Erreur récupération contact: {e}") + raise HTTPException(500, str(e)) + + +@app.put("/clients/{numero}/contacts/{contact_numero}", response_model=Contact, tags=["Contacts"]) +async def modifier_contact(numero: str, contact_numero: int, contact: ContactUpdate): + try: + contact_existant = sage_client.obtenir_contact(numero, contact_numero) + if not contact_existant: + raise HTTPException(404, f"Contact {contact_numero} non trouvé") + + updates = {k: v for k, v in contact.dict().items() if v is not None} + + if not updates: + raise HTTPException(400, "Aucune modification fournie") + + resultat = sage_client.modifier_contact(numero, contact_numero, updates) + return Contact(**resultat) + + except HTTPException: + raise + except Exception as e: + logger.error(f"Erreur modification contact: {e}") + raise HTTPException(500, str(e)) + + +@app.delete("/clients/{numero}/contacts/{contact_numero}", tags=["Contacts"]) +async def supprimer_contact(numero: str, contact_numero: int): + try: + resultat = sage_client.supprimer_contact(numero, contact_numero) + return {"success": True, "message": f"Contact {contact_numero} supprimé"} + except Exception as e: + logger.error(f"Erreur suppression contact: {e}") + raise HTTPException(500, str(e)) + + +@app.post("/clients/{numero}/contacts/{contact_numero}/definir-defaut", tags=["Contacts"]) +async def definir_contact_defaut(numero: str, contact_numero: int): + try: + resultat = sage_client.definir_contact_defaut(numero, contact_numero) + return { + "success": True, + "message": f"Contact {contact_numero} défini comme contact par défaut", + "data": resultat + } + except Exception as e: + logger.error(f"Erreur définition contact par défaut: {e}") + raise HTTPException(500, str(e)) + + if __name__ == "__main__": uvicorn.run( "api:app", diff --git a/sage_client.py b/sage_client.py index 631669d..aba8338 100644 --- a/sage_client.py +++ b/sage_client.py @@ -450,4 +450,62 @@ class SageGatewayClient: raise + + def creer_contact(self, contact_data: Dict) -> Dict: + return self._post("/sage/contacts/create", contact_data) + + + def lister_contacts(self, numero: str) -> List[Dict]: + return self._post("/sage/contacts/list", {"numero": numero}).get("data", []) + + + def obtenir_contact(self, numero: str, contact_numero: int) -> Dict: + result = self._post("/sage/contacts/get", { + "numero": numero, + "contact_numero": contact_numero + }) + return result.get("data") if result.get("success") else None + + + def modifier_contact(self, numero: str, contact_numero: int, updates: Dict) -> Dict: + return self._post("/sage/contacts/update", { + "numero": numero, + "contact_numero": contact_numero, + "updates": updates + }) + + + def supprimer_contact(self, numero: str, contact_numero: int) -> Dict: + """ + Supprime un contact + + Args: + numero: Code du client + contact_numero: Numéro unique du contact + + Returns: + Dictionnaire avec le statut de la suppression + """ + return self._post("/sage/contacts/delete", { + "numero": numero, + "contact_numero": contact_numero + }) + + + def definir_contact_defaut(self, numero: str, contact_numero: int) -> Dict: + """ + Définit un contact comme contact par défaut du client + + Args: + numero: Code du client + contact_numero: Numéro unique du contact à définir comme par défaut + + Returns: + Dictionnaire avec les données du client mis à jour + """ + return self._post("/sage/contacts/set-default", { + "numero": numero, + "contact_numero": contact_numero + }) + sage_client = SageGatewayClient()