From c97db9b058d2d1d4b22ab045e1dce544945dcdea Mon Sep 17 00:00:00 2001 From: Fanilo-Nantenaina Date: Mon, 5 Jan 2026 18:19:11 +0300 Subject: [PATCH] Added collaborator --- api.py | 94 +++++++++++++++++++++++++++++ sage_client.py | 23 +++++++ schemas/tiers/__init__.py | 0 schemas/tiers/commercial.py | 116 ++++++++++++++++++++++++++++++++++++ 4 files changed, 233 insertions(+) create mode 100644 schemas/tiers/__init__.py create mode 100644 schemas/tiers/commercial.py diff --git a/api.py b/api.py index 9e62053..4328593 100644 --- a/api.py +++ b/api.py @@ -71,6 +71,12 @@ from schemas import ( ContactCreate, ContactUpdate, ) +from schemas.tiers.commercial import ( + CollaborateurCreate, + CollaborateurDetails, + CollaborateurListe, + CollaborateurUpdate, +) from utils.normalization import normaliser_type_tiers from routes.sage_gateway import router as sage_gateway_router from core.sage_context import ( @@ -3141,6 +3147,94 @@ async def get_current_sage_config( } +# Routes Collaborateurs +@app.get( + "/collaborateurs", response_model=List[CollaborateurListe], tags=["Collaborateurs"] +) +async def lister_collaborateurs( + filtre: Optional[str] = Query(None, description="Filtre sur nom/prénom"), + actifs_seulement: bool = Query( + True, description="Exclure les collaborateurs en sommeil" + ), +): + """Liste tous les collaborateurs""" + try: + collaborateurs = sage_client.lister_collaborateurs(filtre, actifs_seulement) + return [CollaborateurListe(**c) for c in collaborateurs] + except Exception as e: + logger.error(f"Erreur liste collaborateurs: {e}") + raise HTTPException(500, str(e)) + + +@app.get( + "/collaborateurs/{numero}", + response_model=CollaborateurDetails, + tags=["Collaborateurs"], +) +async def lire_collaborateur_detail(numero: int): + """Lit un collaborateur par son numéro""" + try: + collaborateur = sage_client.lire_collaborateur(numero) + + if not collaborateur: + raise HTTPException(404, f"Collaborateur {numero} introuvable") + + return CollaborateurDetails(**collaborateur) + + except HTTPException: + raise + except Exception as e: + logger.error(f"Erreur lecture collaborateur {numero}: {e}") + raise HTTPException(500, str(e)) + + +@app.post( + "/collaborateurs", + response_model=CollaborateurDetails, + tags=["Collaborateurs"], + status_code=201, +) +async def creer_collaborateur(collaborateur: CollaborateurCreate): + """Crée un nouveau collaborateur""" + try: + nouveau = sage_client.creer_collaborateur(collaborateur.model_dump()) + + if not nouveau: + raise HTTPException(500, "Échec création collaborateur") + + return CollaborateurDetails(**nouveau) + + except HTTPException: + raise + except Exception as e: + logger.error(f"Erreur création collaborateur: {e}") + raise HTTPException(500, str(e)) + + +@app.put( + "/collaborateurs/{numero}", + response_model=CollaborateurDetails, + tags=["Collaborateurs"], +) +async def modifier_collaborateur(numero: int, collaborateur: CollaborateurUpdate): + """Modifie un collaborateur existant""" + try: + modifie = sage_client.modifier_collaborateur( + numero, collaborateur.model_dump(exclude_unset=True) + ) + + if not modifie: + raise HTTPException(404, f"Collaborateur {numero} introuvable") + + return CollaborateurDetails(**modifie) + + except HTTPException: + raise + except Exception as e: + logger.error(f"Erreur modification collaborateur {numero}: {e}") + raise HTTPException(500, str(e)) + + @app.get("/health", tags=["System"]) async def health_check( sage: SageGatewayClient = Depends(get_sage_client_for_user), diff --git a/sage_client.py b/sage_client.py index b0fb6e9..8f47efe 100644 --- a/sage_client.py +++ b/sage_client.py @@ -401,6 +401,29 @@ class SageGatewayClient: result = self._post("/sage/client/remise-max", {"code": code_client}) return result.get("data", {}).get("remise_max", 10.0) + def lister_collaborateurs( + self, filtre: str = "", actifs_seulement: bool = True + ) -> List[Dict]: + """Liste tous les collaborateurs""" + return self._post( + "/sage/collaborateurs/list", + {"filtre": filtre, "actifs_seulement": actifs_seulement}, + ).get("data", []) + + def lire_collaborateur(self, numero: int) -> Optional[Dict]: + """Lit un collaborateur par numéro""" + return self._post("/sage/collaborateurs/get", {"numero": numero}).get("data") + + def creer_collaborateur(self, data: Dict) -> Optional[Dict]: + """Crée un nouveau collaborateur""" + return self._post("/sage/collaborateurs/create", data).get("data") + + def modifier_collaborateur(self, numero: int, data: Dict) -> Optional[Dict]: + """Modifie un collaborateur existant""" + return self._post( + "/sage/collaborateurs/update", {"numero": numero, **data} + ).get("data") + def refresh_cache(self) -> Dict: return self._post("/sage/cache/refresh") diff --git a/schemas/tiers/__init__.py b/schemas/tiers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/schemas/tiers/commercial.py b/schemas/tiers/commercial.py new file mode 100644 index 0000000..5a4685b --- /dev/null +++ b/schemas/tiers/commercial.py @@ -0,0 +1,116 @@ +from pydantic import BaseModel, EmailStr, Field +from typing import Optional + + +class CollaborateurBase(BaseModel): + """Champs communs collaborateur""" + + nom: str = Field(..., max_length=50) + prenom: Optional[str] = Field(None, max_length=50) + fonction: Optional[str] = Field(None, max_length=50) + + # Adresse + adresse: Optional[str] = Field(None, max_length=100) + complement: Optional[str] = Field(None, max_length=100) + code_postal: Optional[str] = Field(None, max_length=10) + ville: Optional[str] = Field(None, max_length=50) + code_region: Optional[str] = Field(None, max_length=50) + pays: Optional[str] = Field(None, max_length=50) + + # Services + service: Optional[str] = Field(None, max_length=50) + vendeur: bool = Field(default=False) + caissier: bool = Field(default=False) + acheteur: bool = Field(default=False) + chef_ventes: bool = Field(default=False) + numero_chef_ventes: Optional[int] = None + + # Contact + telephone: Optional[str] = Field(None, max_length=20) + telecopie: Optional[str] = Field(None, max_length=20) + email: Optional[EmailStr] = None + tel_portable: Optional[str] = Field(None, max_length=20) + + # Réseaux sociaux + facebook: Optional[str] = Field(None, max_length=100) + linkedin: Optional[str] = Field(None, max_length=100) + skype: Optional[str] = Field(None, max_length=100) + + # Autres + matricule: Optional[str] = Field(None, max_length=20) + sommeil: bool = Field(default=False) + + +class CollaborateurCreate(CollaborateurBase): + """Création d'un collaborateur""" + + pass + + +class CollaborateurUpdate(BaseModel): + """Modification d'un collaborateur (tous champs optionnels)""" + + nom: Optional[str] = Field(None, max_length=50) + prenom: Optional[str] = Field(None, max_length=50) + fonction: Optional[str] = Field(None, max_length=50) + + adresse: Optional[str] = Field(None, max_length=100) + complement: Optional[str] = Field(None, max_length=100) + code_postal: Optional[str] = Field(None, max_length=10) + ville: Optional[str] = Field(None, max_length=50) + code_region: Optional[str] = Field(None, max_length=50) + pays: Optional[str] = Field(None, max_length=50) + + service: Optional[str] = Field(None, max_length=50) + vendeur: Optional[bool] = None + caissier: Optional[bool] = None + acheteur: Optional[bool] = None + chef_ventes: Optional[bool] = None + numero_chef_ventes: Optional[int] = None + + telephone: Optional[str] = Field(None, max_length=20) + telecopie: Optional[str] = Field(None, max_length=20) + email: Optional[EmailStr] = None + tel_portable: Optional[str] = Field(None, max_length=20) + + facebook: Optional[str] = Field(None, max_length=100) + linkedin: Optional[str] = Field(None, max_length=100) + skype: Optional[str] = Field(None, max_length=100) + + matricule: Optional[str] = Field(None, max_length=20) + sommeil: Optional[bool] = None + + +class CollaborateurListe(BaseModel): + """Vue liste simplifiée""" + + numero: int + nom: str + prenom: Optional[str] + fonction: Optional[str] + service: Optional[str] + email: Optional[str] + telephone: Optional[str] + vendeur: bool + sommeil: bool + + +class CollaborateurDetails(CollaborateurBase): + """Détails complets d'un collaborateur""" + + numero: int + + class Config: + json_schema_extra = { + "example": { + "numero": 1, + "nom": "DUPONT", + "prenom": "Jean", + "fonction": "Directeur Commercial", + "service": "Commercial", + "vendeur": True, + "email": "j.dupont@entreprise.fr", + "telephone": "0123456789", + "sommeil": False, + } + }