Updated devis creation logics to not automatically be on status 0

This commit is contained in:
Fanilo-Nantenaina 2025-12-06 18:13:04 +03:00
parent 9d569a8e03
commit c4ddf03ae5

View file

@ -1206,16 +1206,22 @@ class SageConnector:
# CRÉATION DEVIS (US-A1) - VERSION TRANSACTIONNELLE
# =========================================================================
def creer_devis_enrichi(self, devis_data: dict):
def creer_devis_enrichi(self, devis_data: dict, valider: bool = False):
"""
Création de devis avec transaction Sage
SOLUTION FINALE: Utilisation de SetDefaultArticle()
Création de devis avec option brouillon/validé
Args:
devis_data: Données du devis
valider: Si True, valide le devis (statut 2). Si False, reste en brouillon (statut 0)
CORRECTION: Par défaut, crée un BROUILLON (statut 0)
"""
if not self.cial:
raise RuntimeError("Connexion Sage non établie")
logger.info(
f"🚀 Début création devis pour client {devis_data['client']['code']}"
f"🚀 Début création devis pour client {devis_data['client']['code']} "
f"(valider={valider})"
)
try:
@ -1258,7 +1264,7 @@ class SageConnector:
doc.DO_Date = pywintypes.Time(date_obj)
logger.info(f"📅 Date définie: {date_obj.date()}")
# ===== CLIENT (CRITIQUE: Doit être défini AVANT les lignes) =====
# ===== CLIENT =====
factory_client = self.cial.CptaApplication.FactoryClient
persist_client = factory_client.ReadNumero(
devis_data["client"]["code"]
@ -1275,14 +1281,13 @@ class SageConnector:
f"❌ Impossible de charger le client {devis_data['client']['code']}"
)
# ✅ CRITIQUE: Associer le client au document
doc.SetDefaultClient(client_obj)
doc.Write()
logger.info(
f"👤 Client {devis_data['client']['code']} associé et document écrit"
)
# ===== LIGNES AVEC SetDefaultArticle() =====
# ===== LIGNES =====
try:
factory_lignes = doc.FactoryDocumentLigne
except:
@ -1297,7 +1302,7 @@ class SageConnector:
f"--- Ligne {idx}: {ligne_data['article_code']} ---"
)
# 🔍 ÉTAPE 1: Charger l'article RÉEL depuis Sage pour le prix
# Charger l'article
persist_article = factory_article.ReadReference(
ligne_data["article_code"]
)
@ -1312,17 +1317,10 @@ class SageConnector:
)
article_obj.Read()
# 💰 ÉTAPE 2: Récupérer le prix de vente RÉEL
prix_sage = float(getattr(article_obj, "AR_PrixVen", 0.0))
designation_sage = getattr(article_obj, "AR_Design", "")
logger.info(f"💰 Prix Sage: {prix_sage}")
if prix_sage == 0:
logger.warning(
f"⚠️ ATTENTION: Article {ligne_data['article_code']} a un prix de vente = 0€"
)
# 📝 ÉTAPE 3: Créer la ligne de devis
# Créer la ligne
ligne_persist = factory_lignes.Create()
try:
@ -1334,114 +1332,75 @@ class SageConnector:
ligne_persist, "IBODocumentVenteLigne3"
)
# ✅✅✅ SOLUTION FINALE: SetDefaultArticleReference avec 2 paramètres ✅✅✅
# Associer article
quantite = float(ligne_data["quantite"])
try:
# Méthode 1: Via référence (plus simple et plus fiable)
ligne_obj.SetDefaultArticleReference(
ligne_data["article_code"], quantite
)
logger.info(
f"✅ Article associé via SetDefaultArticleReference('{ligne_data['article_code']}', {quantite})"
)
except Exception as e:
logger.warning(
f"⚠️ SetDefaultArticleReference échoué: {e}, tentative avec objet article"
)
except:
try:
# Méthode 2: Via objet article
ligne_obj.SetDefaultArticle(article_obj, quantite)
logger.info(
f"✅ Article associé via SetDefaultArticle(obj, {quantite})"
)
except Exception as e2:
logger.error(
f"❌ Toutes les méthodes d'association ont échoué"
)
# Fallback: définir manuellement
except:
ligne_obj.DL_Design = (
designation_sage or ligne_data["designation"]
designation_sage or ligne_data.get("designation", "")
)
ligne_obj.DL_Qte = quantite
logger.warning("⚠️ Configuration manuelle appliquée")
# ⚙️ ÉTAPE 4: Vérifier le prix automatique chargé
prix_auto = float(getattr(ligne_obj, "DL_PrixUnitaire", 0.0))
logger.info(f"💰 Prix auto chargé: {prix_auto}")
# 💵 ÉTAPE 5: Ajuster le prix si nécessaire
# Prix
prix_a_utiliser = ligne_data.get("prix_unitaire_ht")
if prix_a_utiliser is not None and prix_a_utiliser > 0:
# Prix personnalisé fourni
if prix_a_utiliser:
ligne_obj.DL_PrixUnitaire = float(prix_a_utiliser)
logger.info(f"💰 Prix personnalisé: {prix_a_utiliser}")
elif prix_auto == 0:
# Pas de prix auto, forcer le prix Sage
if prix_sage == 0:
raise ValueError(
f"Prix nul pour article {ligne_data['article_code']}"
)
elif prix_sage > 0:
ligne_obj.DL_PrixUnitaire = float(prix_sage)
logger.info(f"💰 Prix Sage forcé: {prix_sage}")
else:
# Prix auto correct, on le garde
logger.info(f"💰 Prix auto conservé: {prix_auto}")
prix_final = float(getattr(ligne_obj, "DL_PrixUnitaire", 0.0))
montant_ligne = quantite * prix_final
logger.info(f"{quantite} x {prix_final}€ = {montant_ligne}")
# 🎁 Remise
# Remise
remise = ligne_data.get("remise_pourcentage", 0)
if remise > 0:
try:
ligne_obj.DL_Remise01REM_Valeur = float(remise)
ligne_obj.DL_Remise01REM_Type = 0
montant_apres_remise = montant_ligne * (
1 - remise / 100
)
logger.info(
f"🎁 Remise {remise}% → {montant_apres_remise}"
)
except Exception as e:
logger.warning(f"⚠️ Remise non appliquée: {e}")
except:
pass
# 💾 ÉTAPE 6: Écrire la ligne
ligne_obj.Write()
logger.info(f"✅ Ligne {idx} écrite")
# 🔍 VÉRIFICATION: Relire la ligne pour confirmer
try:
ligne_obj.Read()
prix_enregistre = float(
getattr(ligne_obj, "DL_PrixUnitaire", 0.0)
)
montant_enregistre = float(
getattr(ligne_obj, "DL_MontantHT", 0.0)
)
logger.info(
f"🔍 Vérif: Prix={prix_enregistre}€, Montant HT={montant_enregistre}"
)
if montant_enregistre == 0:
logger.error(
f"❌ PROBLÈME: Montant enregistré = 0 pour ligne {idx}"
)
else:
logger.info(f"✅ Ligne {idx} OK: {montant_enregistre}")
except Exception as e:
logger.warning(f"⚠️ Impossible de vérifier la ligne: {e}")
# ===== VALIDATION DOCUMENT =====
logger.info("💾 Écriture finale du document...")
# ===== STATUT ET VALIDATION =====
doc.Write()
logger.info("🔄 Lancement du traitement (Process)...")
# 🔑 DIFFÉRENCE CRITIQUE ICI
if valider:
# Option 1: VALIDER le devis (statut 2)
logger.info("🔄 Validation du devis (Process)...")
process.Process()
# Le Process() met généralement le statut à 2
doc.Read()
statut_final = getattr(doc, "DO_Statut", 0)
logger.info(f"✅ Devis validé, statut: {statut_final}")
else:
# Option 2: BROUILLON (statut 0)
logger.info("📝 Devis créé en BROUILLON (pas de Process)...")
# Ne PAS appeler Process() pour garder en brouillon
# Forcer le statut à 0
doc.DO_Statut = 0
doc.Write()
statut_final = 0
logger.info("✅ Devis en brouillon (statut 0)")
# ===== COMMIT =====
if transaction_active:
self.cial.CptaApplication.CommitTrans()
logger.info("✅ Transaction committée")
# ===== RÉCUPÉRATION NUMÉRO =====
time.sleep(2)
numero_devis = None
try:
doc_result = process.DocumentResult
@ -1451,109 +1410,37 @@ class SageConnector:
)
doc_result.Read()
numero_devis = getattr(doc_result, "DO_Piece", "")
logger.info(
f"📄 Numéro (via DocumentResult): {numero_devis}"
)
except Exception as e:
logger.warning(f"⚠️ DocumentResult non accessible: {e}")
except:
pass
if not numero_devis:
doc.Read()
numero_devis = getattr(doc, "DO_Piece", "")
logger.info(f"📄 Numéro (via Document): {numero_devis}")
if not numero_devis:
raise RuntimeError("❌ Numéro devis vide après création")
# ===== COMMIT TRANSACTION =====
if transaction_active:
self.cial.CptaApplication.CommitTrans()
logger.info("✅ Transaction committée")
# ===== ATTENTE INDEXATION =====
logger.info("⏳ Attente indexation Sage (2s)...")
time.sleep(2)
# ===== RELECTURE COMPLÈTE =====
logger.info("🔍 Relecture complète du document...")
factory_doc = self.cial.FactoryDocumentVente
persist_reread = factory_doc.ReadPiece(0, numero_devis)
if not persist_reread:
logger.error(f"❌ Impossible de relire le devis {numero_devis}")
# Fallback: retourner les totaux calculés
total_calcule = sum(
l.get("montant_ligne_ht", 0) for l in devis_data["lignes"]
)
logger.warning(f"⚠️ Utilisation total calculé: {total_calcule}")
return {
"numero_devis": numero_devis,
"total_ht": total_calcule,
"total_ttc": round(total_calcule * 1.20, 2),
"nb_lignes": len(devis_data["lignes"]),
"client_code": devis_data["client"]["code"],
"date_devis": str(date_obj.date()),
}
if persist_reread:
doc_final = win32com.client.CastTo(
persist_reread, "IBODocumentVente3"
)
doc_final.Read()
# ===== EXTRACTION TOTAUX =====
total_ht = float(getattr(doc_final, "DO_TotalHT", 0.0))
total_ttc = float(getattr(doc_final, "DO_TotalTTC", 0.0))
client_code_final = getattr(doc_final, "CT_Num", "")
date_finale = getattr(doc_final, "DO_Date", None)
logger.info(f"💰 Total HT: {total_ht}")
logger.info(f"💰 Total TTC: {total_ttc}")
# ===== DIAGNOSTIC EN CAS D'ANOMALIE =====
if total_ht == 0 and total_ttc > 0:
logger.warning("⚠️ Anomalie: Total HT = 0 mais Total TTC > 0")
logger.info("🔍 Lecture des lignes pour diagnostic...")
try:
factory_lignes_verif = doc_final.FactoryDocumentLigne
except:
factory_lignes_verif = doc_final.FactoryDocumentVenteLigne
index = 1
total_calcule = 0.0
while index <= 20:
try:
ligne_p = factory_lignes_verif.List(index)
if ligne_p is None:
break
ligne_verif = win32com.client.CastTo(
ligne_p, "IBODocumentLigne3"
)
ligne_verif.Read()
montant = float(
getattr(ligne_verif, "DL_MontantHT", 0.0)
)
logger.info(
f" Ligne {index}: Montant HT = {montant}"
)
total_calcule += montant
index += 1
except:
break
logger.info(f"📊 Total calculé manuellement: {total_calcule}")
if total_calcule > 0:
total_ht = total_calcule
total_ttc = round(total_ht * 1.20, 2)
logger.info(
f"✅ Correction appliquée: HT={total_ht}€, TTC={total_ttc}"
)
statut_final = getattr(doc_final, "DO_Statut", 0)
else:
total_ht = 0.0
total_ttc = 0.0
logger.info(
f"✅ ✅ ✅ DEVIS CRÉÉ: {numero_devis} - {total_ttc}€ TTC ✅ ✅ ✅"
f"✅✅✅ DEVIS CRÉÉ: {numero_devis} - {total_ttc}€ TTC "
f"(statut={statut_final}) ✅✅✅"
)
return {
@ -1561,10 +1448,9 @@ class SageConnector:
"total_ht": total_ht,
"total_ttc": total_ttc,
"nb_lignes": len(devis_data["lignes"]),
"client_code": client_code_final,
"date_devis": (
str(date_finale) if date_finale else str(date_obj.date())
),
"client_code": devis_data["client"]["code"],
"date_devis": str(date_obj.date()),
"statut": statut_final, # ✅ AJOUT
}
except Exception as e:
@ -1577,7 +1463,7 @@ class SageConnector:
raise
except Exception as e:
logger.error(f"❌ ERREUR CRÉATION DEVIS: {e}", exc_info=True)
logger.error(f"❌ ERREUR CRÉATION DEVIS: {e}", exc_info=True)
raise RuntimeError(f"Échec création devis: {str(e)}")
# =========================================================================