Another try with introspection
This commit is contained in:
parent
b66525c00e
commit
68c70de7d9
3 changed files with 301 additions and 155 deletions
10
main.py
10
main.py
|
|
@ -1725,6 +1725,16 @@ def introspection_com():
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/sage/debug/introspection-validation")
|
||||||
|
def introspection_validation(numero_facture: str = None):
|
||||||
|
try:
|
||||||
|
resultat = sage.introspecter_validation(numero_facture)
|
||||||
|
return {"success": True, "data": resultat}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur introspection: {e}", exc_info=True)
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
uvicorn.run(
|
uvicorn.run(
|
||||||
"main:app",
|
"main:app",
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,7 @@ from utils.documents.validations import (
|
||||||
valider_facture as _valider,
|
valider_facture as _valider,
|
||||||
devalider_facture as _devalider,
|
devalider_facture as _devalider,
|
||||||
get_statut_validation as _get_statut,
|
get_statut_validation as _get_statut,
|
||||||
|
introspecter_validation as _introspect,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -8094,3 +8095,6 @@ class SageConnector:
|
||||||
|
|
||||||
def get_statut_validation(self, numero_facture: str) -> dict:
|
def get_statut_validation(self, numero_facture: str) -> dict:
|
||||||
return _get_statut(self, numero_facture)
|
return _get_statut(self, numero_facture)
|
||||||
|
|
||||||
|
def introspecter_validation(self, numero_facture: str = None) -> dict:
|
||||||
|
return _introspect(self, numero_facture)
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,10 @@ 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, SQL pour lecture
|
||||||
Solution robuste avec late binding pour éviter les erreurs intermittentes
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
import win32com.client
|
import win32com.client
|
||||||
import win32com.client.dynamic
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -103,109 +101,147 @@ def _get_facture_info_sql(connector, numero_facture: str) -> Dict:
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# FONCTIONS COM (ÉCRITURE)
|
# INTROSPECTION - Pour découvrir les méthodes de validation
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
|
|
||||||
def _set_do_valide_com(doc, valeur: int) -> bool:
|
def introspecter_validation(connector, numero_facture: str = None) -> Dict:
|
||||||
"""
|
"""
|
||||||
Définit DO_Valide via COM avec plusieurs méthodes de fallback
|
Introspection pour découvrir les méthodes de validation disponibles dans Sage.
|
||||||
|
|
||||||
Retourne True si réussi
|
Appeler cette fonction pour voir quelles méthodes/process sont disponibles
|
||||||
|
pour valider un document.
|
||||||
"""
|
"""
|
||||||
erreurs = []
|
if not connector.cial:
|
||||||
|
raise RuntimeError("Connexion Sage non établie")
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
|
||||||
# Méthode 1: Accès direct (early binding)
|
|
||||||
try:
|
try:
|
||||||
doc.DO_Valide = valeur
|
with connector._com_context(), connector._lock_com:
|
||||||
doc.Write()
|
# 1. Méthodes sur cial (BSCIALApplication100c)
|
||||||
logger.debug(" DO_Valide défini via accès direct")
|
cial_attrs = [a for a in dir(connector.cial) if not a.startswith("_")]
|
||||||
return True
|
|
||||||
except AttributeError as e:
|
# Filtrer les CreateProcess et méthodes liées à la validation
|
||||||
erreurs.append(f"Direct: {e}")
|
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
|
||||||
|
if numero_facture:
|
||||||
|
factory = connector.cial.FactoryDocumentVente
|
||||||
|
if factory.ExistPiece(60, numero_facture):
|
||||||
|
persist = factory.ReadPiece(60, numero_facture)
|
||||||
|
|
||||||
|
# Attributs du persist brut
|
||||||
|
persist_attrs = [a for a in dir(persist) if not a.startswith("_")]
|
||||||
|
result["persist_attrs_validation"] = [
|
||||||
|
a
|
||||||
|
for a in persist_attrs
|
||||||
|
if "valide" in a.lower()
|
||||||
|
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
|
||||||
|
result["doc_attrs_validation"] = [
|
||||||
|
a
|
||||||
|
for a in doc_attrs
|
||||||
|
if "valide" in a.lower()
|
||||||
|
or "valid" in a.lower()
|
||||||
|
or "lock" in a.lower()
|
||||||
|
or "statut" in a.lower()
|
||||||
|
]
|
||||||
|
|
||||||
|
# Toutes les méthodes callable
|
||||||
|
result["doc_methods"] = [
|
||||||
|
a
|
||||||
|
for a in doc_attrs
|
||||||
|
if callable(getattr(doc, a, None)) and not a.startswith("_")
|
||||||
|
]
|
||||||
|
|
||||||
|
# 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(
|
||||||
|
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
|
||||||
|
try:
|
||||||
|
persist_doc = win32com.client.CastTo(
|
||||||
|
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
|
||||||
|
for process_name in result.get("cial_createprocess", []):
|
||||||
|
if process_name.startswith("CreateProcess"):
|
||||||
|
try:
|
||||||
|
process = getattr(connector.cial, process_name)()
|
||||||
|
process_attrs = [
|
||||||
|
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
|
||||||
|
validation_processes = [
|
||||||
|
"CreateProcess_ValiderBon",
|
||||||
|
"CreateProcess_ValiderDocument",
|
||||||
|
"CreateProcess_ValiderPiece",
|
||||||
|
"CreateProcess_Validation",
|
||||||
|
]
|
||||||
|
for vp in validation_processes:
|
||||||
|
if vp not in result:
|
||||||
|
try:
|
||||||
|
process = getattr(connector.cial, vp)()
|
||||||
|
process_attrs = [
|
||||||
|
a for a in dir(process) if not a.startswith("_")
|
||||||
|
]
|
||||||
|
result[f"{vp}_attrs"] = process_attrs
|
||||||
|
result[f"{vp}_exists"] = True
|
||||||
|
except AttributeError:
|
||||||
|
result[f"{vp}_exists"] = False
|
||||||
|
except Exception as e:
|
||||||
|
result[f"{vp}_error"] = str(e)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
erreurs.append(f"Direct: {e}")
|
result["global_error"] = str(e)
|
||||||
|
|
||||||
# Méthode 2: Via setattr
|
return result
|
||||||
try:
|
|
||||||
setattr(doc, "DO_Valide", valeur)
|
|
||||||
doc.Write()
|
|
||||||
logger.debug(" DO_Valide défini via setattr")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
erreurs.append(f"setattr: {e}")
|
|
||||||
|
|
||||||
# Méthode 3: Late binding - récupérer l'objet COM sous-jacent
|
|
||||||
try:
|
|
||||||
# Accès via _oleobj_ (objet COM brut)
|
|
||||||
disp = win32com.client.dynamic.Dispatch(doc._oleobj_)
|
|
||||||
disp.DO_Valide = valeur
|
|
||||||
disp.Write()
|
|
||||||
logger.debug(" DO_Valide défini via late binding _oleobj_")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
erreurs.append(f"Late _oleobj_: {e}")
|
|
||||||
|
|
||||||
# Méthode 4: Invoke direct sur la propriété
|
|
||||||
try:
|
|
||||||
import pythoncom
|
|
||||||
from win32com.client import VARIANT
|
|
||||||
|
|
||||||
# DISPATCH_PROPERTYPUT = 4
|
|
||||||
doc._oleobj_.Invoke(
|
|
||||||
doc._oleobj_.GetIDsOfNames(0, "DO_Valide")[0], 0, 4, 0, valeur
|
|
||||||
)
|
|
||||||
doc.Write()
|
|
||||||
logger.debug(" DO_Valide défini via Invoke")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
erreurs.append(f"Invoke: {e}")
|
|
||||||
|
|
||||||
logger.error(f" Toutes les méthodes ont échoué: {erreurs}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def _get_document_com(connector, numero_facture: str):
|
# ============================================================
|
||||||
"""
|
# FONCTIONS COM (ÉCRITURE) - À adapter selon l'introspection
|
||||||
Récupère le document COM avec plusieurs tentatives d'interface
|
# ============================================================
|
||||||
|
|
||||||
Retourne (doc, interface_utilisee) ou lève une exception
|
|
||||||
"""
|
|
||||||
factory = connector.cial.FactoryDocumentVente
|
|
||||||
|
|
||||||
if not factory.ExistPiece(60, numero_facture):
|
|
||||||
raise ValueError(f"Facture {numero_facture} introuvable dans Sage")
|
|
||||||
|
|
||||||
persist = factory.ReadPiece(60, numero_facture)
|
|
||||||
if not persist:
|
|
||||||
raise ValueError(f"Impossible de lire la facture {numero_facture}")
|
|
||||||
|
|
||||||
# Essayer plusieurs interfaces par ordre de préférence
|
|
||||||
interfaces = [
|
|
||||||
"IBODocumentVente3",
|
|
||||||
"IBODocumentVente2",
|
|
||||||
"IBODocumentVente",
|
|
||||||
"IBODocument3",
|
|
||||||
"IBPersistDocument",
|
|
||||||
]
|
|
||||||
|
|
||||||
for iface in interfaces:
|
|
||||||
try:
|
|
||||||
doc = win32com.client.CastTo(persist, iface)
|
|
||||||
doc.Read()
|
|
||||||
logger.debug(f" Document casté vers {iface}")
|
|
||||||
return doc, iface
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug(f" Cast {iface} échoué: {e}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Fallback: utiliser persist directement
|
|
||||||
try:
|
|
||||||
persist.Read()
|
|
||||||
logger.debug(" Utilisation de persist directement (sans cast)")
|
|
||||||
return persist, "persist"
|
|
||||||
except Exception as e:
|
|
||||||
raise RuntimeError(f"Impossible d'accéder au document: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
def valider_facture(connector, numero_facture: str) -> Dict:
|
def valider_facture(connector, numero_facture: str) -> Dict:
|
||||||
|
|
@ -213,7 +249,7 @@ def valider_facture(connector, numero_facture: str) -> Dict:
|
||||||
Valide une facture (pose le cadenas)
|
Valide une facture (pose le cadenas)
|
||||||
|
|
||||||
- Vérifications: SQL
|
- Vérifications: SQL
|
||||||
- Modification DO_Valide: COM
|
- Modification: COM (via Process ou méthode appropriée)
|
||||||
- Réponse: SQL
|
- Réponse: SQL
|
||||||
"""
|
"""
|
||||||
if not connector.cial:
|
if not connector.cial:
|
||||||
|
|
@ -238,14 +274,11 @@ def valider_facture(connector, numero_facture: str) -> Dict:
|
||||||
# 2. Modification via COM
|
# 2. Modification via COM
|
||||||
try:
|
try:
|
||||||
with connector._com_context(), connector._lock_com:
|
with connector._com_context(), connector._lock_com:
|
||||||
doc, iface = _get_document_com(connector, numero_facture)
|
success = _valider_document_com(connector, numero_facture, valider=True)
|
||||||
logger.debug(f" Interface utilisée: {iface}")
|
|
||||||
|
|
||||||
success = _set_do_valide_com(doc, 1)
|
|
||||||
if not success:
|
if not success:
|
||||||
raise RuntimeError("Impossible de définir DO_Valide")
|
raise RuntimeError("La validation COM a échoué")
|
||||||
|
|
||||||
logger.info(f"✅ Facture {numero_facture} validée (COM via {iface})")
|
logger.info(f"✅ Facture {numero_facture} validée")
|
||||||
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise
|
raise
|
||||||
|
|
@ -256,7 +289,9 @@ def valider_facture(connector, numero_facture: str) -> Dict:
|
||||||
# 3. Vérification et réponse via SQL
|
# 3. Vérification et réponse via SQL
|
||||||
info_apres = _get_facture_info_sql(connector, numero_facture)
|
info_apres = _get_facture_info_sql(connector, numero_facture)
|
||||||
if info_apres and info_apres["valide"] != 1:
|
if info_apres and info_apres["valide"] != 1:
|
||||||
raise RuntimeError("Échec validation: DO_Valide non modifié après Write()")
|
raise RuntimeError(
|
||||||
|
"Échec validation: DO_Valide non modifié après l'opération COM"
|
||||||
|
)
|
||||||
|
|
||||||
return _build_response_sql(
|
return _build_response_sql(
|
||||||
connector, numero_facture, deja_valide=False, action="validation"
|
connector, numero_facture, deja_valide=False, action="validation"
|
||||||
|
|
@ -268,7 +303,7 @@ 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
|
- Vérifications: SQL
|
||||||
- Modification DO_Valide: COM
|
- Modification: COM (via Process ou méthode appropriée)
|
||||||
- Réponse: SQL
|
- Réponse: SQL
|
||||||
"""
|
"""
|
||||||
if not connector.cial:
|
if not connector.cial:
|
||||||
|
|
@ -304,14 +339,11 @@ def devalider_facture(connector, numero_facture: str) -> Dict:
|
||||||
# 2. Modification via COM
|
# 2. Modification via COM
|
||||||
try:
|
try:
|
||||||
with connector._com_context(), connector._lock_com:
|
with connector._com_context(), connector._lock_com:
|
||||||
doc, iface = _get_document_com(connector, numero_facture)
|
success = _valider_document_com(connector, numero_facture, valider=False)
|
||||||
logger.debug(f" Interface utilisée: {iface}")
|
|
||||||
|
|
||||||
success = _set_do_valide_com(doc, 0)
|
|
||||||
if not success:
|
if not success:
|
||||||
raise RuntimeError("Impossible de définir DO_Valide")
|
raise RuntimeError("La dévalidation COM a échoué")
|
||||||
|
|
||||||
logger.info(f"✅ Facture {numero_facture} dévalidée (COM via {iface})")
|
logger.info(f"✅ Facture {numero_facture} dévalidée")
|
||||||
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise
|
raise
|
||||||
|
|
@ -322,67 +354,166 @@ def devalider_facture(connector, numero_facture: str) -> Dict:
|
||||||
# 3. Vérification et réponse via SQL
|
# 3. Vérification et réponse via SQL
|
||||||
info_apres = _get_facture_info_sql(connector, numero_facture)
|
info_apres = _get_facture_info_sql(connector, numero_facture)
|
||||||
if info_apres and info_apres["valide"] != 0:
|
if info_apres and info_apres["valide"] != 0:
|
||||||
raise RuntimeError("Échec dévalidation: DO_Valide non modifié après Write()")
|
raise RuntimeError(
|
||||||
|
"Échec dévalidation: DO_Valide non modifié après l'opération COM"
|
||||||
|
)
|
||||||
|
|
||||||
return _build_response_sql(
|
return _build_response_sql(
|
||||||
connector, numero_facture, deja_valide=False, action="devalidation"
|
connector, numero_facture, deja_valide=False, action="devalidation"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
def _valider_document_com(connector, numero_facture: str, valider: bool = True) -> bool:
|
||||||
# FONCTIONS UTILITAIRES
|
"""
|
||||||
# ============================================================
|
Valide ou dévalide un document via COM.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
def _build_response_sql(
|
Retourne True si réussi.
|
||||||
connector, numero_facture: str, deja_valide: bool, action: str
|
"""
|
||||||
) -> Dict:
|
erreurs = []
|
||||||
"""Construit la réponse via SQL (pas COM)"""
|
valeur_cible = 1 if valider else 0
|
||||||
info = _get_facture_info_sql(connector, numero_facture)
|
action = "validation" if valider else "dévalidation"
|
||||||
|
|
||||||
if action == "validation":
|
factory = connector.cial.FactoryDocumentVente
|
||||||
message = (
|
if not factory.ExistPiece(60, numero_facture):
|
||||||
"Facture déjà validée" if deja_valide else "Facture validée avec succès"
|
raise ValueError(f"Facture {numero_facture} introuvable")
|
||||||
)
|
|
||||||
else:
|
|
||||||
message = (
|
|
||||||
"Facture déjà non validée"
|
|
||||||
if deja_valide
|
|
||||||
else "Facture dévalidée avec succès"
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
persist = factory.ReadPiece(60, numero_facture)
|
||||||
"numero_facture": numero_facture,
|
if not persist:
|
||||||
"est_validee": info["valide"] == 1 if info else False,
|
raise ValueError(f"Impossible de lire la facture {numero_facture}")
|
||||||
"statut": info["statut"] if info else 0,
|
|
||||||
"statut_libelle": _get_statut_libelle(info["statut"]) if info else "Inconnu",
|
|
||||||
"total_ht": info["total_ht"] if info else 0.0,
|
|
||||||
"total_ttc": info["total_ttc"] if info else 0.0,
|
|
||||||
"client_code": info["client_code"] if info else "",
|
|
||||||
"message": message,
|
|
||||||
"action_effectuee": not deja_valide,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
# APPROCHE 1: CreateProcess_ValiderBon
|
||||||
|
try:
|
||||||
|
process = connector.cial.CreateProcess_ValiderBon()
|
||||||
|
logger.info(f" Tentative via CreateProcess_ValiderBon...")
|
||||||
|
|
||||||
def _get_statut_libelle(statut: int) -> str:
|
# Ajouter le document au process
|
||||||
"""Retourne le libellé d'un statut de document"""
|
process.Document = persist
|
||||||
statuts = {
|
|
||||||
0: "Brouillon",
|
|
||||||
1: "Confirmé",
|
|
||||||
2: "En cours",
|
|
||||||
3: "Imprimé",
|
|
||||||
4: "Suspendu",
|
|
||||||
5: "Transformé",
|
|
||||||
6: "Annulé",
|
|
||||||
}
|
|
||||||
return statuts.get(statut, f"Statut {statut}")
|
|
||||||
|
|
||||||
|
# Définir l'action (valider ou dévalider)
|
||||||
|
try:
|
||||||
|
process.Valider = valider
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
__all__ = [
|
# Vérifier si on peut traiter
|
||||||
"valider_facture",
|
can_process = getattr(process, "CanProcess", True)
|
||||||
"devalider_facture",
|
if can_process:
|
||||||
"get_statut_validation",
|
process.Process()
|
||||||
]
|
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:
|
||||||
|
erreurs.append("CreateProcess_ValiderBon n'existe pas")
|
||||||
|
except Exception as e:
|
||||||
|
erreurs.append(f"CreateProcess_ValiderBon: {e}")
|
||||||
|
|
||||||
|
# APPROCHE 2: Méthode Valider() ou Devalider() sur le document
|
||||||
|
try:
|
||||||
|
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
|
||||||
|
doc.Read()
|
||||||
|
|
||||||
|
if valider:
|
||||||
|
method_names = ["Valider", "Validate", "Lock", "SetValide"]
|
||||||
|
else:
|
||||||
|
method_names = ["Devalider", "Devalidate", "Unlock", "SetDevalide"]
|
||||||
|
|
||||||
|
for method_name in method_names:
|
||||||
|
try:
|
||||||
|
method = getattr(doc, method_name, None)
|
||||||
|
if method and callable(method):
|
||||||
|
logger.info(f" Tentative via doc.{method_name}()...")
|
||||||
|
method()
|
||||||
|
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:
|
||||||
|
erreurs.append(f"Cast IBODocumentVente3: {e}")
|
||||||
|
|
||||||
|
# APPROCHE 3: SetFieldValue (méthode générique Sage)
|
||||||
|
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])}")
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
@ -438,4 +569,5 @@ __all__ = [
|
||||||
"valider_facture",
|
"valider_facture",
|
||||||
"devalider_facture",
|
"devalider_facture",
|
||||||
"get_statut_validation",
|
"get_statut_validation",
|
||||||
|
"introspecter_validation",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue