Trying again
This commit is contained in:
parent
68c70de7d9
commit
7d58a607f5
1 changed files with 234 additions and 267 deletions
|
|
@ -2,15 +2,22 @@
|
||||||
Validation de factures Sage 100c
|
Validation de factures Sage 100c
|
||||||
Module: utils/documents/validation.py (Windows Server)
|
Module: utils/documents/validation.py (Windows Server)
|
||||||
|
|
||||||
COM pour écriture, SQL pour lecture
|
COM pour écriture (late binding), SQL pour lecture
|
||||||
|
Solution: Contourner le cache gen_py avec late binding pur
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
import win32com.client
|
import win32com.client
|
||||||
|
import pythoncom
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Constantes COM pour Invoke
|
||||||
|
DISPATCH_PROPERTYPUT = 4
|
||||||
|
DISPATCH_PROPERTYGET = 2
|
||||||
|
DISPATCH_METHOD = 1
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# FONCTIONS SQL (LECTURE)
|
# FONCTIONS SQL (LECTURE)
|
||||||
|
|
@ -101,156 +108,202 @@ def _get_facture_info_sql(connector, numero_facture: str) -> Dict:
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# INTROSPECTION - Pour découvrir les méthodes de validation
|
# LATE BINDING - Contourner le cache gen_py
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
|
|
||||||
def introspecter_validation(connector, numero_facture: str = None) -> Dict:
|
def _get_dynamic_dispatch(com_object):
|
||||||
"""
|
"""
|
||||||
Introspection pour découvrir les méthodes de validation disponibles dans Sage.
|
Convertit un objet COM early-binding en late-binding dynamique.
|
||||||
|
Cela contourne le cache gen_py qui marque certaines propriétés en read-only.
|
||||||
Appeler cette fonction pour voir quelles méthodes/process sont disponibles
|
|
||||||
pour valider un document.
|
|
||||||
"""
|
"""
|
||||||
if not connector.cial:
|
|
||||||
raise RuntimeError("Connexion Sage non établie")
|
|
||||||
|
|
||||||
result = {}
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with connector._com_context(), connector._lock_com:
|
# Récupérer l'objet COM brut et créer un dispatch dynamique
|
||||||
# 1. Méthodes sur cial (BSCIALApplication100c)
|
oleobj = com_object._oleobj_
|
||||||
cial_attrs = [a for a in dir(connector.cial) if not a.startswith("_")]
|
return win32com.client.Dispatch(oleobj.QueryInterface(pythoncom.IID_IDispatch))
|
||||||
|
except Exception:
|
||||||
|
return com_object
|
||||||
|
|
||||||
# Filtrer les CreateProcess et méthodes liées à la validation
|
|
||||||
result["cial_createprocess"] = [
|
|
||||||
a
|
|
||||||
for a in cial_attrs
|
|
||||||
if "Process" in a or "Valider" in a or "Valid" in a
|
|
||||||
]
|
|
||||||
|
|
||||||
# 2. Si un numéro de facture est fourni, inspecter le document
|
def _set_property_via_invoke(com_object, prop_name: str, value) -> bool:
|
||||||
if numero_facture:
|
"""
|
||||||
factory = connector.cial.FactoryDocumentVente
|
Définit une propriété COM via Invoke direct (DISPATCH_PROPERTYPUT).
|
||||||
if factory.ExistPiece(60, numero_facture):
|
Contourne les restrictions du cache gen_py.
|
||||||
persist = factory.ReadPiece(60, numero_facture)
|
"""
|
||||||
|
try:
|
||||||
|
oleobj = com_object._oleobj_
|
||||||
|
# Récupérer le DISPID de la propriété
|
||||||
|
dispid = oleobj.GetIDsOfNames(0, prop_name)
|
||||||
|
if isinstance(dispid, tuple):
|
||||||
|
dispid = dispid[0]
|
||||||
|
|
||||||
# Attributs du persist brut
|
# Invoke avec PROPERTYPUT
|
||||||
persist_attrs = [a for a in dir(persist) if not a.startswith("_")]
|
oleobj.Invoke(dispid, 0, DISPATCH_PROPERTYPUT, True, value)
|
||||||
result["persist_attrs_validation"] = [
|
return True
|
||||||
a
|
except Exception as e:
|
||||||
for a in persist_attrs
|
logger.debug(f" Invoke PROPERTYPUT échoué pour {prop_name}: {e}")
|
||||||
if "valide" in a.lower()
|
return False
|
||||||
or "valid" in a.lower()
|
|
||||||
or "lock" in a.lower()
|
|
||||||
or "statut" in a.lower()
|
|
||||||
]
|
|
||||||
result["persist_attrs_all"] = persist_attrs
|
|
||||||
|
|
||||||
# Cast vers IBODocumentVente3
|
|
||||||
try:
|
|
||||||
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
|
|
||||||
doc.Read()
|
|
||||||
doc_attrs = [a for a in dir(doc) if not a.startswith("_")]
|
|
||||||
|
|
||||||
# Attributs liés à la validation
|
def _call_method_via_invoke(com_object, method_name: str) -> bool:
|
||||||
result["doc_attrs_validation"] = [
|
"""
|
||||||
a
|
Appelle une méthode COM via Invoke direct.
|
||||||
for a in doc_attrs
|
"""
|
||||||
if "valide" in a.lower()
|
try:
|
||||||
or "valid" in a.lower()
|
oleobj = com_object._oleobj_
|
||||||
or "lock" in a.lower()
|
dispid = oleobj.GetIDsOfNames(0, method_name)
|
||||||
or "statut" in a.lower()
|
if isinstance(dispid, tuple):
|
||||||
]
|
dispid = dispid[0]
|
||||||
|
|
||||||
# Toutes les méthodes callable
|
oleobj.Invoke(dispid, 0, DISPATCH_METHOD, True)
|
||||||
result["doc_methods"] = [
|
return True
|
||||||
a
|
except Exception as e:
|
||||||
for a in doc_attrs
|
logger.debug(f" Invoke METHOD échoué pour {method_name}: {e}")
|
||||||
if callable(getattr(doc, a, None)) and not a.startswith("_")
|
return False
|
||||||
]
|
|
||||||
|
|
||||||
# Valeur actuelle de DO_Valide et DO_Statut
|
|
||||||
try:
|
|
||||||
result["DO_Valide_current"] = getattr(
|
|
||||||
doc, "DO_Valide", "NOT_FOUND"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
result["DO_Valide_error"] = str(e)
|
|
||||||
|
|
||||||
try:
|
# ============================================================
|
||||||
result["DO_Statut_current"] = getattr(
|
# FONCTIONS COM (ÉCRITURE) avec late binding
|
||||||
doc, "DO_Statut", "NOT_FOUND"
|
# ============================================================
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
result["DO_Statut_error"] = str(e)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
result["cast_error"] = str(e)
|
|
||||||
|
|
||||||
# Essayer IBPersistDocument
|
def _valider_document_com(connector, numero_facture: str, valider: bool = True) -> bool:
|
||||||
try:
|
"""
|
||||||
persist_doc = win32com.client.CastTo(
|
Valide ou dévalide un document via COM en late binding.
|
||||||
persist, "IBPersistDocument"
|
|
||||||
)
|
|
||||||
persist_doc_attrs = [
|
|
||||||
a for a in dir(persist_doc) if not a.startswith("_")
|
|
||||||
]
|
|
||||||
result["IBPersistDocument_attrs"] = persist_doc_attrs
|
|
||||||
except Exception as e:
|
|
||||||
result["IBPersistDocument_error"] = str(e)
|
|
||||||
|
|
||||||
# 3. Explorer chaque CreateProcess pertinent
|
Utilise Invoke direct pour contourner les restrictions gen_py.
|
||||||
for process_name in result.get("cial_createprocess", []):
|
"""
|
||||||
if process_name.startswith("CreateProcess"):
|
erreurs = []
|
||||||
try:
|
valeur_cible = 1 if valider else 0
|
||||||
process = getattr(connector.cial, process_name)()
|
valeur_bool = True if valider else False
|
||||||
process_attrs = [
|
action = "validation" if valider else "dévalidation"
|
||||||
a for a in dir(process) if not a.startswith("_")
|
|
||||||
]
|
|
||||||
result[f"{process_name}_attrs"] = process_attrs
|
|
||||||
except Exception as e:
|
|
||||||
result[f"{process_name}_error"] = str(e)
|
|
||||||
|
|
||||||
# 4. Chercher spécifiquement CreateProcess_ValiderBon ou similaire
|
factory = connector.cial.FactoryDocumentVente
|
||||||
validation_processes = [
|
if not factory.ExistPiece(60, numero_facture):
|
||||||
"CreateProcess_ValiderBon",
|
raise ValueError(f"Facture {numero_facture} introuvable")
|
||||||
"CreateProcess_ValiderDocument",
|
|
||||||
"CreateProcess_ValiderPiece",
|
persist = factory.ReadPiece(60, numero_facture)
|
||||||
"CreateProcess_Validation",
|
if not persist:
|
||||||
]
|
raise ValueError(f"Impossible de lire la facture {numero_facture}")
|
||||||
for vp in validation_processes:
|
|
||||||
if vp not in result:
|
# APPROCHE 1: Late binding via Dispatch dynamique
|
||||||
try:
|
try:
|
||||||
process = getattr(connector.cial, vp)()
|
logger.info(" Tentative late binding dynamique...")
|
||||||
process_attrs = [
|
|
||||||
a for a in dir(process) if not a.startswith("_")
|
# Convertir en dispatch dynamique (late binding)
|
||||||
]
|
doc_dyn = _get_dynamic_dispatch(persist)
|
||||||
result[f"{vp}_attrs"] = process_attrs
|
doc_dyn.Read()
|
||||||
result[f"{vp}_exists"] = True
|
|
||||||
except AttributeError:
|
# Lire la valeur actuelle
|
||||||
result[f"{vp}_exists"] = False
|
current = doc_dyn.DO_Valide
|
||||||
except Exception as e:
|
logger.info(" DO_Valide actuel (late): {current}")
|
||||||
result[f"{vp}_error"] = str(e)
|
|
||||||
|
# Modifier
|
||||||
|
doc_dyn.DO_Valide = valeur_bool
|
||||||
|
doc_dyn.Write()
|
||||||
|
|
||||||
|
logger.info(" Late binding dynamique réussi!")
|
||||||
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
result["global_error"] = str(e)
|
erreurs.append(f"Late binding dynamique: {e}")
|
||||||
|
logger.debug(f" Late binding dynamique échoué: {e}")
|
||||||
|
|
||||||
return result
|
# APPROCHE 2: Invoke direct avec PROPERTYPUT
|
||||||
|
try:
|
||||||
|
logger.info(" Tentative Invoke PROPERTYPUT...")
|
||||||
|
|
||||||
|
# Cast puis invoke
|
||||||
|
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
|
||||||
|
doc.Read()
|
||||||
|
|
||||||
# ============================================================
|
# Essayer avec valeur bool
|
||||||
# FONCTIONS COM (ÉCRITURE) - À adapter selon l'introspection
|
success = _set_property_via_invoke(doc, "DO_Valide", valeur_bool)
|
||||||
# ============================================================
|
if success:
|
||||||
|
_call_method_via_invoke(doc, "Write")
|
||||||
|
logger.info(" Invoke PROPERTYPUT (bool) réussi!")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Essayer avec valeur int
|
||||||
|
success = _set_property_via_invoke(doc, "DO_Valide", valeur_cible)
|
||||||
|
if success:
|
||||||
|
_call_method_via_invoke(doc, "Write")
|
||||||
|
logger.info(" Invoke PROPERTYPUT (int) réussi!")
|
||||||
|
return True
|
||||||
|
|
||||||
|
erreurs.append("Invoke PROPERTYPUT échoué pour bool et int")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
erreurs.append(f"Invoke PROPERTYPUT: {e}")
|
||||||
|
logger.debug(f" Invoke PROPERTYPUT échoué: {e}")
|
||||||
|
|
||||||
|
# APPROCHE 3: Dispatch frais sans cache
|
||||||
|
try:
|
||||||
|
logger.info(" Tentative Dispatch frais (sans cache)...")
|
||||||
|
|
||||||
|
# Créer un nouveau Dispatch sans utiliser le cache gen_py
|
||||||
|
# En passant par l'IDispatch de l'objet
|
||||||
|
oleobj = persist._oleobj_
|
||||||
|
idisp = oleobj.QueryInterface(pythoncom.IID_IDispatch)
|
||||||
|
|
||||||
|
# Créer un wrapper dynamique
|
||||||
|
doc_fresh = win32com.client.dynamic.Dispatch(idisp)
|
||||||
|
doc_fresh.Read()
|
||||||
|
|
||||||
|
logger.info(" DO_Valide actuel: {doc_fresh.DO_Valide}")
|
||||||
|
doc_fresh.DO_Valide = valeur_bool
|
||||||
|
doc_fresh.Write()
|
||||||
|
|
||||||
|
logger.info(" Dispatch frais réussi!")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
erreurs.append(f"Dispatch frais: {e}")
|
||||||
|
logger.debug(f" Dispatch frais échoué: {e}")
|
||||||
|
|
||||||
|
# APPROCHE 4: Via IBPersistDocument
|
||||||
|
try:
|
||||||
|
logger.info(" Tentative via IBPersistDocument...")
|
||||||
|
|
||||||
|
doc = win32com.client.CastTo(persist, "IBPersistDocument")
|
||||||
|
doc.Read()
|
||||||
|
|
||||||
|
doc_dyn = _get_dynamic_dispatch(doc)
|
||||||
|
doc_dyn.DO_Valide = valeur_bool
|
||||||
|
doc_dyn.Write()
|
||||||
|
|
||||||
|
logger.info(" IBPersistDocument réussi!")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
erreurs.append(f"IBPersistDocument: {e}")
|
||||||
|
logger.debug(f" IBPersistDocument échoué: {e}")
|
||||||
|
|
||||||
|
# APPROCHE 5: Accès direct sans Cast
|
||||||
|
try:
|
||||||
|
logger.info(" Tentative accès direct sans Cast...")
|
||||||
|
|
||||||
|
persist.Read()
|
||||||
|
persist_dyn = _get_dynamic_dispatch(persist)
|
||||||
|
|
||||||
|
logger.info(" DO_Valide actuel: {persist_dyn.DO_Valide}")
|
||||||
|
persist_dyn.DO_Valide = valeur_bool
|
||||||
|
persist_dyn.Write()
|
||||||
|
|
||||||
|
logger.info(" Accès direct sans Cast réussi!")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
erreurs.append(f"Direct sans Cast: {e}")
|
||||||
|
logger.debug(f" Direct sans Cast échoué: {e}")
|
||||||
|
|
||||||
|
logger.error(f" Toutes les approches de {action} ont échoué: {erreurs}")
|
||||||
|
raise RuntimeError(f"Échec {action}: {'; '.join(erreurs[:3])}")
|
||||||
|
|
||||||
|
|
||||||
def valider_facture(connector, numero_facture: str) -> Dict:
|
def valider_facture(connector, numero_facture: str) -> Dict:
|
||||||
"""
|
"""
|
||||||
Valide une facture (pose le cadenas)
|
Valide une facture (pose le cadenas)
|
||||||
|
|
||||||
- Vérifications: SQL
|
|
||||||
- Modification: COM (via Process ou méthode appropriée)
|
|
||||||
- Réponse: SQL
|
|
||||||
"""
|
"""
|
||||||
if not connector.cial:
|
if not connector.cial:
|
||||||
raise RuntimeError("Connexion Sage non établie")
|
raise RuntimeError("Connexion Sage non établie")
|
||||||
|
|
@ -271,7 +324,7 @@ def valider_facture(connector, numero_facture: str) -> Dict:
|
||||||
connector, numero_facture, deja_valide=True, action="validation"
|
connector, numero_facture, deja_valide=True, action="validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 2. Modification via COM
|
# 2. Modification via COM (late binding)
|
||||||
try:
|
try:
|
||||||
with connector._com_context(), connector._lock_com:
|
with connector._com_context(), connector._lock_com:
|
||||||
success = _valider_document_com(connector, numero_facture, valider=True)
|
success = _valider_document_com(connector, numero_facture, valider=True)
|
||||||
|
|
@ -301,10 +354,6 @@ def valider_facture(connector, numero_facture: str) -> Dict:
|
||||||
def devalider_facture(connector, numero_facture: str) -> Dict:
|
def devalider_facture(connector, numero_facture: str) -> Dict:
|
||||||
"""
|
"""
|
||||||
Dévalide une facture (retire le cadenas)
|
Dévalide une facture (retire le cadenas)
|
||||||
|
|
||||||
- Vérifications: SQL
|
|
||||||
- Modification: COM (via Process ou méthode appropriée)
|
|
||||||
- Réponse: SQL
|
|
||||||
"""
|
"""
|
||||||
if not connector.cial:
|
if not connector.cial:
|
||||||
raise RuntimeError("Connexion Sage non établie")
|
raise RuntimeError("Connexion Sage non établie")
|
||||||
|
|
@ -336,7 +385,7 @@ def devalider_facture(connector, numero_facture: str) -> Dict:
|
||||||
connector, numero_facture, deja_valide=True, action="devalidation"
|
connector, numero_facture, deja_valide=True, action="devalidation"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 2. Modification via COM
|
# 2. Modification via COM (late binding)
|
||||||
try:
|
try:
|
||||||
with connector._com_context(), connector._lock_com:
|
with connector._com_context(), connector._lock_com:
|
||||||
success = _valider_document_com(connector, numero_facture, valider=False)
|
success = _valider_document_com(connector, numero_facture, valider=False)
|
||||||
|
|
@ -363,157 +412,75 @@ def devalider_facture(connector, numero_facture: str) -> Dict:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _valider_document_com(connector, numero_facture: str, valider: bool = True) -> bool:
|
# ============================================================
|
||||||
|
# INTROSPECTION
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
|
||||||
|
def introspecter_validation(connector, numero_facture: str = None) -> Dict:
|
||||||
"""
|
"""
|
||||||
Valide ou dévalide un document via COM.
|
Introspection pour découvrir les méthodes de validation disponibles.
|
||||||
|
|
||||||
Essaie plusieurs approches dans l'ordre:
|
|
||||||
1. CreateProcess_ValiderBon (si disponible)
|
|
||||||
2. Méthode Valider() sur le document
|
|
||||||
3. SetFieldValue sur DO_Valide
|
|
||||||
4. Accès direct à DO_Valide
|
|
||||||
|
|
||||||
Retourne True si réussi.
|
|
||||||
"""
|
"""
|
||||||
erreurs = []
|
if not connector.cial:
|
||||||
valeur_cible = 1 if valider else 0
|
raise RuntimeError("Connexion Sage non établie")
|
||||||
action = "validation" if valider else "dévalidation"
|
|
||||||
|
|
||||||
factory = connector.cial.FactoryDocumentVente
|
result = {}
|
||||||
if not factory.ExistPiece(60, numero_facture):
|
|
||||||
raise ValueError(f"Facture {numero_facture} introuvable")
|
|
||||||
|
|
||||||
persist = factory.ReadPiece(60, numero_facture)
|
|
||||||
if not persist:
|
|
||||||
raise ValueError(f"Impossible de lire la facture {numero_facture}")
|
|
||||||
|
|
||||||
# APPROCHE 1: CreateProcess_ValiderBon
|
|
||||||
try:
|
try:
|
||||||
process = connector.cial.CreateProcess_ValiderBon()
|
with connector._com_context(), connector._lock_com:
|
||||||
logger.info(f" Tentative via CreateProcess_ValiderBon...")
|
cial_attrs = [a for a in dir(connector.cial) if not a.startswith("_")]
|
||||||
|
result["cial_createprocess"] = [
|
||||||
|
a
|
||||||
|
for a in cial_attrs
|
||||||
|
if "Process" in a or "Valider" in a or "Valid" in a
|
||||||
|
]
|
||||||
|
|
||||||
# Ajouter le document au process
|
if numero_facture:
|
||||||
process.Document = persist
|
factory = connector.cial.FactoryDocumentVente
|
||||||
|
if factory.ExistPiece(60, numero_facture):
|
||||||
|
persist = factory.ReadPiece(60, numero_facture)
|
||||||
|
|
||||||
# Définir l'action (valider ou dévalider)
|
# Test late binding
|
||||||
try:
|
try:
|
||||||
process.Valider = valider
|
doc_dyn = _get_dynamic_dispatch(persist)
|
||||||
except Exception:
|
doc_dyn.Read()
|
||||||
pass
|
|
||||||
|
|
||||||
# Vérifier si on peut traiter
|
# Lister les attributs via late binding
|
||||||
can_process = getattr(process, "CanProcess", True)
|
result["late_binding_test"] = {
|
||||||
if can_process:
|
"DO_Valide_readable": True,
|
||||||
process.Process()
|
"DO_Valide_value": doc_dyn.DO_Valide,
|
||||||
logger.info(f" CreateProcess_ValiderBon.Process() réussi")
|
}
|
||||||
return True
|
|
||||||
else:
|
|
||||||
# Lire les erreurs
|
|
||||||
try:
|
|
||||||
errors = process.Errors
|
|
||||||
if errors and errors.Count > 0:
|
|
||||||
for i in range(1, errors.Count + 1):
|
|
||||||
erreurs.append(f"ValiderBon error: {errors.Item(i)}")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
except AttributeError:
|
# Tester si writable
|
||||||
erreurs.append("CreateProcess_ValiderBon n'existe pas")
|
try:
|
||||||
except Exception as e:
|
original = doc_dyn.DO_Valide
|
||||||
erreurs.append(f"CreateProcess_ValiderBon: {e}")
|
doc_dyn.DO_Valide = (
|
||||||
|
original # Essayer de réécrire la même valeur
|
||||||
|
)
|
||||||
|
result["late_binding_test"]["DO_Valide_writable"] = True
|
||||||
|
except Exception as e:
|
||||||
|
result["late_binding_test"]["DO_Valide_writable"] = False
|
||||||
|
result["late_binding_test"]["write_error"] = str(e)
|
||||||
|
|
||||||
# APPROCHE 2: Méthode Valider() ou Devalider() sur le document
|
except Exception as e:
|
||||||
try:
|
result["late_binding_error"] = str(e)
|
||||||
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
|
|
||||||
doc.Read()
|
|
||||||
|
|
||||||
if valider:
|
# Test des process
|
||||||
method_names = ["Valider", "Validate", "Lock", "SetValide"]
|
for process_name in result.get("cial_createprocess", []):
|
||||||
else:
|
if process_name.startswith("CreateProcess"):
|
||||||
method_names = ["Devalider", "Devalidate", "Unlock", "SetDevalide"]
|
try:
|
||||||
|
process = getattr(connector.cial, process_name)()
|
||||||
for method_name in method_names:
|
process_attrs = [
|
||||||
try:
|
a for a in dir(process) if not a.startswith("_")
|
||||||
method = getattr(doc, method_name, None)
|
]
|
||||||
if method and callable(method):
|
result[f"{process_name}_attrs"] = process_attrs
|
||||||
logger.info(f" Tentative via doc.{method_name}()...")
|
except Exception as e:
|
||||||
method()
|
result[f"{process_name}_error"] = str(e)
|
||||||
doc.Write()
|
|
||||||
logger.info(f" doc.{method_name}() réussi")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
erreurs.append(f"doc.{method_name}: {e}")
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
erreurs.append(f"Cast IBODocumentVente3: {e}")
|
result["global_error"] = str(e)
|
||||||
|
|
||||||
# APPROCHE 3: SetFieldValue (méthode générique Sage)
|
return result
|
||||||
try:
|
|
||||||
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
|
|
||||||
doc.Read()
|
|
||||||
|
|
||||||
try:
|
|
||||||
logger.info(
|
|
||||||
f" Tentative via SetFieldValue('DO_Valide', {valeur_cible})..."
|
|
||||||
)
|
|
||||||
doc.SetFieldValue("DO_Valide", valeur_cible)
|
|
||||||
doc.Write()
|
|
||||||
logger.info(f" SetFieldValue réussi")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
erreurs.append(f"SetFieldValue: {e}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
erreurs.append(f"SetFieldValue cast: {e}")
|
|
||||||
|
|
||||||
# APPROCHE 4: Accès direct via différentes interfaces
|
|
||||||
interfaces = [
|
|
||||||
"IBODocumentVente3",
|
|
||||||
"IBODocumentVente2",
|
|
||||||
"IBODocumentVente",
|
|
||||||
"IBODocument3",
|
|
||||||
]
|
|
||||||
|
|
||||||
for iface in interfaces:
|
|
||||||
try:
|
|
||||||
doc = win32com.client.CastTo(persist, iface)
|
|
||||||
doc.Read()
|
|
||||||
|
|
||||||
logger.info(f" Tentative accès direct DO_Valide via {iface}...")
|
|
||||||
|
|
||||||
# Essayer d'accéder et modifier
|
|
||||||
try:
|
|
||||||
current_val = doc.DO_Valide
|
|
||||||
logger.info(f" Valeur actuelle: {current_val}")
|
|
||||||
doc.DO_Valide = valeur_cible
|
|
||||||
doc.Write()
|
|
||||||
logger.info(f" Accès direct via {iface} réussi")
|
|
||||||
return True
|
|
||||||
except AttributeError as e:
|
|
||||||
erreurs.append(f"{iface} DO_Valide AttributeError: {e}")
|
|
||||||
except Exception as e:
|
|
||||||
erreurs.append(f"{iface} DO_Valide: {e}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
erreurs.append(f"Cast {iface}: {e}")
|
|
||||||
|
|
||||||
# APPROCHE 5: Via IPMDocument si disponible
|
|
||||||
try:
|
|
||||||
doc = win32com.client.CastTo(persist, "IPMDocument")
|
|
||||||
logger.info(f" Tentative via IPMDocument...")
|
|
||||||
|
|
||||||
if valider:
|
|
||||||
doc.Valider()
|
|
||||||
else:
|
|
||||||
doc.Devalider()
|
|
||||||
doc.Write()
|
|
||||||
logger.info(f" IPMDocument.{'Valider' if valider else 'Devalider'}() réussi")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
erreurs.append(f"IPMDocument: {e}")
|
|
||||||
|
|
||||||
logger.error(f" Toutes les approches de {action} ont échoué: {erreurs}")
|
|
||||||
raise RuntimeError(f"Échec {action}: {'; '.join(erreurs[:5])}")
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
@ -524,7 +491,7 @@ def _valider_document_com(connector, numero_facture: str, valider: bool = True)
|
||||||
def _build_response_sql(
|
def _build_response_sql(
|
||||||
connector, numero_facture: str, deja_valide: bool, action: str
|
connector, numero_facture: str, deja_valide: bool, action: str
|
||||||
) -> Dict:
|
) -> Dict:
|
||||||
"""Construit la réponse via SQL (pas COM)"""
|
"""Construit la réponse via SQL"""
|
||||||
info = _get_facture_info_sql(connector, numero_facture)
|
info = _get_facture_info_sql(connector, numero_facture)
|
||||||
|
|
||||||
if action == "validation":
|
if action == "validation":
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue