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 # 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 Création de devis avec option brouillon/validé
SOLUTION FINALE: Utilisation de SetDefaultArticle()
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: if not self.cial:
raise RuntimeError("Connexion Sage non établie") raise RuntimeError("Connexion Sage non établie")
logger.info( 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: try:
@ -1258,7 +1264,7 @@ class SageConnector:
doc.DO_Date = pywintypes.Time(date_obj) doc.DO_Date = pywintypes.Time(date_obj)
logger.info(f"📅 Date définie: {date_obj.date()}") 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 factory_client = self.cial.CptaApplication.FactoryClient
persist_client = factory_client.ReadNumero( persist_client = factory_client.ReadNumero(
devis_data["client"]["code"] devis_data["client"]["code"]
@ -1275,14 +1281,13 @@ class SageConnector:
f"❌ Impossible de charger le client {devis_data['client']['code']}" f"❌ Impossible de charger le client {devis_data['client']['code']}"
) )
# ✅ CRITIQUE: Associer le client au document
doc.SetDefaultClient(client_obj) doc.SetDefaultClient(client_obj)
doc.Write() doc.Write()
logger.info( logger.info(
f"👤 Client {devis_data['client']['code']} associé et document écrit" f"👤 Client {devis_data['client']['code']} associé et document écrit"
) )
# ===== LIGNES AVEC SetDefaultArticle() ===== # ===== LIGNES =====
try: try:
factory_lignes = doc.FactoryDocumentLigne factory_lignes = doc.FactoryDocumentLigne
except: except:
@ -1297,7 +1302,7 @@ class SageConnector:
f"--- Ligne {idx}: {ligne_data['article_code']} ---" 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( persist_article = factory_article.ReadReference(
ligne_data["article_code"] ligne_data["article_code"]
) )
@ -1312,17 +1317,10 @@ class SageConnector:
) )
article_obj.Read() article_obj.Read()
# 💰 ÉTAPE 2: Récupérer le prix de vente RÉEL
prix_sage = float(getattr(article_obj, "AR_PrixVen", 0.0)) prix_sage = float(getattr(article_obj, "AR_PrixVen", 0.0))
designation_sage = getattr(article_obj, "AR_Design", "") designation_sage = getattr(article_obj, "AR_Design", "")
logger.info(f"💰 Prix Sage: {prix_sage}")
if prix_sage == 0: # Créer la ligne
logger.warning(
f"⚠️ ATTENTION: Article {ligne_data['article_code']} a un prix de vente = 0€"
)
# 📝 ÉTAPE 3: Créer la ligne de devis
ligne_persist = factory_lignes.Create() ligne_persist = factory_lignes.Create()
try: try:
@ -1334,114 +1332,75 @@ class SageConnector:
ligne_persist, "IBODocumentVenteLigne3" ligne_persist, "IBODocumentVenteLigne3"
) )
# ✅✅✅ SOLUTION FINALE: SetDefaultArticleReference avec 2 paramètres ✅✅✅ # Associer article
quantite = float(ligne_data["quantite"]) quantite = float(ligne_data["quantite"])
try: try:
# Méthode 1: Via référence (plus simple et plus fiable)
ligne_obj.SetDefaultArticleReference( ligne_obj.SetDefaultArticleReference(
ligne_data["article_code"], quantite ligne_data["article_code"], quantite
) )
logger.info( except:
f"✅ Article associé via SetDefaultArticleReference('{ligne_data['article_code']}', {quantite})"
)
except Exception as e:
logger.warning(
f"⚠️ SetDefaultArticleReference échoué: {e}, tentative avec objet article"
)
try: try:
# Méthode 2: Via objet article
ligne_obj.SetDefaultArticle(article_obj, quantite) ligne_obj.SetDefaultArticle(article_obj, quantite)
logger.info( except:
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
ligne_obj.DL_Design = ( ligne_obj.DL_Design = (
designation_sage or ligne_data["designation"] designation_sage or ligne_data.get("designation", "")
) )
ligne_obj.DL_Qte = quantite ligne_obj.DL_Qte = quantite
logger.warning("⚠️ Configuration manuelle appliquée")
# ⚙️ ÉTAPE 4: Vérifier le prix automatique chargé # Prix
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_a_utiliser = ligne_data.get("prix_unitaire_ht") prix_a_utiliser = ligne_data.get("prix_unitaire_ht")
if prix_a_utiliser:
if prix_a_utiliser is not None and prix_a_utiliser > 0:
# Prix personnalisé fourni
ligne_obj.DL_PrixUnitaire = float(prix_a_utiliser) ligne_obj.DL_PrixUnitaire = float(prix_a_utiliser)
logger.info(f"💰 Prix personnalisé: {prix_a_utiliser}") elif prix_sage > 0:
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']}"
)
ligne_obj.DL_PrixUnitaire = float(prix_sage) 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)) # Remise
montant_ligne = quantite * prix_final
logger.info(f"{quantite} x {prix_final}€ = {montant_ligne}")
# 🎁 Remise
remise = ligne_data.get("remise_pourcentage", 0) remise = ligne_data.get("remise_pourcentage", 0)
if remise > 0: if remise > 0:
try: try:
ligne_obj.DL_Remise01REM_Valeur = float(remise) ligne_obj.DL_Remise01REM_Valeur = float(remise)
ligne_obj.DL_Remise01REM_Type = 0 ligne_obj.DL_Remise01REM_Type = 0
montant_apres_remise = montant_ligne * ( except:
1 - remise / 100 pass
)
logger.info(
f"🎁 Remise {remise}% → {montant_apres_remise}"
)
except Exception as e:
logger.warning(f"⚠️ Remise non appliquée: {e}")
# 💾 ÉTAPE 6: Écrire la ligne
ligne_obj.Write() ligne_obj.Write()
logger.info(f"✅ Ligne {idx} écrite") logger.info(f"✅ Ligne {idx} écrite")
# 🔍 VÉRIFICATION: Relire la ligne pour confirmer # ===== STATUT ET VALIDATION =====
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...")
doc.Write() doc.Write()
logger.info("🔄 Lancement du traitement (Process)...") # 🔑 DIFFÉRENCE CRITIQUE ICI
process.Process() 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 ===== # ===== RÉCUPÉRATION NUMÉRO =====
time.sleep(2)
numero_devis = None numero_devis = None
try: try:
doc_result = process.DocumentResult doc_result = process.DocumentResult
@ -1451,109 +1410,37 @@ class SageConnector:
) )
doc_result.Read() doc_result.Read()
numero_devis = getattr(doc_result, "DO_Piece", "") numero_devis = getattr(doc_result, "DO_Piece", "")
logger.info( except:
f"📄 Numéro (via DocumentResult): {numero_devis}" pass
)
except Exception as e:
logger.warning(f"⚠️ DocumentResult non accessible: {e}")
if not numero_devis: if not numero_devis:
doc.Read()
numero_devis = getattr(doc, "DO_Piece", "") numero_devis = getattr(doc, "DO_Piece", "")
logger.info(f"📄 Numéro (via Document): {numero_devis}")
if not numero_devis: if not numero_devis:
raise RuntimeError("❌ Numéro devis vide après création") 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 ===== # ===== RELECTURE COMPLÈTE =====
logger.info("🔍 Relecture complète du document...") logger.info("🔍 Relecture complète du document...")
factory_doc = self.cial.FactoryDocumentVente factory_doc = self.cial.FactoryDocumentVente
persist_reread = factory_doc.ReadPiece(0, numero_devis) persist_reread = factory_doc.ReadPiece(0, numero_devis)
if not persist_reread: if persist_reread:
logger.error(f"❌ Impossible de relire le devis {numero_devis}") doc_final = win32com.client.CastTo(
# Fallback: retourner les totaux calculés persist_reread, "IBODocumentVente3"
total_calcule = sum(
l.get("montant_ligne_ht", 0) for l in devis_data["lignes"]
) )
logger.warning(f"⚠️ Utilisation total calculé: {total_calcule}") doc_final.Read()
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()),
}
doc_final = win32com.client.CastTo( total_ht = float(getattr(doc_final, "DO_TotalHT", 0.0))
persist_reread, "IBODocumentVente3" total_ttc = float(getattr(doc_final, "DO_TotalTTC", 0.0))
) statut_final = getattr(doc_final, "DO_Statut", 0)
doc_final.Read() else:
total_ht = 0.0
# ===== EXTRACTION TOTAUX ===== total_ttc = 0.0
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}"
)
logger.info( logger.info(
f"✅ ✅ ✅ DEVIS CRÉÉ: {numero_devis} - {total_ttc}€ TTC ✅ ✅ ✅" f"✅✅✅ DEVIS CRÉÉ: {numero_devis} - {total_ttc}€ TTC "
f"(statut={statut_final}) ✅✅✅"
) )
return { return {
@ -1561,10 +1448,9 @@ class SageConnector:
"total_ht": total_ht, "total_ht": total_ht,
"total_ttc": total_ttc, "total_ttc": total_ttc,
"nb_lignes": len(devis_data["lignes"]), "nb_lignes": len(devis_data["lignes"]),
"client_code": client_code_final, "client_code": devis_data["client"]["code"],
"date_devis": ( "date_devis": str(date_obj.date()),
str(date_finale) if date_finale else str(date_obj.date()) "statut": statut_final, # ✅ AJOUT
),
} }
except Exception as e: except Exception as e:
@ -1577,7 +1463,7 @@ class SageConnector:
raise raise
except Exception as e: 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)}") raise RuntimeError(f"Échec création devis: {str(e)}")
# ========================================================================= # =========================================================================