Another try
This commit is contained in:
parent
bd48bf5aac
commit
b66525c00e
1 changed files with 195 additions and 26 deletions
|
|
@ -1,5 +1,14 @@
|
|||
"""
|
||||
Validation de factures Sage 100c
|
||||
Module: utils/documents/validation.py (Windows Server)
|
||||
|
||||
COM pour écriture, SQL pour lecture
|
||||
Solution robuste avec late binding pour éviter les erreurs intermittentes
|
||||
"""
|
||||
|
||||
from typing import Dict
|
||||
import win32com.client
|
||||
import win32com.client.dynamic
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -20,7 +29,7 @@ def get_statut_validation(connector, numero_facture: str) -> Dict:
|
|||
"""
|
||||
SELECT
|
||||
DO_Valide, DO_Statut, DO_TotalHT, DO_TotalTTC,
|
||||
DO_MontantRegle, CT_NumPayeur, DO_Date, DO_Ref
|
||||
ISNULL(DO_MontantRegle, 0), CT_NumPayeur, DO_Date, DO_Ref
|
||||
FROM F_DOCENTETE
|
||||
WHERE DO_Piece = ? AND DO_Type = 6
|
||||
""",
|
||||
|
|
@ -71,10 +80,9 @@ def _get_facture_info_sql(connector, numero_facture: str) -> Dict:
|
|||
"""
|
||||
SELECT
|
||||
DO_Valide, DO_Statut, DO_TotalHT, DO_TotalTTC,
|
||||
ISNULL((SELECT SUM(DR_MontantRegle) FROM F_REGLECH
|
||||
WHERE DO_Piece = d.DO_Piece AND DO_Type = 6), 0) as MontantRegle,
|
||||
ISNULL(DO_MontantRegle, 0) as MontantRegle,
|
||||
CT_NumPayeur
|
||||
FROM F_DOCENTETE d
|
||||
FROM F_DOCENTETE
|
||||
WHERE DO_Piece = ? AND DO_Type = 6
|
||||
""",
|
||||
(numero_facture,),
|
||||
|
|
@ -94,7 +102,120 @@ def _get_facture_info_sql(connector, numero_facture: str) -> Dict:
|
|||
}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# FONCTIONS COM (ÉCRITURE)
|
||||
# ============================================================
|
||||
|
||||
|
||||
def _set_do_valide_com(doc, valeur: int) -> bool:
|
||||
"""
|
||||
Définit DO_Valide via COM avec plusieurs méthodes de fallback
|
||||
|
||||
Retourne True si réussi
|
||||
"""
|
||||
erreurs = []
|
||||
|
||||
# Méthode 1: Accès direct (early binding)
|
||||
try:
|
||||
doc.DO_Valide = valeur
|
||||
doc.Write()
|
||||
logger.debug(" DO_Valide défini via accès direct")
|
||||
return True
|
||||
except AttributeError as e:
|
||||
erreurs.append(f"Direct: {e}")
|
||||
except Exception as e:
|
||||
erreurs.append(f"Direct: {e}")
|
||||
|
||||
# Méthode 2: Via setattr
|
||||
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):
|
||||
"""
|
||||
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:
|
||||
"""
|
||||
Valide une facture (pose le cadenas)
|
||||
|
||||
- Vérifications: SQL
|
||||
- Modification DO_Valide: COM
|
||||
- Réponse: SQL
|
||||
"""
|
||||
if not connector.cial:
|
||||
raise RuntimeError("Connexion Sage non établie")
|
||||
|
||||
|
|
@ -117,18 +238,14 @@ def valider_facture(connector, numero_facture: str) -> Dict:
|
|||
# 2. Modification via COM
|
||||
try:
|
||||
with connector._com_context(), connector._lock_com:
|
||||
factory = connector.cial.FactoryDocumentVente
|
||||
persist = factory.ReadPiece(60, numero_facture)
|
||||
doc, iface = _get_document_com(connector, numero_facture)
|
||||
logger.debug(f" Interface utilisée: {iface}")
|
||||
|
||||
if not persist:
|
||||
raise ValueError(f"Impossible de lire la facture {numero_facture}")
|
||||
success = _set_do_valide_com(doc, 1)
|
||||
if not success:
|
||||
raise RuntimeError("Impossible de définir DO_Valide")
|
||||
|
||||
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
|
||||
doc.Read()
|
||||
doc.DO_Valide = 1
|
||||
doc.Write()
|
||||
|
||||
logger.info(f"✅ Facture {numero_facture} validée")
|
||||
logger.info(f"✅ Facture {numero_facture} validée (COM via {iface})")
|
||||
|
||||
except ValueError:
|
||||
raise
|
||||
|
|
@ -139,7 +256,7 @@ def valider_facture(connector, numero_facture: str) -> Dict:
|
|||
# 3. Vérification et réponse via SQL
|
||||
info_apres = _get_facture_info_sql(connector, numero_facture)
|
||||
if info_apres and info_apres["valide"] != 1:
|
||||
raise RuntimeError("Échec validation: DO_Valide non modifié")
|
||||
raise RuntimeError("Échec validation: DO_Valide non modifié après Write()")
|
||||
|
||||
return _build_response_sql(
|
||||
connector, numero_facture, deja_valide=False, action="validation"
|
||||
|
|
@ -187,18 +304,14 @@ def devalider_facture(connector, numero_facture: str) -> Dict:
|
|||
# 2. Modification via COM
|
||||
try:
|
||||
with connector._com_context(), connector._lock_com:
|
||||
factory = connector.cial.FactoryDocumentVente
|
||||
persist = factory.ReadPiece(60, numero_facture)
|
||||
doc, iface = _get_document_com(connector, numero_facture)
|
||||
logger.debug(f" Interface utilisée: {iface}")
|
||||
|
||||
if not persist:
|
||||
raise ValueError(f"Impossible de lire la facture {numero_facture}")
|
||||
success = _set_do_valide_com(doc, 0)
|
||||
if not success:
|
||||
raise RuntimeError("Impossible de définir DO_Valide")
|
||||
|
||||
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
|
||||
doc.Read()
|
||||
doc.DO_Valide = 0
|
||||
doc.Write()
|
||||
|
||||
logger.info(f"✅ Facture {numero_facture} dévalidée")
|
||||
logger.info(f"✅ Facture {numero_facture} dévalidée (COM via {iface})")
|
||||
|
||||
except ValueError:
|
||||
raise
|
||||
|
|
@ -209,7 +322,7 @@ def devalider_facture(connector, numero_facture: str) -> Dict:
|
|||
# 3. Vérification et réponse via SQL
|
||||
info_apres = _get_facture_info_sql(connector, numero_facture)
|
||||
if info_apres and info_apres["valide"] != 0:
|
||||
raise RuntimeError("Échec dévalidation: DO_Valide non modifié")
|
||||
raise RuntimeError("Échec dévalidation: DO_Valide non modifié après Write()")
|
||||
|
||||
return _build_response_sql(
|
||||
connector, numero_facture, deja_valide=False, action="devalidation"
|
||||
|
|
@ -221,6 +334,62 @@ def devalider_facture(connector, numero_facture: str) -> Dict:
|
|||
# ============================================================
|
||||
|
||||
|
||||
def _build_response_sql(
|
||||
connector, numero_facture: str, deja_valide: bool, action: str
|
||||
) -> Dict:
|
||||
"""Construit la réponse via SQL (pas COM)"""
|
||||
info = _get_facture_info_sql(connector, numero_facture)
|
||||
|
||||
if action == "validation":
|
||||
message = (
|
||||
"Facture déjà validée" if deja_valide else "Facture validée avec succès"
|
||||
)
|
||||
else:
|
||||
message = (
|
||||
"Facture déjà non validée"
|
||||
if deja_valide
|
||||
else "Facture dévalidée avec succès"
|
||||
)
|
||||
|
||||
return {
|
||||
"numero_facture": numero_facture,
|
||||
"est_validee": info["valide"] == 1 if info else False,
|
||||
"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,
|
||||
}
|
||||
|
||||
|
||||
def _get_statut_libelle(statut: int) -> str:
|
||||
"""Retourne le libellé d'un statut de document"""
|
||||
statuts = {
|
||||
0: "Brouillon",
|
||||
1: "Confirmé",
|
||||
2: "En cours",
|
||||
3: "Imprimé",
|
||||
4: "Suspendu",
|
||||
5: "Transformé",
|
||||
6: "Annulé",
|
||||
}
|
||||
return statuts.get(statut, f"Statut {statut}")
|
||||
|
||||
|
||||
__all__ = [
|
||||
"valider_facture",
|
||||
"devalider_facture",
|
||||
"get_statut_validation",
|
||||
]
|
||||
|
||||
|
||||
# ============================================================
|
||||
# FONCTIONS UTILITAIRES
|
||||
# ============================================================
|
||||
|
||||
|
||||
def _build_response_sql(
|
||||
connector, numero_facture: str, deja_valide: bool, action: str
|
||||
) -> Dict:
|
||||
|
|
|
|||
Loading…
Reference in a new issue