Creating new fournisseur
This commit is contained in:
parent
3aadc67abf
commit
c66280b305
2 changed files with 354 additions and 3 deletions
41
main.py
41
main.py
|
|
@ -94,6 +94,19 @@ class ClientUpdateGatewayRequest(BaseModel):
|
|||
code: str
|
||||
client_data: Dict
|
||||
|
||||
class FournisseurCreateRequest(BaseModel):
|
||||
intitule: str = Field(..., description="Raison sociale du fournisseur")
|
||||
compte_collectif: str = Field("401000", description="Compte général rattaché")
|
||||
num: Optional[str] = Field(None, description="Code fournisseur (auto si vide)")
|
||||
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É
|
||||
|
|
@ -2595,6 +2608,34 @@ def fournisseurs_list(req: FiltreRequest):
|
|||
logger.error(f"❌ Erreur liste fournisseurs: {e}", exc_info=True)
|
||||
raise HTTPException(500, str(e))
|
||||
|
||||
@app.post("/sage/fournisseurs/create", dependencies=[Depends(verify_token)])
|
||||
def create_fournisseur_endpoint(req: FournisseurCreateRequest):
|
||||
"""
|
||||
➕ Création d'un fournisseur dans Sage
|
||||
|
||||
✅ Utilise FactoryFournisseur.Create() directement
|
||||
"""
|
||||
try:
|
||||
# Appel au connecteur Sage
|
||||
resultat = sage.creer_fournisseur(req.dict())
|
||||
|
||||
logger.info(f"✅ Fournisseur créé: {resultat.get('numero')}")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"data": resultat
|
||||
}
|
||||
|
||||
except ValueError as e:
|
||||
# Erreur métier (ex: doublon)
|
||||
logger.warning(f"⚠️ Erreur métier création fournisseur: {e}")
|
||||
raise HTTPException(400, str(e))
|
||||
|
||||
except Exception as e:
|
||||
# Erreur technique (ex: COM)
|
||||
logger.error(f"❌ Erreur technique création fournisseur: {e}")
|
||||
raise HTTPException(500, str(e))
|
||||
|
||||
@app.post("/sage/fournisseurs/get", dependencies=[Depends(verify_token)])
|
||||
def fournisseur_get(req: CodeRequest):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ class SageConnector:
|
|||
|
||||
def _refresh_cache_clients(self):
|
||||
"""
|
||||
Actualise le cache des clients ET fournisseurs
|
||||
Actualise le cache des clients
|
||||
Charge TOUS les tiers (CT_Type=0 ET CT_Type=1)
|
||||
"""
|
||||
if not self.cial:
|
||||
|
|
@ -191,7 +191,7 @@ class SageConnector:
|
|||
erreurs_consecutives = 0
|
||||
max_erreurs = 50
|
||||
|
||||
logger.info("🔄 Actualisation cache clients/fournisseurs/prospects...")
|
||||
logger.info("🔄 Actualisation cache clients et prospects...")
|
||||
|
||||
while index < 10000 and erreurs_consecutives < max_erreurs:
|
||||
try:
|
||||
|
|
@ -399,6 +399,316 @@ class SageConnector:
|
|||
logger.error(f"❌ Erreur liste fournisseurs: {e}", exc_info=True)
|
||||
return []
|
||||
|
||||
def creer_fournisseur(self, fournisseur_data: Dict) -> Dict:
|
||||
"""
|
||||
✅ Crée un nouveau fournisseur dans Sage 100c via FactoryFournisseur
|
||||
|
||||
IMPORTANT: Utilise FactoryFournisseur.Create() et NON FactoryClient.Create()
|
||||
car les fournisseurs sont gérés séparément dans Sage.
|
||||
|
||||
Args:
|
||||
fournisseur_data: Dictionnaire contenant:
|
||||
- intitule (obligatoire): Raison sociale
|
||||
- compte_collectif (défaut: "401000"): Compte général
|
||||
- num (optionnel): Code fournisseur personnalisé
|
||||
- adresse, code_postal, ville, pays
|
||||
- email, telephone
|
||||
- siret, tva_intra
|
||||
|
||||
Returns:
|
||||
Dict contenant le fournisseur créé avec son numéro définitif
|
||||
|
||||
Raises:
|
||||
ValueError: Si le fournisseur existe déjà ou données invalides
|
||||
RuntimeError: Si erreur technique Sage
|
||||
"""
|
||||
if not self.cial:
|
||||
raise RuntimeError("Connexion Sage non établie")
|
||||
|
||||
try:
|
||||
with self._com_context(), self._lock_com:
|
||||
# ========================================
|
||||
# ÉTAPE 0 : VALIDATION & NETTOYAGE
|
||||
# ========================================
|
||||
logger.info("🔍 === VALIDATION DONNÉES FOURNISSEUR ===")
|
||||
|
||||
if not fournisseur_data.get("intitule"):
|
||||
raise ValueError("Le champ 'intitule' est obligatoire")
|
||||
|
||||
# Nettoyage et troncature (longueurs max Sage)
|
||||
intitule = str(fournisseur_data["intitule"])[:69].strip()
|
||||
num_prop = str(fournisseur_data.get("num", "")).upper()[:17].strip() if fournisseur_data.get("num") else ""
|
||||
compte = str(fournisseur_data.get("compte_collectif", "401000"))[:13].strip()
|
||||
|
||||
adresse = str(fournisseur_data.get("adresse", ""))[:35].strip()
|
||||
code_postal = str(fournisseur_data.get("code_postal", ""))[:9].strip()
|
||||
ville = str(fournisseur_data.get("ville", ""))[:35].strip()
|
||||
pays = str(fournisseur_data.get("pays", ""))[:35].strip()
|
||||
|
||||
telephone = str(fournisseur_data.get("telephone", ""))[:21].strip()
|
||||
email = str(fournisseur_data.get("email", ""))[:69].strip()
|
||||
|
||||
siret = str(fournisseur_data.get("siret", ""))[:14].strip()
|
||||
tva_intra = str(fournisseur_data.get("tva_intra", ""))[:25].strip()
|
||||
|
||||
logger.info(f" intitule: '{intitule}' (len={len(intitule)})")
|
||||
logger.info(f" num: '{num_prop or 'AUTO'}' (len={len(num_prop)})")
|
||||
logger.info(f" compte: '{compte}' (len={len(compte)})")
|
||||
|
||||
# ========================================
|
||||
# ÉTAPE 1 : CRÉATION OBJET FOURNISSEUR
|
||||
# ========================================
|
||||
# 🔑 CRITIQUE: Utiliser FactoryFournisseur, PAS FactoryClient !
|
||||
factory_fournisseur = self.cial.CptaApplication.FactoryFournisseur
|
||||
|
||||
persist = factory_fournisseur.Create()
|
||||
fournisseur = win32com.client.CastTo(persist, "IBOFournisseur3")
|
||||
|
||||
# 🔑 CRITIQUE : Initialiser l'objet
|
||||
fournisseur.SetDefault()
|
||||
|
||||
logger.info("✅ Objet fournisseur créé et initialisé")
|
||||
|
||||
# ========================================
|
||||
# ÉTAPE 2 : CHAMPS OBLIGATOIRES
|
||||
# ========================================
|
||||
logger.info("📝 Définition des champs obligatoires...")
|
||||
|
||||
# 1. Intitulé (OBLIGATOIRE)
|
||||
fournisseur.CT_Intitule = intitule
|
||||
logger.debug(f" ✅ CT_Intitule: '{intitule}'")
|
||||
|
||||
# 2. Type = Fournisseur (1)
|
||||
# ⚠️ NOTE: Sur certaines versions Sage, CT_Type n'existe pas
|
||||
# et le type est automatiquement défini par la factory utilisée
|
||||
try:
|
||||
fournisseur.CT_Type = 1 # 1 = Fournisseur
|
||||
logger.debug(" ✅ CT_Type: 1 (Fournisseur)")
|
||||
except:
|
||||
logger.debug(" ⚠️ CT_Type non défini (géré par FactoryFournisseur)")
|
||||
|
||||
# 3. Qualité (pour versions récentes Sage)
|
||||
try:
|
||||
fournisseur.CT_Qualite = "FOU"
|
||||
logger.debug(" ✅ CT_Qualite: 'FOU'")
|
||||
except:
|
||||
logger.debug(" ⚠️ CT_Qualite non défini (pas critique)")
|
||||
|
||||
# 4. Compte général principal (OBLIGATOIRE)
|
||||
try:
|
||||
factory_compte = self.cial.CptaApplication.FactoryCompteG
|
||||
persist_compte = factory_compte.ReadNumero(compte)
|
||||
|
||||
if persist_compte:
|
||||
compte_obj = win32com.client.CastTo(persist_compte, "IBOCompteG3")
|
||||
compte_obj.Read()
|
||||
|
||||
# Assigner l'objet CompteG
|
||||
fournisseur.CompteGPrinc = compte_obj
|
||||
logger.debug(f" ✅ CompteGPrinc: objet '{compte}' assigné")
|
||||
else:
|
||||
logger.warning(f" ⚠️ Compte {compte} introuvable - utilisation défaut")
|
||||
except Exception as e:
|
||||
logger.warning(f" ⚠️ Erreur CompteGPrinc: {e}")
|
||||
|
||||
# 5. Numéro fournisseur (OBLIGATOIRE - générer si vide)
|
||||
if num_prop:
|
||||
fournisseur.CT_Num = num_prop
|
||||
logger.debug(f" ✅ CT_Num fourni: '{num_prop}'")
|
||||
else:
|
||||
# 🔑 CRITIQUE : Générer le numéro automatiquement
|
||||
try:
|
||||
# Méthode 1 : SetDefaultNumPiece (si disponible)
|
||||
if hasattr(fournisseur, 'SetDefaultNumPiece'):
|
||||
fournisseur.SetDefaultNumPiece()
|
||||
num_genere = getattr(fournisseur, "CT_Num", "")
|
||||
logger.debug(f" ✅ CT_Num auto-généré: '{num_genere}'")
|
||||
else:
|
||||
# Méthode 2 : GetNextNumero depuis la factory
|
||||
num_genere = factory_fournisseur.GetNextNumero()
|
||||
if num_genere:
|
||||
fournisseur.CT_Num = num_genere
|
||||
logger.debug(f" ✅ CT_Num auto (GetNextNumero): '{num_genere}'")
|
||||
else:
|
||||
# Méthode 3 : Fallback - timestamp
|
||||
import time
|
||||
num_genere = f"FOUR{int(time.time()) % 1000000}"
|
||||
fournisseur.CT_Num = num_genere
|
||||
logger.warning(f" ⚠️ CT_Num fallback: '{num_genere}'")
|
||||
except Exception as e:
|
||||
logger.error(f" ❌ Impossible de générer CT_Num: {e}")
|
||||
raise ValueError("Impossible de générer le numéro fournisseur automatiquement")
|
||||
|
||||
# 6. Catégories (valeurs par défaut)
|
||||
try:
|
||||
if hasattr(fournisseur, 'N_CatTarif'):
|
||||
fournisseur.N_CatTarif = 1
|
||||
if hasattr(fournisseur, 'N_CatCompta'):
|
||||
fournisseur.N_CatCompta = 1
|
||||
if hasattr(fournisseur, 'N_Period'):
|
||||
fournisseur.N_Period = 1
|
||||
logger.debug(" ✅ Catégories (N_*) initialisées")
|
||||
except Exception as e:
|
||||
logger.warning(f" ⚠️ Catégories: {e}")
|
||||
|
||||
# ========================================
|
||||
# ÉTAPE 3 : CHAMPS OPTIONNELS
|
||||
# ========================================
|
||||
logger.info("📝 Définition champs optionnels...")
|
||||
|
||||
# Adresse (objet IAdresse)
|
||||
if any([adresse, code_postal, ville, pays]):
|
||||
try:
|
||||
adresse_obj = fournisseur.Adresse
|
||||
|
||||
if adresse:
|
||||
adresse_obj.Adresse = adresse
|
||||
if code_postal:
|
||||
adresse_obj.CodePostal = code_postal
|
||||
if ville:
|
||||
adresse_obj.Ville = ville
|
||||
if pays:
|
||||
adresse_obj.Pays = pays
|
||||
|
||||
logger.debug(" ✅ Adresse définie")
|
||||
except Exception as e:
|
||||
logger.warning(f" ⚠️ Adresse: {e}")
|
||||
|
||||
# Télécom (objet ITelecom)
|
||||
if telephone or email:
|
||||
try:
|
||||
telecom_obj = fournisseur.Telecom
|
||||
|
||||
if telephone:
|
||||
telecom_obj.Telephone = telephone
|
||||
if email:
|
||||
telecom_obj.EMail = email
|
||||
|
||||
logger.debug(" ✅ Télécom défini")
|
||||
except Exception as e:
|
||||
logger.warning(f" ⚠️ Télécom: {e}")
|
||||
|
||||
# Identifiants fiscaux
|
||||
if siret:
|
||||
try:
|
||||
fournisseur.CT_Siret = siret
|
||||
logger.debug(f" ✅ SIRET: '{siret}'")
|
||||
except Exception as e:
|
||||
logger.warning(f" ⚠️ SIRET: {e}")
|
||||
|
||||
if tva_intra:
|
||||
try:
|
||||
fournisseur.CT_Identifiant = tva_intra
|
||||
logger.debug(f" ✅ TVA intra: '{tva_intra}'")
|
||||
except Exception as e:
|
||||
logger.warning(f" ⚠️ TVA: {e}")
|
||||
|
||||
# Options par défaut
|
||||
try:
|
||||
if hasattr(fournisseur, 'CT_Lettrage'):
|
||||
fournisseur.CT_Lettrage = True
|
||||
if hasattr(fournisseur, 'CT_Sommeil'):
|
||||
fournisseur.CT_Sommeil = False
|
||||
logger.debug(" ✅ Options par défaut définies")
|
||||
except Exception as e:
|
||||
logger.debug(f" ⚠️ Options: {e}")
|
||||
|
||||
# ========================================
|
||||
# ÉTAPE 4 : VÉRIFICATION PRÉ-WRITE
|
||||
# ========================================
|
||||
logger.info("🔍 === DIAGNOSTIC PRÉ-WRITE ===")
|
||||
|
||||
num_avant_write = getattr(fournisseur, "CT_Num", "")
|
||||
if not num_avant_write:
|
||||
logger.error("❌ CRITIQUE: CT_Num toujours vide !")
|
||||
raise ValueError("Le numéro fournisseur (CT_Num) est obligatoire")
|
||||
|
||||
logger.info(f"✅ CT_Num confirmé: '{num_avant_write}'")
|
||||
|
||||
# ========================================
|
||||
# ÉTAPE 5 : ÉCRITURE EN BASE
|
||||
# ========================================
|
||||
logger.info("💾 Écriture du fournisseur dans Sage...")
|
||||
|
||||
try:
|
||||
fournisseur.Write()
|
||||
logger.info("✅ Write() réussi !")
|
||||
|
||||
except Exception as e:
|
||||
error_detail = str(e)
|
||||
|
||||
# Récupérer l'erreur Sage détaillée
|
||||
try:
|
||||
sage_error = self.cial.CptaApplication.LastError
|
||||
if sage_error:
|
||||
error_detail = f"{sage_error.Description} (Code: {sage_error.Number})"
|
||||
logger.error(f"❌ Erreur Sage: {error_detail}")
|
||||
except:
|
||||
pass
|
||||
|
||||
# Analyser l'erreur
|
||||
if "doublon" in error_detail.lower() or "existe" in error_detail.lower():
|
||||
raise ValueError(f"Ce fournisseur existe déjà : {error_detail}")
|
||||
|
||||
raise RuntimeError(f"Échec Write(): {error_detail}")
|
||||
|
||||
# ========================================
|
||||
# ÉTAPE 6 : RELECTURE & FINALISATION
|
||||
# ========================================
|
||||
try:
|
||||
fournisseur.Read()
|
||||
except Exception as e:
|
||||
logger.warning(f"⚠️ Impossible de relire: {e}")
|
||||
|
||||
num_final = getattr(fournisseur, "CT_Num", "")
|
||||
|
||||
if not num_final:
|
||||
raise RuntimeError("CT_Num vide après Write()")
|
||||
|
||||
logger.info(f"✅✅✅ FOURNISSEUR CRÉÉ: {num_final} - {intitule} ✅✅✅")
|
||||
|
||||
# ========================================
|
||||
# ÉTAPE 7 : CONSTRUCTION RÉPONSE
|
||||
# ========================================
|
||||
resultat = {
|
||||
"numero": num_final,
|
||||
"intitule": intitule,
|
||||
"compte_collectif": compte,
|
||||
"type": 1, # Fournisseur
|
||||
"est_fournisseur": True,
|
||||
"adresse": adresse or None,
|
||||
"code_postal": code_postal or None,
|
||||
"ville": ville or None,
|
||||
"pays": pays or None,
|
||||
"email": email or None,
|
||||
"telephone": telephone or None,
|
||||
"siret": siret or None,
|
||||
"tva_intra": tva_intra or None
|
||||
}
|
||||
|
||||
# ⚠️ PAS DE REFRESH CACHE ICI
|
||||
# Car lister_tous_fournisseurs() utilise FactoryFournisseur.List()
|
||||
# qui lit directement depuis Sage (pas de cache)
|
||||
|
||||
return resultat
|
||||
|
||||
except ValueError as e:
|
||||
logger.error(f"❌ Erreur métier: {e}")
|
||||
raise
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Erreur création fournisseur: {e}", exc_info=True)
|
||||
|
||||
error_message = str(e)
|
||||
if self.cial:
|
||||
try:
|
||||
err = self.cial.CptaApplication.LastError
|
||||
if err:
|
||||
error_message = f"Erreur Sage: {err.Description}"
|
||||
except:
|
||||
pass
|
||||
|
||||
raise RuntimeError(f"Erreur technique Sage: {error_message}")
|
||||
|
||||
def lire_fournisseur(self, code):
|
||||
"""
|
||||
|
|
|
|||
Loading…
Reference in a new issue