refactor: Add date to datetime import and apply minor formatting adjustments.
This commit is contained in:
parent
efa4edcae0
commit
96c9c5e7df
1 changed files with 520 additions and 344 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
import win32com.client
|
import win32com.client
|
||||||
import pythoncom # AJOUT CRITIQUE
|
import pythoncom # AJOUT CRITIQUE
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, date
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
@ -9,6 +9,7 @@ from contextlib import contextmanager
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class SageConnector:
|
class SageConnector:
|
||||||
"""
|
"""
|
||||||
Connecteur Sage 100c avec gestion COM threading correcte
|
Connecteur Sage 100c avec gestion COM threading correcte
|
||||||
|
|
@ -63,11 +64,13 @@ class SageConnector:
|
||||||
Chaque thread doit initialiser COM avant d'utiliser les objets Sage.
|
Chaque thread doit initialiser COM avant d'utiliser les objets Sage.
|
||||||
"""
|
"""
|
||||||
# Vérifier si COM est déjà initialisé pour ce thread
|
# Vérifier si COM est déjà initialisé pour ce thread
|
||||||
if not hasattr(self._thread_local, 'com_initialized'):
|
if not hasattr(self._thread_local, "com_initialized"):
|
||||||
try:
|
try:
|
||||||
pythoncom.CoInitialize()
|
pythoncom.CoInitialize()
|
||||||
self._thread_local.com_initialized = True
|
self._thread_local.com_initialized = True
|
||||||
logger.debug(f"COM initialisé pour thread {threading.current_thread().name}")
|
logger.debug(
|
||||||
|
f"COM initialisé pour thread {threading.current_thread().name}"
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur initialisation COM: {e}")
|
logger.error(f"Erreur initialisation COM: {e}")
|
||||||
raise
|
raise
|
||||||
|
|
@ -80,11 +83,13 @@ class SageConnector:
|
||||||
|
|
||||||
def _cleanup_com_thread(self):
|
def _cleanup_com_thread(self):
|
||||||
"""Nettoie COM pour le thread actuel (à appeler à la fin)"""
|
"""Nettoie COM pour le thread actuel (à appeler à la fin)"""
|
||||||
if hasattr(self._thread_local, 'com_initialized'):
|
if hasattr(self._thread_local, "com_initialized"):
|
||||||
try:
|
try:
|
||||||
pythoncom.CoUninitialize()
|
pythoncom.CoUninitialize()
|
||||||
delattr(self._thread_local, 'com_initialized')
|
delattr(self._thread_local, "com_initialized")
|
||||||
logger.debug(f"COM nettoyé pour thread {threading.current_thread().name}")
|
logger.debug(
|
||||||
|
f"COM nettoyé pour thread {threading.current_thread().name}"
|
||||||
|
)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -96,7 +101,9 @@ class SageConnector:
|
||||||
"""Connexion initiale à Sage"""
|
"""Connexion initiale à Sage"""
|
||||||
try:
|
try:
|
||||||
with self._com_context():
|
with self._com_context():
|
||||||
self.cial = win32com.client.gencache.EnsureDispatch("Objets100c.Cial.Stream")
|
self.cial = win32com.client.gencache.EnsureDispatch(
|
||||||
|
"Objets100c.Cial.Stream"
|
||||||
|
)
|
||||||
self.cial.Name = self.chemin_base
|
self.cial.Name = self.chemin_base
|
||||||
self.cial.Loggable.UserName = self.utilisateur
|
self.cial.Loggable.UserName = self.utilisateur
|
||||||
self.cial.Loggable.UserPwd = self.mot_de_passe
|
self.cial.Loggable.UserPwd = self.mot_de_passe
|
||||||
|
|
@ -108,7 +115,9 @@ class SageConnector:
|
||||||
logger.info("Chargement initial du cache...")
|
logger.info("Chargement initial du cache...")
|
||||||
self._refresh_cache_clients()
|
self._refresh_cache_clients()
|
||||||
self._refresh_cache_articles()
|
self._refresh_cache_articles()
|
||||||
logger.info(f"Cache initialisé: {len(self._cache_clients)} clients, {len(self._cache_articles)} articles")
|
logger.info(
|
||||||
|
f"Cache initialisé: {len(self._cache_clients)} clients, {len(self._cache_articles)} articles"
|
||||||
|
)
|
||||||
|
|
||||||
# Démarrage du thread d'actualisation
|
# Démarrage du thread d'actualisation
|
||||||
self._start_refresh_thread()
|
self._start_refresh_thread()
|
||||||
|
|
@ -140,6 +149,7 @@ class SageConnector:
|
||||||
|
|
||||||
def _start_refresh_thread(self):
|
def _start_refresh_thread(self):
|
||||||
"""Démarre le thread d'actualisation automatique"""
|
"""Démarre le thread d'actualisation automatique"""
|
||||||
|
|
||||||
def refresh_loop():
|
def refresh_loop():
|
||||||
# Initialiser COM pour ce thread worker
|
# Initialiser COM pour ce thread worker
|
||||||
pythoncom.CoInitialize()
|
pythoncom.CoInitialize()
|
||||||
|
|
@ -152,20 +162,26 @@ class SageConnector:
|
||||||
if self._cache_clients_last_update:
|
if self._cache_clients_last_update:
|
||||||
age = datetime.now() - self._cache_clients_last_update
|
age = datetime.now() - self._cache_clients_last_update
|
||||||
if age.total_seconds() > self._cache_ttl_minutes * 60:
|
if age.total_seconds() > self._cache_ttl_minutes * 60:
|
||||||
logger.info(f"Actualisation cache clients (âge: {age.seconds//60}min)")
|
logger.info(
|
||||||
|
f"Actualisation cache clients (âge: {age.seconds//60}min)"
|
||||||
|
)
|
||||||
self._refresh_cache_clients()
|
self._refresh_cache_clients()
|
||||||
|
|
||||||
# Articles
|
# Articles
|
||||||
if self._cache_articles_last_update:
|
if self._cache_articles_last_update:
|
||||||
age = datetime.now() - self._cache_articles_last_update
|
age = datetime.now() - self._cache_articles_last_update
|
||||||
if age.total_seconds() > self._cache_ttl_minutes * 60:
|
if age.total_seconds() > self._cache_ttl_minutes * 60:
|
||||||
logger.info(f"Actualisation cache articles (âge: {age.seconds//60}min)")
|
logger.info(
|
||||||
|
f"Actualisation cache articles (âge: {age.seconds//60}min)"
|
||||||
|
)
|
||||||
self._refresh_cache_articles()
|
self._refresh_cache_articles()
|
||||||
finally:
|
finally:
|
||||||
# Nettoyer COM en fin de thread
|
# Nettoyer COM en fin de thread
|
||||||
pythoncom.CoUninitialize()
|
pythoncom.CoUninitialize()
|
||||||
|
|
||||||
self._refresh_thread = threading.Thread(target=refresh_loop, daemon=True, name="SageCacheRefresh")
|
self._refresh_thread = threading.Thread(
|
||||||
|
target=refresh_loop, daemon=True, name="SageCacheRefresh"
|
||||||
|
)
|
||||||
self._refresh_thread.start()
|
self._refresh_thread.start()
|
||||||
|
|
||||||
def _refresh_cache_clients(self):
|
def _refresh_cache_clients(self):
|
||||||
|
|
@ -193,7 +209,7 @@ class SageConnector:
|
||||||
if obj:
|
if obj:
|
||||||
data = self._extraire_client(obj)
|
data = self._extraire_client(obj)
|
||||||
clients.append(data)
|
clients.append(data)
|
||||||
clients_dict[data['numero']] = data
|
clients_dict[data["numero"]] = data
|
||||||
erreurs_consecutives = 0
|
erreurs_consecutives = 0
|
||||||
|
|
||||||
index += 1
|
index += 1
|
||||||
|
|
@ -202,7 +218,9 @@ class SageConnector:
|
||||||
erreurs_consecutives += 1
|
erreurs_consecutives += 1
|
||||||
index += 1
|
index += 1
|
||||||
if erreurs_consecutives >= max_erreurs:
|
if erreurs_consecutives >= max_erreurs:
|
||||||
logger.warning(f"Arrêt refresh clients après {max_erreurs} erreurs")
|
logger.warning(
|
||||||
|
f"Arrêt refresh clients après {max_erreurs} erreurs"
|
||||||
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
with self._lock_clients:
|
with self._lock_clients:
|
||||||
|
|
@ -240,7 +258,7 @@ class SageConnector:
|
||||||
if obj:
|
if obj:
|
||||||
data = self._extraire_article(obj)
|
data = self._extraire_article(obj)
|
||||||
articles.append(data)
|
articles.append(data)
|
||||||
articles_dict[data['reference']] = data
|
articles_dict[data["reference"]] = data
|
||||||
erreurs_consecutives = 0
|
erreurs_consecutives = 0
|
||||||
|
|
||||||
index += 1
|
index += 1
|
||||||
|
|
@ -249,7 +267,9 @@ class SageConnector:
|
||||||
erreurs_consecutives += 1
|
erreurs_consecutives += 1
|
||||||
index += 1
|
index += 1
|
||||||
if erreurs_consecutives >= max_erreurs:
|
if erreurs_consecutives >= max_erreurs:
|
||||||
logger.warning(f"Arrêt refresh articles après {max_erreurs} erreurs")
|
logger.warning(
|
||||||
|
f"Arrêt refresh articles après {max_erreurs} erreurs"
|
||||||
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
with self._lock_articles:
|
with self._lock_articles:
|
||||||
|
|
@ -274,9 +294,10 @@ class SageConnector:
|
||||||
|
|
||||||
filtre_lower = filtre.lower()
|
filtre_lower = filtre.lower()
|
||||||
return [
|
return [
|
||||||
c for c in self._cache_clients
|
c
|
||||||
if filtre_lower in c['numero'].lower() or
|
for c in self._cache_clients
|
||||||
filtre_lower in c['intitule'].lower()
|
if filtre_lower in c["numero"].lower()
|
||||||
|
or filtre_lower in c["intitule"].lower()
|
||||||
]
|
]
|
||||||
|
|
||||||
def lire_client(self, code_client):
|
def lire_client(self, code_client):
|
||||||
|
|
@ -292,9 +313,10 @@ class SageConnector:
|
||||||
|
|
||||||
filtre_lower = filtre.lower()
|
filtre_lower = filtre.lower()
|
||||||
return [
|
return [
|
||||||
a for a in self._cache_articles
|
a
|
||||||
if filtre_lower in a['reference'].lower() or
|
for a in self._cache_articles
|
||||||
filtre_lower in a['designation'].lower()
|
if filtre_lower in a["reference"].lower()
|
||||||
|
or filtre_lower in a["designation"].lower()
|
||||||
]
|
]
|
||||||
|
|
||||||
def lire_article(self, reference):
|
def lire_article(self, reference):
|
||||||
|
|
@ -315,15 +337,37 @@ class SageConnector:
|
||||||
return {
|
return {
|
||||||
"clients": {
|
"clients": {
|
||||||
"count": len(self._cache_clients),
|
"count": len(self._cache_clients),
|
||||||
"last_update": self._cache_clients_last_update.isoformat() if self._cache_clients_last_update else None,
|
"last_update": (
|
||||||
"age_minutes": (datetime.now() - self._cache_clients_last_update).total_seconds() / 60 if self._cache_clients_last_update else None
|
self._cache_clients_last_update.isoformat()
|
||||||
|
if self._cache_clients_last_update
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
"age_minutes": (
|
||||||
|
(
|
||||||
|
datetime.now() - self._cache_clients_last_update
|
||||||
|
).total_seconds()
|
||||||
|
/ 60
|
||||||
|
if self._cache_clients_last_update
|
||||||
|
else None
|
||||||
|
),
|
||||||
},
|
},
|
||||||
"articles": {
|
"articles": {
|
||||||
"count": len(self._cache_articles),
|
"count": len(self._cache_articles),
|
||||||
"last_update": self._cache_articles_last_update.isoformat() if self._cache_articles_last_update else None,
|
"last_update": (
|
||||||
"age_minutes": (datetime.now() - self._cache_articles_last_update).total_seconds() / 60 if self._cache_articles_last_update else None
|
self._cache_articles_last_update.isoformat()
|
||||||
|
if self._cache_articles_last_update
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
"age_minutes": (
|
||||||
|
(
|
||||||
|
datetime.now() - self._cache_articles_last_update
|
||||||
|
).total_seconds()
|
||||||
|
/ 60
|
||||||
|
if self._cache_articles_last_update
|
||||||
|
else None
|
||||||
|
),
|
||||||
},
|
},
|
||||||
"ttl_minutes": self._cache_ttl_minutes
|
"ttl_minutes": self._cache_ttl_minutes,
|
||||||
}
|
}
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
|
|
@ -354,7 +398,7 @@ class SageConnector:
|
||||||
data = {
|
data = {
|
||||||
"numero": getattr(client_obj, "CT_Num", ""),
|
"numero": getattr(client_obj, "CT_Num", ""),
|
||||||
"intitule": getattr(client_obj, "CT_Intitule", ""),
|
"intitule": getattr(client_obj, "CT_Intitule", ""),
|
||||||
"type": getattr(client_obj, "CT_Type", 0)
|
"type": getattr(client_obj, "CT_Type", 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -383,7 +427,7 @@ class SageConnector:
|
||||||
"prix_vente": getattr(article_obj, "AR_PrixVen", 0.0),
|
"prix_vente": getattr(article_obj, "AR_PrixVen", 0.0),
|
||||||
"prix_achat": getattr(article_obj, "AR_PrixAch", 0.0),
|
"prix_achat": getattr(article_obj, "AR_PrixAch", 0.0),
|
||||||
"stock_reel": getattr(article_obj, "AR_Stock", 0.0),
|
"stock_reel": getattr(article_obj, "AR_Stock", 0.0),
|
||||||
"stock_mini": getattr(article_obj, "AR_StockMini", 0.0)
|
"stock_mini": getattr(article_obj, "AR_StockMini", 0.0),
|
||||||
}
|
}
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
|
|
@ -398,7 +442,9 @@ class SageConnector:
|
||||||
if not self.cial:
|
if not self.cial:
|
||||||
raise RuntimeError("Connexion Sage non établie")
|
raise RuntimeError("Connexion Sage non établie")
|
||||||
|
|
||||||
logger.info(f"🚀 Début création devis pour client {devis_data['client']['code']}")
|
logger.info(
|
||||||
|
f"🚀 Début création devis pour client {devis_data['client']['code']}"
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with self._com_context(), self._lock_com:
|
with self._com_context(), self._lock_com:
|
||||||
|
|
@ -425,13 +471,15 @@ class SageConnector:
|
||||||
# ===== DATE =====
|
# ===== DATE =====
|
||||||
import pywintypes
|
import pywintypes
|
||||||
|
|
||||||
if isinstance(devis_data['date_devis'], str):
|
if isinstance(devis_data["date_devis"], str):
|
||||||
try:
|
try:
|
||||||
date_obj = datetime.fromisoformat(devis_data['date_devis'])
|
date_obj = datetime.fromisoformat(devis_data["date_devis"])
|
||||||
except:
|
except:
|
||||||
date_obj = datetime.now()
|
date_obj = datetime.now()
|
||||||
elif isinstance(devis_data['date_devis'], date):
|
elif isinstance(devis_data["date_devis"], date):
|
||||||
date_obj = datetime.combine(devis_data['date_devis'], datetime.min.time())
|
date_obj = datetime.combine(
|
||||||
|
devis_data["date_devis"], datetime.min.time()
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
date_obj = datetime.now()
|
date_obj = datetime.now()
|
||||||
|
|
||||||
|
|
@ -440,19 +488,27 @@ class SageConnector:
|
||||||
|
|
||||||
# ===== CLIENT (CRITIQUE: Doit être défini AVANT les lignes) =====
|
# ===== CLIENT (CRITIQUE: Doit être défini AVANT les lignes) =====
|
||||||
factory_client = self.cial.CptaApplication.FactoryClient
|
factory_client = self.cial.CptaApplication.FactoryClient
|
||||||
persist_client = factory_client.ReadNumero(devis_data['client']['code'])
|
persist_client = factory_client.ReadNumero(
|
||||||
|
devis_data["client"]["code"]
|
||||||
|
)
|
||||||
|
|
||||||
if not persist_client:
|
if not persist_client:
|
||||||
raise ValueError(f"❌ Client {devis_data['client']['code']} introuvable")
|
raise ValueError(
|
||||||
|
f"❌ Client {devis_data['client']['code']} introuvable"
|
||||||
|
)
|
||||||
|
|
||||||
client_obj = self._cast_client(persist_client)
|
client_obj = self._cast_client(persist_client)
|
||||||
if not client_obj:
|
if not client_obj:
|
||||||
raise ValueError(f"❌ Impossible de charger le client {devis_data['client']['code']}")
|
raise ValueError(
|
||||||
|
f"❌ Impossible de charger le client {devis_data['client']['code']}"
|
||||||
|
)
|
||||||
|
|
||||||
# ✅ CRITIQUE: Associer le client au document
|
# ✅ CRITIQUE: Associer le client au document
|
||||||
doc.SetDefaultClient(client_obj)
|
doc.SetDefaultClient(client_obj)
|
||||||
doc.Write()
|
doc.Write()
|
||||||
logger.info(f"👤 Client {devis_data['client']['code']} associé et document écrit")
|
logger.info(
|
||||||
|
f"👤 Client {devis_data['client']['code']} associé et document écrit"
|
||||||
|
)
|
||||||
|
|
||||||
# ===== LIGNES AVEC SetDefaultArticle() =====
|
# ===== LIGNES AVEC SetDefaultArticle() =====
|
||||||
try:
|
try:
|
||||||
|
|
@ -464,16 +520,24 @@ class SageConnector:
|
||||||
|
|
||||||
logger.info(f"📦 Ajout de {len(devis_data['lignes'])} lignes...")
|
logger.info(f"📦 Ajout de {len(devis_data['lignes'])} lignes...")
|
||||||
|
|
||||||
for idx, ligne_data in enumerate(devis_data['lignes'], 1):
|
for idx, ligne_data in enumerate(devis_data["lignes"], 1):
|
||||||
logger.info(f"--- Ligne {idx}: {ligne_data['article_code']} ---")
|
logger.info(
|
||||||
|
f"--- Ligne {idx}: {ligne_data['article_code']} ---"
|
||||||
|
)
|
||||||
|
|
||||||
# 🔍 ÉTAPE 1: Charger l'article RÉEL depuis Sage pour le prix
|
# 🔍 ÉTAPE 1: Charger l'article RÉEL depuis Sage pour le prix
|
||||||
persist_article = factory_article.ReadReference(ligne_data['article_code'])
|
persist_article = factory_article.ReadReference(
|
||||||
|
ligne_data["article_code"]
|
||||||
|
)
|
||||||
|
|
||||||
if not persist_article:
|
if not persist_article:
|
||||||
raise ValueError(f"❌ Article {ligne_data['article_code']} introuvable dans Sage")
|
raise ValueError(
|
||||||
|
f"❌ Article {ligne_data['article_code']} introuvable dans Sage"
|
||||||
|
)
|
||||||
|
|
||||||
article_obj = win32com.client.CastTo(persist_article, "IBOArticle3")
|
article_obj = win32com.client.CastTo(
|
||||||
|
persist_article, "IBOArticle3"
|
||||||
|
)
|
||||||
article_obj.Read()
|
article_obj.Read()
|
||||||
|
|
||||||
# 💰 ÉTAPE 2: Récupérer le prix de vente RÉEL
|
# 💰 ÉTAPE 2: Récupérer le prix de vente RÉEL
|
||||||
|
|
@ -482,33 +546,51 @@ class SageConnector:
|
||||||
logger.info(f"💰 Prix Sage: {prix_sage}€")
|
logger.info(f"💰 Prix Sage: {prix_sage}€")
|
||||||
|
|
||||||
if prix_sage == 0:
|
if prix_sage == 0:
|
||||||
logger.warning(f"⚠️ ATTENTION: Article {ligne_data['article_code']} a un prix de vente = 0€")
|
logger.warning(
|
||||||
|
f"⚠️ ATTENTION: Article {ligne_data['article_code']} a un prix de vente = 0€"
|
||||||
|
)
|
||||||
|
|
||||||
# 📝 ÉTAPE 3: Créer la ligne de devis
|
# 📝 ÉTAPE 3: Créer la ligne de devis
|
||||||
ligne_persist = factory_lignes.Create()
|
ligne_persist = factory_lignes.Create()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ligne_obj = win32com.client.CastTo(ligne_persist, "IBODocumentLigne3")
|
ligne_obj = win32com.client.CastTo(
|
||||||
|
ligne_persist, "IBODocumentLigne3"
|
||||||
|
)
|
||||||
except:
|
except:
|
||||||
ligne_obj = win32com.client.CastTo(ligne_persist, "IBODocumentVenteLigne3")
|
ligne_obj = win32com.client.CastTo(
|
||||||
|
ligne_persist, "IBODocumentVenteLigne3"
|
||||||
|
)
|
||||||
|
|
||||||
# ✅✅✅ SOLUTION FINALE: SetDefaultArticleReference avec 2 paramètres ✅✅✅
|
# ✅✅✅ SOLUTION FINALE: SetDefaultArticleReference avec 2 paramètres ✅✅✅
|
||||||
quantite = float(ligne_data['quantite'])
|
quantite = float(ligne_data["quantite"])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Méthode 1: Via référence (plus simple et plus fiable)
|
# Méthode 1: Via référence (plus simple et plus fiable)
|
||||||
ligne_obj.SetDefaultArticleReference(ligne_data['article_code'], quantite)
|
ligne_obj.SetDefaultArticleReference(
|
||||||
logger.info(f"✅ Article associé via SetDefaultArticleReference('{ligne_data['article_code']}', {quantite})")
|
ligne_data["article_code"], quantite
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
f"✅ Article associé via SetDefaultArticleReference('{ligne_data['article_code']}', {quantite})"
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"⚠️ SetDefaultArticleReference échoué: {e}, tentative avec objet article")
|
logger.warning(
|
||||||
|
f"⚠️ SetDefaultArticleReference échoué: {e}, tentative avec objet article"
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
# Méthode 2: Via objet article
|
# Méthode 2: Via objet article
|
||||||
ligne_obj.SetDefaultArticle(article_obj, quantite)
|
ligne_obj.SetDefaultArticle(article_obj, quantite)
|
||||||
logger.info(f"✅ Article associé via SetDefaultArticle(obj, {quantite})")
|
logger.info(
|
||||||
|
f"✅ Article associé via SetDefaultArticle(obj, {quantite})"
|
||||||
|
)
|
||||||
except Exception as e2:
|
except Exception as e2:
|
||||||
logger.error(f"❌ Toutes les méthodes d'association ont échoué")
|
logger.error(
|
||||||
|
f"❌ Toutes les méthodes d'association ont échoué"
|
||||||
|
)
|
||||||
# Fallback: définir manuellement
|
# Fallback: définir manuellement
|
||||||
ligne_obj.DL_Design = designation_sage or ligne_data['designation']
|
ligne_obj.DL_Design = (
|
||||||
|
designation_sage or ligne_data["designation"]
|
||||||
|
)
|
||||||
ligne_obj.DL_Qte = quantite
|
ligne_obj.DL_Qte = quantite
|
||||||
logger.warning("⚠️ Configuration manuelle appliquée")
|
logger.warning("⚠️ Configuration manuelle appliquée")
|
||||||
|
|
||||||
|
|
@ -517,7 +599,7 @@ class SageConnector:
|
||||||
logger.info(f"💰 Prix auto chargé: {prix_auto}€")
|
logger.info(f"💰 Prix auto chargé: {prix_auto}€")
|
||||||
|
|
||||||
# 💵 ÉTAPE 5: Ajuster le prix si nécessaire
|
# 💵 É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 is not None and prix_a_utiliser > 0:
|
if prix_a_utiliser is not None and prix_a_utiliser > 0:
|
||||||
# Prix personnalisé fourni
|
# Prix personnalisé fourni
|
||||||
|
|
@ -526,7 +608,9 @@ class SageConnector:
|
||||||
elif prix_auto == 0:
|
elif prix_auto == 0:
|
||||||
# Pas de prix auto, forcer le prix Sage
|
# Pas de prix auto, forcer le prix Sage
|
||||||
if prix_sage == 0:
|
if prix_sage == 0:
|
||||||
raise ValueError(f"Prix nul pour article {ligne_data['article_code']}")
|
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}€")
|
logger.info(f"💰 Prix Sage forcé: {prix_sage}€")
|
||||||
else:
|
else:
|
||||||
|
|
@ -538,13 +622,17 @@ class SageConnector:
|
||||||
logger.info(f"✅ {quantite} x {prix_final}€ = {montant_ligne}€")
|
logger.info(f"✅ {quantite} x {prix_final}€ = {montant_ligne}€")
|
||||||
|
|
||||||
# 🎁 Remise
|
# 🎁 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 * (1 - remise / 100)
|
montant_apres_remise = montant_ligne * (
|
||||||
logger.info(f"🎁 Remise {remise}% → {montant_apres_remise}€")
|
1 - remise / 100
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
f"🎁 Remise {remise}% → {montant_apres_remise}€"
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"⚠️ Remise non appliquée: {e}")
|
logger.warning(f"⚠️ Remise non appliquée: {e}")
|
||||||
|
|
||||||
|
|
@ -555,12 +643,20 @@ class SageConnector:
|
||||||
# 🔍 VÉRIFICATION: Relire la ligne pour confirmer
|
# 🔍 VÉRIFICATION: Relire la ligne pour confirmer
|
||||||
try:
|
try:
|
||||||
ligne_obj.Read()
|
ligne_obj.Read()
|
||||||
prix_enregistre = float(getattr(ligne_obj, "DL_PrixUnitaire", 0.0))
|
prix_enregistre = float(
|
||||||
montant_enregistre = float(getattr(ligne_obj, "DL_MontantHT", 0.0))
|
getattr(ligne_obj, "DL_PrixUnitaire", 0.0)
|
||||||
logger.info(f"🔍 Vérif: Prix={prix_enregistre}€, Montant HT={montant_enregistre}€")
|
)
|
||||||
|
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:
|
if montant_enregistre == 0:
|
||||||
logger.error(f"❌ PROBLÈME: Montant enregistré = 0 pour ligne {idx}")
|
logger.error(
|
||||||
|
f"❌ PROBLÈME: Montant enregistré = 0 pour ligne {idx}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logger.info(f"✅ Ligne {idx} OK: {montant_enregistre}€")
|
logger.info(f"✅ Ligne {idx} OK: {montant_enregistre}€")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -578,10 +674,14 @@ class SageConnector:
|
||||||
try:
|
try:
|
||||||
doc_result = process.DocumentResult
|
doc_result = process.DocumentResult
|
||||||
if doc_result:
|
if doc_result:
|
||||||
doc_result = win32com.client.CastTo(doc_result, "IBODocumentVente3")
|
doc_result = win32com.client.CastTo(
|
||||||
|
doc_result, "IBODocumentVente3"
|
||||||
|
)
|
||||||
doc_result.Read()
|
doc_result.Read()
|
||||||
numero_devis = getattr(doc_result, "DO_Piece", "")
|
numero_devis = getattr(doc_result, "DO_Piece", "")
|
||||||
logger.info(f"📄 Numéro (via DocumentResult): {numero_devis}")
|
logger.info(
|
||||||
|
f"📄 Numéro (via DocumentResult): {numero_devis}"
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"⚠️ DocumentResult non accessible: {e}")
|
logger.warning(f"⚠️ DocumentResult non accessible: {e}")
|
||||||
|
|
||||||
|
|
@ -609,18 +709,22 @@ class SageConnector:
|
||||||
if not persist_reread:
|
if not persist_reread:
|
||||||
logger.error(f"❌ Impossible de relire le devis {numero_devis}")
|
logger.error(f"❌ Impossible de relire le devis {numero_devis}")
|
||||||
# Fallback: retourner les totaux calculés
|
# Fallback: retourner les totaux calculés
|
||||||
total_calcule = sum(l.get('montant_ligne_ht', 0) for l in devis_data['lignes'])
|
total_calcule = sum(
|
||||||
|
l.get("montant_ligne_ht", 0) for l in devis_data["lignes"]
|
||||||
|
)
|
||||||
logger.warning(f"⚠️ Utilisation total calculé: {total_calcule}€")
|
logger.warning(f"⚠️ Utilisation total calculé: {total_calcule}€")
|
||||||
return {
|
return {
|
||||||
"numero_devis": numero_devis,
|
"numero_devis": numero_devis,
|
||||||
"total_ht": total_calcule,
|
"total_ht": total_calcule,
|
||||||
"total_ttc": round(total_calcule * 1.20, 2),
|
"total_ttc": round(total_calcule * 1.20, 2),
|
||||||
"nb_lignes": len(devis_data['lignes']),
|
"nb_lignes": len(devis_data["lignes"]),
|
||||||
"client_code": devis_data['client']['code'],
|
"client_code": devis_data["client"]["code"],
|
||||||
"date_devis": str(date_obj.date())
|
"date_devis": str(date_obj.date()),
|
||||||
}
|
}
|
||||||
|
|
||||||
doc_final = win32com.client.CastTo(persist_reread, "IBODocumentVente3")
|
doc_final = win32com.client.CastTo(
|
||||||
|
persist_reread, "IBODocumentVente3"
|
||||||
|
)
|
||||||
doc_final.Read()
|
doc_final.Read()
|
||||||
|
|
||||||
# ===== EXTRACTION TOTAUX =====
|
# ===== EXTRACTION TOTAUX =====
|
||||||
|
|
@ -650,11 +754,17 @@ class SageConnector:
|
||||||
if ligne_p is None:
|
if ligne_p is None:
|
||||||
break
|
break
|
||||||
|
|
||||||
ligne_verif = win32com.client.CastTo(ligne_p, "IBODocumentLigne3")
|
ligne_verif = win32com.client.CastTo(
|
||||||
|
ligne_p, "IBODocumentLigne3"
|
||||||
|
)
|
||||||
ligne_verif.Read()
|
ligne_verif.Read()
|
||||||
|
|
||||||
montant = float(getattr(ligne_verif, "DL_MontantHT", 0.0))
|
montant = float(
|
||||||
logger.info(f" Ligne {index}: Montant HT = {montant}€")
|
getattr(ligne_verif, "DL_MontantHT", 0.0)
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
f" Ligne {index}: Montant HT = {montant}€"
|
||||||
|
)
|
||||||
total_calcule += montant
|
total_calcule += montant
|
||||||
|
|
||||||
index += 1
|
index += 1
|
||||||
|
|
@ -666,17 +776,23 @@ class SageConnector:
|
||||||
if total_calcule > 0:
|
if total_calcule > 0:
|
||||||
total_ht = total_calcule
|
total_ht = total_calcule
|
||||||
total_ttc = round(total_ht * 1.20, 2)
|
total_ttc = round(total_ht * 1.20, 2)
|
||||||
logger.info(f"✅ Correction appliquée: HT={total_ht}€, TTC={total_ttc}€")
|
logger.info(
|
||||||
|
f"✅ Correction appliquée: HT={total_ht}€, TTC={total_ttc}€"
|
||||||
|
)
|
||||||
|
|
||||||
logger.info(f"✅ ✅ ✅ DEVIS CRÉÉ: {numero_devis} - {total_ttc}€ TTC ✅ ✅ ✅")
|
logger.info(
|
||||||
|
f"✅ ✅ ✅ DEVIS CRÉÉ: {numero_devis} - {total_ttc}€ TTC ✅ ✅ ✅"
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"numero_devis": numero_devis,
|
"numero_devis": numero_devis,
|
||||||
"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": client_code_final,
|
||||||
"date_devis": str(date_finale) if date_finale else str(date_obj.date())
|
"date_devis": (
|
||||||
|
str(date_finale) if date_finale else str(date_obj.date())
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -720,11 +836,15 @@ class SageConnector:
|
||||||
if persist_test is None:
|
if persist_test is None:
|
||||||
break
|
break
|
||||||
|
|
||||||
doc_test = win32com.client.CastTo(persist_test, "IBODocumentVente3")
|
doc_test = win32com.client.CastTo(
|
||||||
|
persist_test, "IBODocumentVente3"
|
||||||
|
)
|
||||||
doc_test.Read()
|
doc_test.Read()
|
||||||
|
|
||||||
if (getattr(doc_test, "DO_Type", -1) == 0 and
|
if (
|
||||||
getattr(doc_test, "DO_Piece", "") == numero_devis):
|
getattr(doc_test, "DO_Type", -1) == 0
|
||||||
|
and getattr(doc_test, "DO_Piece", "") == numero_devis
|
||||||
|
):
|
||||||
persist = persist_test
|
persist = persist_test
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
@ -749,7 +869,9 @@ class SageConnector:
|
||||||
client_obj.Read()
|
client_obj.Read()
|
||||||
client_code = getattr(client_obj, "CT_Num", "").strip()
|
client_code = getattr(client_obj, "CT_Num", "").strip()
|
||||||
client_intitule = getattr(client_obj, "CT_Intitule", "").strip()
|
client_intitule = getattr(client_obj, "CT_Intitule", "").strip()
|
||||||
logger.debug(f"Client chargé via .Client: {client_code} - {client_intitule}")
|
logger.debug(
|
||||||
|
f"Client chargé via .Client: {client_code} - {client_intitule}"
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"Erreur chargement client: {e}")
|
logger.debug(f"Erreur chargement client: {e}")
|
||||||
|
|
||||||
|
|
@ -757,7 +879,7 @@ class SageConnector:
|
||||||
if client_code:
|
if client_code:
|
||||||
client_obj_cache = self.lire_client(client_code)
|
client_obj_cache = self.lire_client(client_code)
|
||||||
if client_obj_cache:
|
if client_obj_cache:
|
||||||
client_intitule = client_obj_cache.get('intitule', '')
|
client_intitule = client_obj_cache.get("intitule", "")
|
||||||
|
|
||||||
devis = {
|
devis = {
|
||||||
"numero": getattr(doc, "DO_Piece", ""),
|
"numero": getattr(doc, "DO_Piece", ""),
|
||||||
|
|
@ -767,7 +889,7 @@ class SageConnector:
|
||||||
"total_ht": float(getattr(doc, "DO_TotalHT", 0.0)),
|
"total_ht": float(getattr(doc, "DO_TotalHT", 0.0)),
|
||||||
"total_ttc": float(getattr(doc, "DO_TotalTTC", 0.0)),
|
"total_ttc": float(getattr(doc, "DO_TotalTTC", 0.0)),
|
||||||
"statut": getattr(doc, "DO_Statut", 0),
|
"statut": getattr(doc, "DO_Statut", 0),
|
||||||
"lignes": []
|
"lignes": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Lecture des lignes
|
# Lecture des lignes
|
||||||
|
|
@ -783,7 +905,9 @@ class SageConnector:
|
||||||
if ligne_persist is None:
|
if ligne_persist is None:
|
||||||
break
|
break
|
||||||
|
|
||||||
ligne = win32com.client.CastTo(ligne_persist, "IBODocumentLigne3")
|
ligne = win32com.client.CastTo(
|
||||||
|
ligne_persist, "IBODocumentLigne3"
|
||||||
|
)
|
||||||
ligne.Read()
|
ligne.Read()
|
||||||
|
|
||||||
# ✅✅✅ CHARGEMENT ARTICLE VIA .Article ✅✅✅
|
# ✅✅✅ CHARGEMENT ARTICLE VIA .Article ✅✅✅
|
||||||
|
|
@ -798,25 +922,39 @@ class SageConnector:
|
||||||
article_obj = getattr(ligne, "Article", None)
|
article_obj = getattr(ligne, "Article", None)
|
||||||
if article_obj:
|
if article_obj:
|
||||||
article_obj.Read()
|
article_obj.Read()
|
||||||
article_ref = getattr(article_obj, "AR_Ref", "").strip()
|
article_ref = getattr(
|
||||||
logger.debug(f"Article chargé via .Article: {article_ref}")
|
article_obj, "AR_Ref", ""
|
||||||
|
).strip()
|
||||||
|
logger.debug(
|
||||||
|
f"Article chargé via .Article: {article_ref}"
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"Erreur chargement article ligne {index}: {e}")
|
logger.debug(
|
||||||
|
f"Erreur chargement article ligne {index}: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
devis["lignes"].append({
|
devis["lignes"].append(
|
||||||
"article": article_ref,
|
{
|
||||||
"designation": getattr(ligne, "DL_Design", ""),
|
"article": article_ref,
|
||||||
"quantite": float(getattr(ligne, "DL_Qte", 0.0)),
|
"designation": getattr(ligne, "DL_Design", ""),
|
||||||
"prix_unitaire": float(getattr(ligne, "DL_PrixUnitaire", 0.0)),
|
"quantite": float(getattr(ligne, "DL_Qte", 0.0)),
|
||||||
"montant_ht": float(getattr(ligne, "DL_MontantHT", 0.0))
|
"prix_unitaire": float(
|
||||||
})
|
getattr(ligne, "DL_PrixUnitaire", 0.0)
|
||||||
|
),
|
||||||
|
"montant_ht": float(
|
||||||
|
getattr(ligne, "DL_MontantHT", 0.0)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
index += 1
|
index += 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"Erreur lecture ligne {index}: {e}")
|
logger.debug(f"Erreur lecture ligne {index}: {e}")
|
||||||
break
|
break
|
||||||
|
|
||||||
logger.info(f"✅ Devis {numero_devis} lu: {len(devis['lignes'])} lignes, {devis['total_ttc']:.2f}€, client: {client_intitule}")
|
logger.info(
|
||||||
|
f"✅ Devis {numero_devis} lu: {len(devis['lignes'])} lignes, {devis['total_ttc']:.2f}€, client: {client_intitule}"
|
||||||
|
)
|
||||||
return devis
|
return devis
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -856,12 +994,14 @@ class SageConnector:
|
||||||
ligne = win32com.client.CastTo(ligne_p, "IBODocumentLigne3")
|
ligne = win32com.client.CastTo(ligne_p, "IBODocumentLigne3")
|
||||||
ligne.Read()
|
ligne.Read()
|
||||||
|
|
||||||
lignes.append({
|
lignes.append(
|
||||||
"designation": getattr(ligne, "DL_Design", ""),
|
{
|
||||||
"quantite": getattr(ligne, "DL_Qte", 0.0),
|
"designation": getattr(ligne, "DL_Design", ""),
|
||||||
"prix_unitaire": getattr(ligne, "DL_PrixUnitaire", 0.0),
|
"quantite": getattr(ligne, "DL_Qte", 0.0),
|
||||||
"montant_ht": getattr(ligne, "DL_MontantHT", 0.0)
|
"prix_unitaire": getattr(ligne, "DL_PrixUnitaire", 0.0),
|
||||||
})
|
"montant_ht": getattr(ligne, "DL_MontantHT", 0.0),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
index += 1
|
index += 1
|
||||||
except:
|
except:
|
||||||
|
|
@ -874,7 +1014,7 @@ class SageConnector:
|
||||||
"client_intitule": getattr(doc, "CT_Intitule", ""),
|
"client_intitule": getattr(doc, "CT_Intitule", ""),
|
||||||
"total_ht": getattr(doc, "DO_TotalHT", 0.0),
|
"total_ht": getattr(doc, "DO_TotalHT", 0.0),
|
||||||
"total_ttc": getattr(doc, "DO_TotalTTC", 0.0),
|
"total_ttc": getattr(doc, "DO_TotalTTC", 0.0),
|
||||||
"lignes": lignes
|
"lignes": lignes,
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f" Erreur lecture document: {e}")
|
logger.error(f" Erreur lecture document: {e}")
|
||||||
|
|
@ -884,7 +1024,6 @@ class SageConnector:
|
||||||
# TRANSFORMATION (US-A2)
|
# TRANSFORMATION (US-A2)
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
|
|
||||||
|
|
||||||
def transformer_document(self, numero_source, type_source, type_cible):
|
def transformer_document(self, numero_source, type_source, type_cible):
|
||||||
"""
|
"""
|
||||||
Transformation avec transaction
|
Transformation avec transaction
|
||||||
|
|
@ -916,7 +1055,9 @@ class SageConnector:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if not client_code:
|
if not client_code:
|
||||||
raise ValueError(f"Impossible de récupérer le client du document {numero_source}")
|
raise ValueError(
|
||||||
|
f"Impossible de récupérer le client du document {numero_source}"
|
||||||
|
)
|
||||||
|
|
||||||
# Transaction
|
# Transaction
|
||||||
transaction_active = False
|
transaction_active = False
|
||||||
|
|
@ -933,7 +1074,9 @@ class SageConnector:
|
||||||
doc_cible = process.Document
|
doc_cible = process.Document
|
||||||
|
|
||||||
try:
|
try:
|
||||||
doc_cible = win32com.client.CastTo(doc_cible, "IBODocumentVente3")
|
doc_cible = win32com.client.CastTo(
|
||||||
|
doc_cible, "IBODocumentVente3"
|
||||||
|
)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -945,7 +1088,9 @@ class SageConnector:
|
||||||
persist_client = factory_client.ReadNumero(client_code)
|
persist_client = factory_client.ReadNumero(client_code)
|
||||||
|
|
||||||
if persist_client:
|
if persist_client:
|
||||||
client_obj_cible = win32com.client.CastTo(persist_client, "IBOClient3")
|
client_obj_cible = win32com.client.CastTo(
|
||||||
|
persist_client, "IBOClient3"
|
||||||
|
)
|
||||||
client_obj_cible.Read()
|
client_obj_cible.Read()
|
||||||
doc_cible.SetDefaultClient(client_obj_cible)
|
doc_cible.SetDefaultClient(client_obj_cible)
|
||||||
doc_cible.Write()
|
doc_cible.Write()
|
||||||
|
|
@ -956,6 +1101,7 @@ class SageConnector:
|
||||||
|
|
||||||
# Date
|
# Date
|
||||||
import pywintypes
|
import pywintypes
|
||||||
|
|
||||||
doc_cible.DO_Date = pywintypes.Time(datetime.now())
|
doc_cible.DO_Date = pywintypes.Time(datetime.now())
|
||||||
|
|
||||||
# Référence
|
# Référence
|
||||||
|
|
@ -982,50 +1128,78 @@ class SageConnector:
|
||||||
if ligne_source_p is None:
|
if ligne_source_p is None:
|
||||||
break
|
break
|
||||||
|
|
||||||
ligne_source = win32com.client.CastTo(ligne_source_p, "IBODocumentLigne3")
|
ligne_source = win32com.client.CastTo(
|
||||||
|
ligne_source_p, "IBODocumentLigne3"
|
||||||
|
)
|
||||||
ligne_source.Read()
|
ligne_source.Read()
|
||||||
|
|
||||||
# Créer ligne cible
|
# Créer ligne cible
|
||||||
ligne_cible_p = factory_lignes_cible.Create()
|
ligne_cible_p = factory_lignes_cible.Create()
|
||||||
ligne_cible = win32com.client.CastTo(ligne_cible_p, "IBODocumentLigne3")
|
ligne_cible = win32com.client.CastTo(
|
||||||
|
ligne_cible_p, "IBODocumentLigne3"
|
||||||
|
)
|
||||||
|
|
||||||
# Récupérer référence article
|
# Récupérer référence article
|
||||||
article_ref = ""
|
article_ref = ""
|
||||||
try:
|
try:
|
||||||
article_ref = getattr(ligne_source, "AR_Ref", "").strip()
|
article_ref = getattr(
|
||||||
|
ligne_source, "AR_Ref", ""
|
||||||
|
).strip()
|
||||||
if not article_ref:
|
if not article_ref:
|
||||||
article_obj = getattr(ligne_source, "Article", None)
|
article_obj = getattr(ligne_source, "Article", None)
|
||||||
if article_obj:
|
if article_obj:
|
||||||
article_obj.Read()
|
article_obj.Read()
|
||||||
article_ref = getattr(article_obj, "AR_Ref", "").strip()
|
article_ref = getattr(
|
||||||
|
article_obj, "AR_Ref", ""
|
||||||
|
).strip()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Associer article si disponible
|
# Associer article si disponible
|
||||||
if article_ref:
|
if article_ref:
|
||||||
try:
|
try:
|
||||||
persist_article = factory_article.ReadReference(article_ref)
|
persist_article = factory_article.ReadReference(
|
||||||
|
article_ref
|
||||||
|
)
|
||||||
if persist_article:
|
if persist_article:
|
||||||
article_obj = win32com.client.CastTo(persist_article, "IBOArticle3")
|
article_obj = win32com.client.CastTo(
|
||||||
|
persist_article, "IBOArticle3"
|
||||||
|
)
|
||||||
article_obj.Read()
|
article_obj.Read()
|
||||||
|
|
||||||
quantite = float(getattr(ligne_source, "DL_Qte", 1.0))
|
quantite = float(
|
||||||
|
getattr(ligne_source, "DL_Qte", 1.0)
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ligne_cible.SetDefaultArticleReference(article_ref, quantite)
|
ligne_cible.SetDefaultArticleReference(
|
||||||
|
article_ref, quantite
|
||||||
|
)
|
||||||
except:
|
except:
|
||||||
ligne_cible.SetDefaultArticle(article_obj, quantite)
|
ligne_cible.SetDefaultArticle(
|
||||||
|
article_obj, quantite
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"Erreur association article {article_ref}: {e}")
|
logger.debug(
|
||||||
|
f"Erreur association article {article_ref}: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
# Copier propriétés
|
# Copier propriétés
|
||||||
ligne_cible.DL_Design = getattr(ligne_source, "DL_Design", "")
|
ligne_cible.DL_Design = getattr(
|
||||||
ligne_cible.DL_Qte = float(getattr(ligne_source, "DL_Qte", 0.0))
|
ligne_source, "DL_Design", ""
|
||||||
ligne_cible.DL_PrixUnitaire = float(getattr(ligne_source, "DL_PrixUnitaire", 0.0))
|
)
|
||||||
|
ligne_cible.DL_Qte = float(
|
||||||
|
getattr(ligne_source, "DL_Qte", 0.0)
|
||||||
|
)
|
||||||
|
ligne_cible.DL_PrixUnitaire = float(
|
||||||
|
getattr(ligne_source, "DL_PrixUnitaire", 0.0)
|
||||||
|
)
|
||||||
|
|
||||||
# Remise
|
# Remise
|
||||||
try:
|
try:
|
||||||
remise = float(getattr(ligne_source, "DL_Remise01REM_Valeur", 0.0))
|
remise = float(
|
||||||
|
getattr(ligne_source, "DL_Remise01REM_Valeur", 0.0)
|
||||||
|
)
|
||||||
if remise > 0:
|
if remise > 0:
|
||||||
ligne_cible.DL_Remise01REM_Valeur = remise
|
ligne_cible.DL_Remise01REM_Valeur = remise
|
||||||
ligne_cible.DL_Remise01REM_Type = 0
|
ligne_cible.DL_Remise01REM_Type = 0
|
||||||
|
|
@ -1065,13 +1239,15 @@ class SageConnector:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"Impossible de MAJ statut source: {e}")
|
logger.debug(f"Impossible de MAJ statut source: {e}")
|
||||||
|
|
||||||
logger.info(f"✅ Transformation: {numero_source} ({type_source}) → {numero_cible} ({type_cible})")
|
logger.info(
|
||||||
|
f"✅ Transformation: {numero_source} ({type_source}) → {numero_cible} ({type_cible})"
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"document_source": numero_source,
|
"document_source": numero_source,
|
||||||
"document_cible": numero_cible,
|
"document_cible": numero_cible,
|
||||||
"nb_lignes": nb_lignes
|
"nb_lignes": nb_lignes,
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -1163,7 +1339,7 @@ class SageConnector:
|
||||||
"client_intitule": getattr(client, "CT_Intitule", ""),
|
"client_intitule": getattr(client, "CT_Intitule", ""),
|
||||||
"email": None,
|
"email": None,
|
||||||
"nom": None,
|
"nom": None,
|
||||||
"telephone": None
|
"telephone": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Email principal depuis Telecom
|
# Email principal depuis Telecom
|
||||||
|
|
@ -1177,7 +1353,10 @@ class SageConnector:
|
||||||
|
|
||||||
# Nom du contact
|
# Nom du contact
|
||||||
try:
|
try:
|
||||||
contact_info["nom"] = getattr(client, "CT_Contact", "") or contact_info["client_intitule"]
|
contact_info["nom"] = (
|
||||||
|
getattr(client, "CT_Contact", "")
|
||||||
|
or contact_info["client_intitule"]
|
||||||
|
)
|
||||||
except:
|
except:
|
||||||
contact_info["nom"] = contact_info["client_intitule"]
|
contact_info["nom"] = contact_info["client_intitule"]
|
||||||
|
|
||||||
|
|
@ -1199,8 +1378,5 @@ class SageConnector:
|
||||||
"""
|
"""
|
||||||
date_relance = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
date_relance = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
return self.mettre_a_jour_champ_libre(
|
return self.mettre_a_jour_champ_libre(
|
||||||
doc_id,
|
doc_id, type_doc, "DerniereRelance", date_relance
|
||||||
type_doc,
|
|
||||||
"DerniereRelance",
|
|
||||||
date_relance
|
|
||||||
)
|
)
|
||||||
Loading…
Reference in a new issue