diff --git a/main.py b/main.py index 426ed98..d009574 100644 --- a/main.py +++ b/main.py @@ -76,6 +76,19 @@ class StatutRequest(BaseModel): nouveau_statut: int +class ClientCreateRequest(BaseModel): + intitule: str = Field(..., description="Nom du client (CT_Intitule)") + compte_collectif: str = Field("411000", description="Compte général rattaché") + num: Optional[str] = Field(None, description="Laisser vide pour numérotation auto") + adresse: Optional[str] = None + code_postal: Optional[str] = None + ville: Optional[str] = None + pays: Optional[str] = None + email: Optional[str] = None + telephone: Optional[str] = None + siret: Optional[str] = None + tva_intra: Optional[str] = None + # ===================================================== # SÉCURITÉ # ===================================================== @@ -185,6 +198,20 @@ def client_get(req: CodeRequest): raise HTTPException(500, str(e)) +@app.post("/sage/clients/create", dependencies=[Depends(verify_token)]) +def create_client_endpoint(req: ClientCreateRequest): + """Création d'un client dans Sage""" + try: + # Transformation du modèle Pydantic en dict pour le connecteur + resultat = sage.creer_client(req.dict()) + return {"success": True, "data": resultat} + except ValueError as e: + logger.warning(f"Erreur métier création client: {e}") + raise HTTPException(400, str(e)) + except Exception as e: + logger.error(f"Erreur technique création client: {e}") + raise HTTPException(500, str(e)) + # ===================================================== # ENDPOINTS - ARTICLES # ===================================================== @@ -2566,7 +2593,168 @@ def livraison_get(req: CodeRequest): logger.error(f"Erreur lecture livraison: {e}") raise HTTPException(500, str(e)) +# À ajouter dans main.py (Windows Gateway) +@app.get("/sage/diagnostic/fournisseurs-detection", dependencies=[Depends(verify_token)]) +def diagnostiquer_detection_fournisseurs(): + """ + 🔍 DIAGNOSTIC : Découvre comment identifier les fournisseurs dans votre Sage + + Teste plusieurs méthodes : + - CT_Type = 1 (méthode classique) + - CT_Qualite (méthode moderne : 0=Aucune, 1=Client, 2=Fournisseur, 3=Client+Fournisseur) + - CT_TypeTiers + - Présence dans FactoryFournisseur + """ + try: + if not sage or not sage.cial: + raise HTTPException(503, "Service Sage indisponible") + + with sage._com_context(), sage._lock_com: + factory_client = sage.cial.CptaApplication.FactoryClient + + resultats = { + "methodes_detectees": [], + "tiers_analyses": [], + "recommandation": None + } + + # Scanner les 50 premiers tiers + index = 1 + while index <= 50: + try: + persist = factory_client.List(index) + if persist is None: + break + + tiers = sage._cast_client(persist) + if not tiers: + index += 1 + continue + + analyse = { + "index": index, + "numero": getattr(tiers, "CT_Num", ""), + "intitule": getattr(tiers, "CT_Intitule", ""), + "ct_type": getattr(tiers, "CT_Type", None), + "ct_prospect": getattr(tiers, "CT_Prospect", None), + "methodes_identifiees": [] + } + + # Méthode 1 : CT_Qualite (la plus courante aujourd'hui) + try: + qualite = getattr(tiers, "CT_Qualite", None) + if qualite is not None: + analyse["ct_qualite"] = qualite + analyse["ct_qualite_libelle"] = { + 0: "Aucune", + 1: "Client uniquement", + 2: "Fournisseur uniquement", + 3: "Client ET Fournisseur" + }.get(qualite, f"Inconnu ({qualite})") + + if qualite in [2, 3]: + analyse["methodes_identifiees"].append("CT_Qualite (2 ou 3)") + except: + pass + + # Méthode 2 : CT_TypeTiers + try: + type_tiers = getattr(tiers, "CT_TypeTiers", None) + if type_tiers is not None: + analyse["ct_type_tiers"] = type_tiers + except: + pass + + # Méthode 3 : Vérifier si accessible via FactoryFournisseur + try: + factory_fourn = sage.cial.CptaApplication.FactoryFournisseur + persist_fourn = factory_fourn.ReadNumero(analyse["numero"]) + + if persist_fourn: + analyse["methodes_identifiees"].append("FactoryFournisseur.ReadNumero()") + analyse["accessible_via_factory_fournisseur"] = True + except: + analyse["accessible_via_factory_fournisseur"] = False + + # Méthode 4 : CT_Type = 1 (ancienne méthode) + if analyse["ct_type"] == 1: + analyse["methodes_identifiees"].append("CT_Type = 1") + + resultats["tiers_analyses"].append(analyse) + index += 1 + + except Exception as e: + logger.debug(f"Erreur analyse tiers {index}: {e}") + index += 1 + + # Analyser les méthodes détectées + fournisseurs_via_qualite = [ + t for t in resultats["tiers_analyses"] + if t.get("ct_qualite") in [2, 3] + ] + + fournisseurs_via_type = [ + t for t in resultats["tiers_analyses"] + if t.get("ct_type") == 1 + ] + + fournisseurs_via_factory = [ + t for t in resultats["tiers_analyses"] + if t.get("accessible_via_factory_fournisseur") == True + ] + + resultats["statistiques"] = { + "total_tiers_scannes": len(resultats["tiers_analyses"]), + "fournisseurs_via_ct_qualite": len(fournisseurs_via_qualite), + "fournisseurs_via_ct_type": len(fournisseurs_via_type), + "fournisseurs_via_factory": len(fournisseurs_via_factory) + } + + # Exemples de fournisseurs détectés + if fournisseurs_via_qualite: + resultats["exemples_fournisseurs_qualite"] = fournisseurs_via_qualite[:5] + + if fournisseurs_via_factory: + resultats["exemples_fournisseurs_factory"] = fournisseurs_via_factory[:5] + + # Recommandation + if fournisseurs_via_qualite: + resultats["recommandation"] = { + "methode": "CT_Qualite", + "condition": "CT_Qualite IN (2, 3)", + "description": "Votre Sage utilise le champ CT_Qualite pour distinguer clients/fournisseurs" + } + elif fournisseurs_via_factory: + resultats["recommandation"] = { + "methode": "FactoryFournisseur", + "condition": "Accessible via FactoryFournisseur.ReadNumero()", + "description": "Utilisez FactoryFournisseur au lieu de FactoryClient" + } + elif fournisseurs_via_type: + resultats["recommandation"] = { + "methode": "CT_Type", + "condition": "CT_Type = 1", + "description": "Méthode classique (rare aujourd'hui)" + } + else: + resultats["recommandation"] = { + "methode": "AUCUNE", + "description": "Aucun fournisseur détecté dans les 50 premiers tiers", + "suggestion": "Vérifiez si des fournisseurs existent dans Sage, ou augmentez le scan" + } + + logger.info(f"[DIAG] Fournisseurs détectés: {resultats['statistiques']}") + + return { + "success": True, + "diagnostic": resultats + } + + except Exception as e: + logger.error(f"[DIAG] Erreur diagnostic fournisseurs: {e}", exc_info=True) + raise HTTPException(500, str(e)) + # ===================================================== # LANCEMENT # ===================================================== diff --git a/sage_connector.py b/sage_connector.py index 76d2452..e7ac1cc 100644 --- a/sage_connector.py +++ b/sage_connector.py @@ -2370,3 +2370,86 @@ class SageConnector: except Exception as e: logger.error(f"❌ Erreur lecture livraison {numero}: {e}") return None + + # ========================================================================= + # CREATION CLIENT (US-A8 ?) + # ========================================================================= + + def creer_client(self, client_data: Dict) -> Dict: + """ + Crée un nouveau client dans Sage via CptaApplication.FactoryClient + """ + if not self.cial: + raise RuntimeError("Connexion Sage non établie") + + try: + with self._com_context(), self._lock_com: + # 1. Accès à la Factory Client (Module Comptabilité) + factory_client = self.cial.CptaApplication.FactoryClient + + # 2. Création de l'objet en mémoire + client = factory_client.Create() # Ou CreateNew() selon version, Create() est plus standard + + # 3. Remplissage des Champs OBLIGATOIRES + # Si 'num' est fourni, on l'utilise, sinon Sage utilisera la souche par défaut + if client_data.get("num"): + client.CT_Num = client_data["num"] + + client.CT_Intitule = client_data["intitule"] + client.CT_Type = 0 # 0 = Client + + # Compte Collectif (Obligatoire, ex: 411000) + if client_data.get("compte_collectif"): + client.CT_CompteA = client_data["compte_collectif"] + + # 4. Remplissage des Champs OPTIONNELS + # --- Adresse principale --- + try: + client.Adresse.Adresse = client_data.get("adresse", "") + client.Adresse.CodePostal = client_data.get("code_postal", "") + client.Adresse.Ville = client_data.get("ville", "") + client.Adresse.Pays = client_data.get("pays", "") + except Exception as e: + logger.warning(f"Erreur remplissage adresse: {e}") + + # --- Contact / Télécom --- + try: + client.Telecom.Telephone = client_data.get("telephone", "") + client.Telecom.EMail = client_data.get("email", "") + except Exception as e: + logger.warning(f"Erreur remplissage telecom: {e}") + + # --- Identifiants --- + if client_data.get("siret"): + try: client.CT_Siret = client_data["siret"] + except: pass + + if client_data.get("tva_intra"): + try: client.CT_Identifiant = client_data["tva_intra"] + except: pass + + # 5. Écriture en base + client.Write() + + # Récupération du numéro généré (surtout si auto-incrément) + num_final = client.CT_Num + + # Mise à jour du cache local immédiate pour qu'il soit trouvable tout de suite + with self._lock_clients: + self._cache_clients.append(self._extraire_client(client)) + self._cache_clients_dict[num_final] = self._extraire_client(client) + + logger.info(f"✅ Client créé avec succès : {num_final} - {client.CT_Intitule}") + + return { + "numero": num_final, + "intitule": client.CT_Intitule, + "compte_collectif": getattr(client, "CT_CompteA", "") + } + + except Exception as e: + logger.error(f"❌ Erreur création client : {e}", exc_info=True) + # Gestion d'erreur spécifique pour les doublons + if "doublon" in str(e).lower() or "existe déjà" in str(e).lower(): + raise ValueError(f"Le client ou le numéro existe déjà.") + raise RuntimeError(f"Erreur technique Sage: {str(e)}") \ No newline at end of file