710 lines
25 KiB
Python
710 lines
25 KiB
Python
"""
|
|
Validation de factures Sage 100c
|
|
Module: utils/documents/validation.py (Windows Server)
|
|
|
|
Version diagnostic - Introspection complète pour trouver la méthode de validation
|
|
"""
|
|
|
|
from typing import Dict, List
|
|
import win32com.client
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# ============================================================
|
|
# FONCTIONS SQL (LECTURE)
|
|
# ============================================================
|
|
|
|
|
|
def get_statut_validation(connector, numero_facture: str) -> Dict:
|
|
"""Retourne le statut de validation d'une facture (SQL)"""
|
|
with connector._get_sql_connection() as conn:
|
|
cursor = conn.cursor()
|
|
cursor.execute(
|
|
"""
|
|
SELECT
|
|
DO_Valide, DO_Statut, DO_TotalHT, DO_TotalTTC,
|
|
ISNULL(DO_MontantRegle, 0), CT_NumPayeur, DO_Date, DO_Ref
|
|
FROM F_DOCENTETE
|
|
WHERE DO_Piece = ? AND DO_Type = 6
|
|
""",
|
|
(numero_facture,),
|
|
)
|
|
row = cursor.fetchone()
|
|
|
|
if not row:
|
|
raise ValueError(f"Facture {numero_facture} introuvable")
|
|
|
|
valide = int(row[0]) if row[0] is not None else 0
|
|
statut = int(row[1]) if row[1] is not None else 0
|
|
total_ht = float(row[2]) if row[2] else 0.0
|
|
total_ttc = float(row[3]) if row[3] else 0.0
|
|
montant_regle = float(row[4]) if row[4] else 0.0
|
|
client_code = row[5].strip() if row[5] else ""
|
|
date_facture = row[6]
|
|
reference = row[7].strip() if row[7] else ""
|
|
|
|
solde = max(0.0, total_ttc - montant_regle)
|
|
|
|
return {
|
|
"numero_facture": numero_facture,
|
|
"est_validee": valide == 1,
|
|
"statut": statut,
|
|
"statut_libelle": _get_statut_libelle(statut),
|
|
"peut_etre_modifiee": valide == 0 and statut not in (5, 6),
|
|
"peut_etre_devalidee": valide == 1
|
|
and montant_regle < 0.01
|
|
and statut not in (5, 6),
|
|
"total_ht": total_ht,
|
|
"total_ttc": total_ttc,
|
|
"montant_regle": montant_regle,
|
|
"solde_restant": solde,
|
|
"client_code": client_code,
|
|
"date_facture": date_facture.strftime("%Y-%m-%d") if date_facture else None,
|
|
"reference": reference,
|
|
}
|
|
|
|
|
|
def _get_facture_info_sql(connector, numero_facture: str) -> Dict:
|
|
"""Récupère les infos d'une facture via SQL"""
|
|
with connector._get_sql_connection() as conn:
|
|
cursor = conn.cursor()
|
|
cursor.execute(
|
|
"""
|
|
SELECT
|
|
DO_Valide, DO_Statut, DO_TotalHT, DO_TotalTTC,
|
|
ISNULL(DO_MontantRegle, 0), CT_NumPayeur
|
|
FROM F_DOCENTETE
|
|
WHERE DO_Piece = ? AND DO_Type = 6
|
|
""",
|
|
(numero_facture,),
|
|
)
|
|
row = cursor.fetchone()
|
|
|
|
if not row:
|
|
return None
|
|
|
|
return {
|
|
"valide": int(row[0]) if row[0] is not None else 0,
|
|
"statut": int(row[1]) if row[1] is not None else 0,
|
|
"total_ht": float(row[2]) if row[2] else 0.0,
|
|
"total_ttc": float(row[3]) if row[3] else 0.0,
|
|
"montant_regle": float(row[4]) if row[4] else 0.0,
|
|
"client_code": row[5].strip() if row[5] else "",
|
|
}
|
|
|
|
|
|
# ============================================================
|
|
# INTROSPECTION COMPLÈTE
|
|
# ============================================================
|
|
|
|
|
|
def introspecter_document_complet(connector, numero_facture: str) -> Dict:
|
|
"""
|
|
Introspection COMPLÈTE d'un document pour découvrir toutes les méthodes
|
|
et propriétés disponibles.
|
|
"""
|
|
if not connector.cial:
|
|
raise RuntimeError("Connexion Sage non établie")
|
|
|
|
result = {
|
|
"numero_facture": numero_facture,
|
|
"persist": {},
|
|
"IBODocumentVente3": {},
|
|
"IBODocument3": {},
|
|
"IPMDocument": {},
|
|
}
|
|
|
|
try:
|
|
with connector._com_context(), connector._lock_com:
|
|
factory = connector.cial.FactoryDocumentVente
|
|
|
|
if not factory.ExistPiece(60, numero_facture):
|
|
raise ValueError(f"Facture {numero_facture} introuvable")
|
|
|
|
persist = factory.ReadPiece(60, numero_facture)
|
|
|
|
# 1. Attributs du persist brut
|
|
persist_attrs = [a for a in dir(persist) if not a.startswith("_")]
|
|
result["persist"]["all_attrs"] = persist_attrs
|
|
result["persist"]["methods"] = []
|
|
result["persist"]["properties"] = []
|
|
|
|
for attr in persist_attrs:
|
|
try:
|
|
val = getattr(persist, attr, None)
|
|
if callable(val):
|
|
result["persist"]["methods"].append(attr)
|
|
else:
|
|
result["persist"]["properties"].append(
|
|
{
|
|
"name": attr,
|
|
"value": str(val)[:100] if val is not None else None,
|
|
}
|
|
)
|
|
except Exception as e:
|
|
result["persist"]["properties"].append(
|
|
{"name": attr, "error": str(e)[:50]}
|
|
)
|
|
|
|
# Chercher spécifiquement les attributs liés à validation/valide
|
|
result["persist"]["validation_related"] = [
|
|
a
|
|
for a in persist_attrs
|
|
if any(
|
|
x in a.lower()
|
|
for x in ["valid", "lock", "confirm", "statut", "etat"]
|
|
)
|
|
]
|
|
|
|
# 2. IBODocumentVente3
|
|
try:
|
|
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
|
|
doc.Read()
|
|
|
|
doc_attrs = [a for a in dir(doc) if not a.startswith("_")]
|
|
result["IBODocumentVente3"]["all_attrs"] = doc_attrs
|
|
result["IBODocumentVente3"]["methods"] = []
|
|
result["IBODocumentVente3"]["properties_with_values"] = []
|
|
|
|
# Lister les méthodes
|
|
for attr in doc_attrs:
|
|
try:
|
|
val = getattr(doc, attr, None)
|
|
if callable(val):
|
|
result["IBODocumentVente3"]["methods"].append(attr)
|
|
except:
|
|
pass
|
|
|
|
# Chercher DO_* properties
|
|
result["IBODocumentVente3"]["DO_properties"] = []
|
|
for attr in doc_attrs:
|
|
if attr.startswith("DO_"):
|
|
try:
|
|
val = getattr(doc, attr, "ERROR")
|
|
result["IBODocumentVente3"]["DO_properties"].append(
|
|
{"name": attr, "value": str(val)[:50]}
|
|
)
|
|
except Exception as e:
|
|
result["IBODocumentVente3"]["DO_properties"].append(
|
|
{"name": attr, "error": str(e)[:50]}
|
|
)
|
|
|
|
# Chercher les attributs liés à validation
|
|
result["IBODocumentVente3"]["validation_related"] = [
|
|
a
|
|
for a in doc_attrs
|
|
if any(
|
|
x in a.lower()
|
|
for x in ["valid", "lock", "confirm", "statut", "etat"]
|
|
)
|
|
]
|
|
|
|
except Exception as e:
|
|
result["IBODocumentVente3"]["error"] = str(e)
|
|
|
|
# 3. IBODocument3
|
|
try:
|
|
doc3 = win32com.client.CastTo(persist, "IBODocument3")
|
|
doc3.Read()
|
|
|
|
doc3_attrs = [a for a in dir(doc3) if not a.startswith("_")]
|
|
result["IBODocument3"]["all_attrs"] = doc3_attrs
|
|
result["IBODocument3"]["validation_related"] = [
|
|
a
|
|
for a in doc3_attrs
|
|
if any(
|
|
x in a.lower()
|
|
for x in ["valid", "lock", "confirm", "statut", "etat"]
|
|
)
|
|
]
|
|
|
|
except Exception as e:
|
|
result["IBODocument3"]["error"] = str(e)
|
|
|
|
# 4. IPMDocument
|
|
try:
|
|
pmdoc = win32com.client.CastTo(persist, "IPMDocument")
|
|
|
|
pmdoc_attrs = [a for a in dir(pmdoc) if not a.startswith("_")]
|
|
result["IPMDocument"]["all_attrs"] = pmdoc_attrs
|
|
result["IPMDocument"]["methods"] = [
|
|
a for a in pmdoc_attrs if callable(getattr(pmdoc, a, None))
|
|
]
|
|
|
|
except Exception as e:
|
|
result["IPMDocument"]["error"] = str(e)
|
|
|
|
# 5. Chercher FactoryDocument* sur le document
|
|
result["factories_on_doc"] = []
|
|
for attr in persist_attrs:
|
|
if "Factory" in attr:
|
|
result["factories_on_doc"].append(attr)
|
|
|
|
except Exception as e:
|
|
result["global_error"] = str(e)
|
|
|
|
return result
|
|
|
|
|
|
def introspecter_validation(connector, numero_facture: str = None) -> Dict:
|
|
"""
|
|
Introspection pour découvrir les méthodes de validation.
|
|
"""
|
|
if not connector.cial:
|
|
raise RuntimeError("Connexion Sage non établie")
|
|
|
|
result = {}
|
|
|
|
try:
|
|
with connector._com_context(), connector._lock_com:
|
|
# Tous les CreateProcess
|
|
cial_attrs = [a for a in dir(connector.cial) if not a.startswith("_")]
|
|
result["all_createprocess"] = [
|
|
a for a in cial_attrs if "CreateProcess" in a
|
|
]
|
|
|
|
# Explorer chaque process
|
|
for process_name in result["all_createprocess"]:
|
|
try:
|
|
process = getattr(connector.cial, process_name)()
|
|
process_attrs = [a for a in dir(process) if not a.startswith("_")]
|
|
result[process_name] = {
|
|
"attrs": process_attrs,
|
|
"has_valider": "Valider" in process_attrs
|
|
or "Valid" in str(process_attrs),
|
|
"has_document": "Document" in process_attrs,
|
|
}
|
|
except Exception as e:
|
|
result[process_name] = {"error": str(e)}
|
|
|
|
# Introspection document si fourni
|
|
if numero_facture:
|
|
result["document"] = introspecter_document_complet(
|
|
connector, numero_facture
|
|
)
|
|
|
|
except Exception as e:
|
|
result["global_error"] = str(e)
|
|
|
|
return result
|
|
|
|
|
|
def valider_facture(connector, numero_facture: str) -> Dict:
|
|
"""
|
|
Valide une facture via SQL direct
|
|
⚠️ Contourne les règles métier Sage - à utiliser avec précaution
|
|
"""
|
|
logger.info(f"🔒 Validation facture {numero_facture} (SQL direct)")
|
|
|
|
# Vérifications préalables
|
|
with connector._get_sql_connection() as conn:
|
|
cursor = conn.cursor()
|
|
|
|
# Vérifier que la facture existe et peut être validée
|
|
cursor.execute(
|
|
"""
|
|
SELECT DO_Valide, DO_Statut, DO_TotalTTC, DO_MontantRegle
|
|
FROM F_DOCENTETE
|
|
WHERE DO_Piece = ? AND DO_Type = 6
|
|
""",
|
|
(numero_facture,),
|
|
)
|
|
|
|
row = cursor.fetchone()
|
|
if not row:
|
|
raise ValueError(f"Facture {numero_facture} introuvable")
|
|
|
|
valide_avant, statut, total_ttc, montant_regle = row
|
|
|
|
if valide_avant == 1:
|
|
return {"numero_facture": numero_facture, "deja_valide": True}
|
|
|
|
if statut == 6: # Annulé
|
|
raise ValueError("Facture annulée, validation impossible")
|
|
|
|
# Valider via SQL
|
|
cursor.execute(
|
|
"""
|
|
UPDATE F_DOCENTETE
|
|
SET DO_Valide = 1, DO_Imprim = 1
|
|
WHERE DO_Piece = ? AND DO_Type = 6
|
|
""",
|
|
(numero_facture,),
|
|
)
|
|
|
|
conn.commit()
|
|
|
|
# Vérifier
|
|
cursor.execute(
|
|
"""
|
|
SELECT DO_Valide, DO_Imprim
|
|
FROM F_DOCENTETE
|
|
WHERE DO_Piece = ? AND DO_Type = 6
|
|
""",
|
|
(numero_facture,),
|
|
)
|
|
|
|
valide_apres, imprim_apres = cursor.fetchone()
|
|
|
|
logger.info(f" SQL: DO_Valide={valide_apres}, DO_Imprim={imprim_apres}")
|
|
|
|
return {
|
|
"numero_facture": numero_facture,
|
|
"methode": "SQL_DIRECT",
|
|
"DO_Valide": valide_apres == 1,
|
|
"DO_Imprim": imprim_apres == 1,
|
|
"success": valide_apres == 1,
|
|
"warning": "Validation SQL directe - règles métier Sage contournées",
|
|
}
|
|
|
|
|
|
def devalider_facture(connector, numero_facture: str) -> Dict:
|
|
"""
|
|
Dévalide une facture (retire le cadenas)
|
|
"""
|
|
if not connector.cial:
|
|
raise RuntimeError("Connexion Sage non établie")
|
|
|
|
logger.info(f"🔓 Dévalidation facture {numero_facture}")
|
|
|
|
info = _get_facture_info_sql(connector, numero_facture)
|
|
if not info:
|
|
raise ValueError(f"Facture {numero_facture} introuvable")
|
|
|
|
if info["statut"] == 6:
|
|
raise ValueError(f"Facture {numero_facture} annulée")
|
|
|
|
if info["statut"] == 5:
|
|
raise ValueError(
|
|
f"Facture {numero_facture} transformée, dévalidation impossible"
|
|
)
|
|
|
|
if info["montant_regle"] > 0.01:
|
|
raise ValueError(
|
|
f"Facture {numero_facture} partiellement réglée ({info['montant_regle']:.2f}€), "
|
|
"dévalidation impossible"
|
|
)
|
|
|
|
if info["valide"] == 0:
|
|
logger.info(f"Facture {numero_facture} déjà non validée")
|
|
return _build_response_sql(
|
|
connector, numero_facture, deja_valide=True, action="devalidation"
|
|
)
|
|
|
|
try:
|
|
with connector._com_context(), connector._lock_com:
|
|
success = _valider_document_com(connector, numero_facture, valider=False)
|
|
if not success:
|
|
raise RuntimeError("La dévalidation COM a échoué")
|
|
|
|
logger.info(f"✅ Facture {numero_facture} dévalidée")
|
|
|
|
except ValueError:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"❌ Erreur COM dévalidation {numero_facture}: {e}", exc_info=True)
|
|
raise RuntimeError(f"Échec dévalidation: {str(e)}")
|
|
|
|
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é après l'opération COM"
|
|
)
|
|
|
|
return _build_response_sql(
|
|
connector, numero_facture, deja_valide=False, action="devalidation"
|
|
)
|
|
|
|
|
|
def _valider_document_com(connector, numero_facture: str, valider: bool = True) -> bool:
|
|
"""
|
|
Tente de valider/dévalider un document via COM.
|
|
"""
|
|
erreurs = []
|
|
action = "validation" if valider else "dévalidation"
|
|
valeur_cible = 1 if valider else 0
|
|
|
|
factory = connector.cial.FactoryDocumentVente
|
|
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: Accès direct à DO_Valide sur IBODocumentVente3
|
|
try:
|
|
logger.info(
|
|
" APPROCHE 1: Modification directe DO_Valide sur IBODocumentVente3..."
|
|
)
|
|
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
|
|
doc.Read()
|
|
|
|
# Vérifier la valeur actuelle
|
|
valeur_avant = getattr(doc, "DO_Valide", None)
|
|
logger.info(f" DO_Valide avant: {valeur_avant}")
|
|
|
|
# Tenter la modification
|
|
doc.DO_Valide = valeur_cible
|
|
doc.Write()
|
|
|
|
# Relire pour vérifier
|
|
doc.Read()
|
|
valeur_apres = getattr(doc, "DO_Valide", None)
|
|
logger.info(f" DO_Valide après: {valeur_apres}")
|
|
|
|
if valeur_apres == valeur_cible:
|
|
logger.info(f" ✅ DO_Valide modifié avec succès!")
|
|
return True
|
|
else:
|
|
erreurs.append(
|
|
f"DO_Valide non modifié (avant={valeur_avant}, après={valeur_apres})"
|
|
)
|
|
|
|
except Exception as e:
|
|
erreurs.append(f"IBODocumentVente3.DO_Valide: {e}")
|
|
logger.warning(f" Erreur: {e}")
|
|
|
|
# APPROCHE 2: Via IBODocument3 (interface parent)
|
|
try:
|
|
logger.info(" APPROCHE 2: Via IBODocument3...")
|
|
doc3 = win32com.client.CastTo(persist, "IBODocument3")
|
|
doc3.Read()
|
|
|
|
if hasattr(doc3, "DO_Valide"):
|
|
doc3.DO_Valide = valeur_cible
|
|
doc3.Write()
|
|
logger.info(f" IBODocument3.DO_Valide = {valeur_cible} OK")
|
|
return True
|
|
else:
|
|
erreurs.append("IBODocument3 n'a pas DO_Valide")
|
|
|
|
except Exception as e:
|
|
erreurs.append(f"IBODocument3: {e}")
|
|
|
|
# APPROCHE 3: Chercher un CreateProcess de validation
|
|
try:
|
|
logger.info(" APPROCHE 3: Recherche CreateProcess de validation...")
|
|
cial_attrs = [a for a in dir(connector.cial) if "CreateProcess" in a]
|
|
validation_processes = [
|
|
a
|
|
for a in cial_attrs
|
|
if any(x in a.lower() for x in ["valid", "confirm", "lock"])
|
|
]
|
|
logger.info(f" CreateProcess trouvés: {cial_attrs}")
|
|
logger.info(f" Liés à validation: {validation_processes}")
|
|
|
|
for proc_name in validation_processes:
|
|
try:
|
|
process = getattr(connector.cial, proc_name)()
|
|
# Lister les attributs du process
|
|
proc_attrs = [a for a in dir(process) if not a.startswith("_")]
|
|
logger.info(f" {proc_name} attrs: {proc_attrs}")
|
|
|
|
if hasattr(process, "Document"):
|
|
process.Document = persist
|
|
if hasattr(process, "Valider"):
|
|
process.Valider = valider
|
|
if hasattr(process, "Process"):
|
|
process.Process()
|
|
logger.info(f" {proc_name}.Process() exécuté!")
|
|
return True
|
|
except Exception as e:
|
|
erreurs.append(f"{proc_name}: {e}")
|
|
|
|
except Exception as e:
|
|
erreurs.append(f"CreateProcess: {e}")
|
|
|
|
# APPROCHE 4: WriteDefault avec paramètres
|
|
try:
|
|
logger.info(" APPROCHE 4: WriteDefault...")
|
|
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
|
|
doc.Read()
|
|
doc.DO_Valide = valeur_cible
|
|
|
|
if hasattr(doc, "WriteDefault"):
|
|
doc.WriteDefault()
|
|
logger.info(" WriteDefault() exécuté")
|
|
return True
|
|
|
|
except Exception as e:
|
|
erreurs.append(f"WriteDefault: {e}")
|
|
|
|
logger.error(f" Toutes les approches de {action} ont échoué: {erreurs}")
|
|
raise RuntimeError(f"Échec {action}: {'; '.join(erreurs[:5])}")
|
|
|
|
|
|
def explorer_impression_validation(connector, numero_facture: str) -> Dict:
|
|
"""Explorer les méthodes d'impression/validation pour les factures"""
|
|
result = {"numero_facture": numero_facture}
|
|
|
|
with connector._com_context(), connector._lock_com:
|
|
factory = connector.cial.FactoryDocumentVente
|
|
persist = factory.ReadPiece(60, numero_facture)
|
|
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
|
|
doc.Read()
|
|
|
|
# 1. CreateProcess_Document SANS paramètre
|
|
try:
|
|
process = connector.cial.CreateProcess_Document()
|
|
attrs = [a for a in dir(process) if not a.startswith("_")]
|
|
result["CreateProcess_Document_no_param"] = {
|
|
"attrs": attrs,
|
|
"print_related": [
|
|
a
|
|
for a in attrs
|
|
if any(
|
|
x in a.lower()
|
|
for x in ["print", "imprim", "edit", "model", "bgc", "valid"]
|
|
)
|
|
],
|
|
}
|
|
|
|
# Essayer d'assigner le document
|
|
if "Document" in attrs:
|
|
try:
|
|
process.Document = doc
|
|
result["CreateProcess_Document_no_param"]["Document_assigned"] = (
|
|
True
|
|
)
|
|
except Exception as e:
|
|
result["CreateProcess_Document_no_param"]["Document_error"] = str(e)
|
|
|
|
if "SetDocument" in attrs:
|
|
try:
|
|
process.SetDocument(doc)
|
|
result["CreateProcess_Document_no_param"]["SetDocument_ok"] = True
|
|
except Exception as e:
|
|
result["CreateProcess_Document_no_param"]["SetDocument_error"] = (
|
|
str(e)
|
|
)
|
|
|
|
except Exception as e:
|
|
result["CreateProcess_Document_no_param_error"] = str(e)
|
|
|
|
# 2. CreateProcess_Document avec type (60 = facture)
|
|
try:
|
|
process = connector.cial.CreateProcess_Document(60)
|
|
attrs = [a for a in dir(process) if not a.startswith("_")]
|
|
result["CreateProcess_Document_type60"] = {"attrs": attrs}
|
|
except Exception as e:
|
|
result["CreateProcess_Document_type60_error"] = str(e)
|
|
|
|
# 3. Explorer TOUS les CreateProcess
|
|
cial_attrs = [a for a in dir(connector.cial) if "CreateProcess" in a]
|
|
result["all_createprocess"] = cial_attrs
|
|
|
|
# 4. Chercher spécifiquement impression/validation
|
|
for proc_name in cial_attrs:
|
|
if any(
|
|
x in proc_name.lower()
|
|
for x in ["imprim", "print", "edit", "valid", "confirm"]
|
|
):
|
|
try:
|
|
proc = getattr(connector.cial, proc_name)()
|
|
proc_attrs = [a for a in dir(proc) if not a.startswith("_")]
|
|
result[proc_name] = {
|
|
"attrs": proc_attrs,
|
|
"has_modele": [
|
|
a
|
|
for a in proc_attrs
|
|
if "model" in a.lower() or "bgc" in a.lower()
|
|
],
|
|
"has_document": "Document" in proc_attrs
|
|
or "SetDocument" in proc_attrs,
|
|
}
|
|
except Exception as e:
|
|
result[proc_name] = {"error": str(e)}
|
|
|
|
# 5. Explorer le document pour méthodes Print/Imprimer
|
|
doc_attrs = [a for a in dir(doc) if not a.startswith("_")]
|
|
result["doc_print_methods"] = [
|
|
a
|
|
for a in doc_attrs
|
|
if any(x in a.lower() for x in ["print", "imprim", "edit", "valid"])
|
|
]
|
|
|
|
# 6. Chercher IPMDocument (Process Manager)
|
|
try:
|
|
pm = win32com.client.CastTo(persist, "IPMDocument")
|
|
pm_attrs = [a for a in dir(pm) if not a.startswith("_")]
|
|
result["IPMDocument"] = {
|
|
"attrs": pm_attrs,
|
|
"print_related": [
|
|
a
|
|
for a in pm_attrs
|
|
if any(
|
|
x in a.lower()
|
|
for x in ["print", "imprim", "edit", "valid", "process"]
|
|
)
|
|
],
|
|
}
|
|
except Exception as e:
|
|
result["IPMDocument_error"] = str(e)
|
|
|
|
# 7. Chemin du modèle BGC
|
|
result["modele_bgc_path"] = (
|
|
r"C:\Users\Public\Documents\Sage\Entreprise 100c\fr-FR\Documents standards\Gestion commerciale\Ventes\Facture client.bgc"
|
|
)
|
|
|
|
return result
|
|
|
|
|
|
# ============================================================
|
|
# FONCTIONS UTILITAIRES
|
|
# ============================================================
|
|
|
|
|
|
def _build_response_sql(
|
|
connector, numero_facture: str, deja_valide: bool, action: str
|
|
) -> Dict:
|
|
"""Construit la réponse via SQL"""
|
|
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",
|
|
"introspecter_validation",
|
|
"introspecter_document_complet",
|
|
]
|