Diagnostic document transformation error

This commit is contained in:
Fanilo-Nantenaina 2025-11-28 06:30:32 +03:00
parent 3505ecfd2b
commit 92c79f1362
2 changed files with 328 additions and 226 deletions

216
main.py
View file

@ -1061,6 +1061,222 @@ def diagnostiquer_devis(numero: str):
raise HTTPException(500, str(e)) raise HTTPException(500, str(e))
@app.get("/sage/diagnostic/configuration", dependencies=[Depends(verify_token)])
def diagnostic_configuration():
"""
DIAGNOSTIC COMPLET de la configuration Sage
Teste:
- Quelles méthodes COM sont disponibles
- Quels types de documents sont autorisés
- Quelles permissions l'utilisateur a
- Version de Sage
"""
try:
if not sage or not sage.cial:
raise HTTPException(503, "Service Sage indisponible")
with sage._com_context(), sage._lock_com:
diagnostic = {
"connexion": "OK",
"chemin_base": sage.chemin_base,
"utilisateur": sage.utilisateur,
}
# Version Sage
try:
version = getattr(sage.cial, "Version", "Inconnue")
diagnostic["version_sage"] = str(version)
except:
diagnostic["version_sage"] = "Non disponible"
# Test des méthodes disponibles sur IBSCIALApplication3
methodes_disponibles = []
methodes_a_tester = [
"CreateProcess_Document",
"FactoryDocumentVente",
"FactoryArticle",
"CptaApplication",
"BeginTrans",
"CommitTrans",
"RollbackTrans",
]
for methode in methodes_a_tester:
try:
if hasattr(sage.cial, methode):
methodes_disponibles.append(methode)
except:
pass
diagnostic["methodes_cial_disponibles"] = methodes_disponibles
# Test des types de documents autorisés
types_autorises = []
types_bloques = []
for type_doc in range(6): # 0-5
try:
# Essayer de créer un process (sans le valider)
process = sage.cial.CreateProcess_Document(type_doc)
if process:
types_autorises.append(
{
"type": type_doc,
"libelle": {
0: "Devis",
1: "Bon de livraison",
2: "Bon de retour",
3: "Commande",
4: "Preparation",
5: "Facture",
}[type_doc],
}
)
# Ne pas valider, juste tester
del process
except Exception as e:
types_bloques.append(
{
"type": type_doc,
"libelle": {
0: "Devis",
1: "Bon de livraison",
2: "Bon de retour",
3: "Commande",
4: "Preparation",
5: "Facture",
}[type_doc],
"erreur": str(e)[:200],
}
)
diagnostic["types_documents_autorises"] = types_autorises
diagnostic["types_documents_bloques"] = types_bloques
# Test TransformInto() sur un devis test
try:
factory = sage.cial.FactoryDocumentVente
# Chercher n'importe quel devis
index = 1
devis_test = None
while index < 100:
try:
persist = factory.List(index)
if persist is None:
break
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
doc.Read()
if getattr(doc, "DO_Type", -1) == 0: # Devis
devis_test = doc
break
index += 1
except:
index += 1
if devis_test:
# Tester si TransformInto existe
if hasattr(devis_test, "TransformInto"):
diagnostic["transforminto_disponible"] = True
diagnostic["transforminto_test"] = "Methode existe (non testee)"
else:
diagnostic["transforminto_disponible"] = False
diagnostic["transforminto_test"] = (
"Methode TransformInto() inexistante"
)
else:
diagnostic["transforminto_disponible"] = (
"Impossible de tester (aucun devis trouve)"
)
except Exception as e:
diagnostic["transforminto_disponible"] = False
diagnostic["transforminto_erreur"] = str(e)
# Modules Sage actifs
try:
# Tester l'accès aux différentes factories
modules = {}
try:
sage.cial.FactoryDocumentVente
modules["Ventes"] = "OK"
except:
modules["Ventes"] = "INACCESSIBLE"
try:
sage.cial.CptaApplication.FactoryClient
modules["Clients"] = "OK"
except:
modules["Clients"] = "INACCESSIBLE"
try:
sage.cial.FactoryArticle
modules["Articles"] = "OK"
except:
modules["Articles"] = "INACCESSIBLE"
diagnostic["modules_actifs"] = modules
except Exception as e:
diagnostic["modules_actifs_erreur"] = str(e)
# Compter documents existants
try:
counts = {}
factory = sage.cial.FactoryDocumentVente
for type_doc in range(6):
count = 0
index = 1
while index < 1000:
try:
persist = factory.List(index)
if persist is None:
break
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
doc.Read()
if getattr(doc, "DO_Type", -1) == type_doc:
count += 1
index += 1
except:
index += 1
break
counts[
{
0: "Devis",
1: "Bons_livraison",
2: "Bons_retour",
3: "Commandes",
4: "Preparations",
5: "Factures",
}[type_doc]
] = count
diagnostic["documents_existants"] = counts
except Exception as e:
diagnostic["documents_existants_erreur"] = str(e)
logger.info("[DIAG] Configuration Sage analysee")
return {"success": True, "diagnostic": diagnostic}
except HTTPException:
raise
except Exception as e:
logger.error(f"[DIAG] Erreur diagnostic config: {e}", exc_info=True)
raise HTTPException(500, str(e))
# ===================================================== # =====================================================
# LANCEMENT # LANCEMENT
# ===================================================== # =====================================================

View file

@ -1026,13 +1026,15 @@ class SageConnector:
def transformer_document(self, numero_source, type_source, type_cible): def transformer_document(self, numero_source, type_source, type_cible):
""" """
Transformation de document avec gestion complète des statuts Transformation de document avec la méthode NATIVE de Sage
CORRECTIONS: CHANGEMENT MAJEUR:
- Pas d'émojis dans les logs - Utilise TransformInto() au lieu de CreateProcess_Document()
- Validation stricte des statuts Sage - Méthode officielle Sage pour les transformations
- Gestion des documents "Réalisé partiellement" - Gère automatiquement les numéros, statuts, et lignes
- Meilleure détection d'erreurs
Documentation Sage:
IBODocumentVente3.TransformInto(DO_Type: int) -> IBODocumentVente3
""" """
if not self.cial: if not self.cial:
raise RuntimeError("Connexion Sage non etablie") raise RuntimeError("Connexion Sage non etablie")
@ -1058,8 +1060,10 @@ class SageConnector:
transformations_autorisees = { transformations_autorisees = {
(0, 3): "Devis -> Commande", (0, 3): "Devis -> Commande",
(0, 1): "Devis -> Bon de livraison", (0, 1): "Devis -> Bon de livraison",
(0, 5): "Devis -> Facture", # Peut être supporté selon config
(3, 1): "Commande -> Bon de livraison", (3, 1): "Commande -> Bon de livraison",
(3, 4): "Commande -> Preparation", (3, 4): "Commande -> Preparation",
(3, 5): "Commande -> Facture", # Direct si autorisé
(1, 5): "Bon de livraison -> Facture", (1, 5): "Bon de livraison -> Facture",
(4, 1): "Preparation -> Bon de livraison", (4, 1): "Preparation -> Bon de livraison",
} }
@ -1081,7 +1085,9 @@ class SageConnector:
persist_source = factory.ReadPiece(type_source, numero_source) persist_source = factory.ReadPiece(type_source, numero_source)
if not persist_source: if not persist_source:
logger.warning(f"ReadPiece failed, searching in List()...") logger.warning(
f"[TRANSFORM] ReadPiece failed, searching in List()..."
)
persist_source = self._find_document_in_list( persist_source = self._find_document_in_list(
numero_source, type_source numero_source, type_source
) )
@ -1110,14 +1116,7 @@ class SageConnector:
f"pas de type {type_source}" f"pas de type {type_source}"
) )
# RÈGLES DE STATUT SAGE # RÈGLES DE STATUT
# 0=Brouillon, 1=Soumis, 2=Accepte, 3=Realise partiellement,
# 4=Realise totalement, 5=Transforme, 6=Annule
# CORRECTION CRITIQUE: Statut 3 = "Réalisé partiellement"
# Cela signifie qu'une partie du document a déjà été transformée
# mais pas tout. Sage REFUSE de créer un nouveau document dans ce cas.
if statut_actuel == 5: if statut_actuel == 5:
raise ValueError( raise ValueError(
f"Document {numero_source} deja transforme (statut=5). " f"Document {numero_source} deja transforme (statut=5). "
@ -1130,12 +1129,10 @@ class SageConnector:
f"Impossible de le transformer." f"Impossible de le transformer."
) )
# CORRECTION: Statut 3 ou 4 = document déjà réalisé/livré
if statut_actuel in [3, 4]: if statut_actuel in [3, 4]:
raise ValueError( raise ValueError(
f"Document {numero_source} deja realise (statut={statut_actuel}). " f"Document {numero_source} deja realise (statut={statut_actuel}). "
f"Ce document a deja ete transforme partiellement ou totalement. " f"Ce document a deja ete transforme partiellement ou totalement."
f"Verifiez si une commande/BL/facture n'existe pas deja pour ce document."
) )
# Forcer statut "Accepté" si brouillon # Forcer statut "Accepté" si brouillon
@ -1157,25 +1154,7 @@ class SageConnector:
f"Echec changement statut: toujours a {nouveau_statut}" f"Echec changement statut: toujours a {nouveau_statut}"
) )
except Exception as e: except Exception as e:
raise RuntimeError( raise RuntimeError(f"Impossible de changer le statut: {e}")
f"Impossible de changer le statut du devis: {e}. "
f"Le devis doit etre accepte avant transformation."
)
# Récupérer client
client_code = ""
try:
client_obj = getattr(doc_source, "Client", None)
if client_obj:
client_obj.Read()
client_code = getattr(client_obj, "CT_Num", "").strip()
except Exception as e:
logger.error(f"Erreur lecture client: {e}")
if not client_code:
raise ValueError(
f"Client introuvable pour document {numero_source}"
)
# ===== TRANSACTION ===== # ===== TRANSACTION =====
transaction_active = False transaction_active = False
@ -1183,27 +1162,31 @@ class SageConnector:
self.cial.CptaApplication.BeginTrans() self.cial.CptaApplication.BeginTrans()
transaction_active = True transaction_active = True
logger.debug("[TRANSFORM] Transaction demarree") logger.debug("[TRANSFORM] Transaction demarree")
except AttributeError:
# BeginTrans n'existe pas sur cette version
logger.debug(
"[TRANSFORM] BeginTrans non disponible, continue sans transaction"
)
except Exception as e: except Exception as e:
logger.warning(f"[TRANSFORM] BeginTrans echoue: {e}") logger.warning(f"[TRANSFORM] BeginTrans echoue: {e}")
try: try:
# CRÉATION DOCUMENT CIBLE # ✅✅✅ MÉTHODE NATIVE SAGE: TransformInto() ✅✅✅
logger.info(f"[TRANSFORM] CreateProcess_Document({type_cible})...") logger.info(f"[TRANSFORM] Appel TransformInto({type_cible})...")
try: try:
process = self.cial.CreateProcess_Document(type_cible) # La méthode TransformInto() retourne le nouveau document
except Exception as e: doc_cible = doc_source.TransformInto(type_cible)
logger.error(
f"[TRANSFORM] CreateProcess_Document echoue pour type {type_cible}: {e}" if doc_cible is None:
)
raise RuntimeError( raise RuntimeError(
f"Sage refuse de creer un document de type {type_cible}. " "TransformInto() a retourne None. "
f"Verifiez la configuration Sage et les permissions. " "Verifiez la configuration Sage et les autorisations."
f"Erreur: {e}"
) )
doc_cible = process.Document logger.info("[TRANSFORM] TransformInto() execute avec succes")
# Cast vers le bon type
try: try:
doc_cible = win32com.client.CastTo( doc_cible = win32com.client.CastTo(
doc_cible, "IBODocumentVente3" doc_cible, "IBODocumentVente3"
@ -1211,186 +1194,87 @@ class SageConnector:
except: except:
pass pass
logger.info(f"[TRANSFORM] Document cible cree (type {type_cible})") # Lire le document cible
doc_cible.Read()
# Associer client # Récupérer le numéro
try:
factory_client = self.cial.CptaApplication.FactoryClient
persist_client = factory_client.ReadNumero(client_code)
if not persist_client:
raise ValueError(f"Client {client_code} introuvable")
client_obj_cible = win32com.client.CastTo(
persist_client, "IBOClient3"
)
client_obj_cible.Read()
doc_cible.SetDefaultClient(client_obj_cible)
doc_cible.Write()
logger.info(f"[TRANSFORM] Client {client_code} associe")
except Exception as e:
logger.error(f"[TRANSFORM] Erreur association client: {e}")
raise
# Date
import pywintypes
doc_cible.DO_Date = pywintypes.Time(datetime.now())
# Référence
try:
doc_cible.DO_Ref = f"Trans. {numero_source}"
except:
pass
# ===== COPIE LIGNES =====
try:
factory_lignes_source = doc_source.FactoryDocumentLigne
factory_lignes_cible = doc_cible.FactoryDocumentLigne
except:
factory_lignes_source = doc_source.FactoryDocumentVenteLigne
factory_lignes_cible = doc_cible.FactoryDocumentVenteLigne
factory_article = self.cial.FactoryArticle
index = 1
nb_lignes = 0
erreurs_lignes = []
while index <= 1000:
try:
ligne_source_p = factory_lignes_source.List(index)
if ligne_source_p is None:
break
ligne_source = win32com.client.CastTo(
ligne_source_p, "IBODocumentLigne3"
)
ligne_source.Read()
# Créer ligne cible
ligne_cible_p = factory_lignes_cible.Create()
ligne_cible = win32com.client.CastTo(
ligne_cible_p, "IBODocumentLigne3"
)
# Récupérer article
article_ref = ""
try:
article_ref = getattr(
ligne_source, "AR_Ref", ""
).strip()
if not article_ref:
article_obj = getattr(ligne_source, "Article", None)
if article_obj:
article_obj.Read()
article_ref = getattr(
article_obj, "AR_Ref", ""
).strip()
except:
pass
# Associer article
if article_ref:
try:
persist_article = factory_article.ReadReference(
article_ref
)
if persist_article:
article_obj = win32com.client.CastTo(
persist_article, "IBOArticle3"
)
article_obj.Read()
quantite = float(
getattr(ligne_source, "DL_Qte", 1.0)
)
try:
ligne_cible.SetDefaultArticleReference(
article_ref, quantite
)
except:
ligne_cible.SetDefaultArticle(
article_obj, quantite
)
except Exception as e:
logger.debug(
f"Erreur association article {article_ref}: {e}"
)
# Copier propriétés
ligne_cible.DL_Design = getattr(
ligne_source, "DL_Design", ""
)
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
try:
remise = float(
getattr(ligne_source, "DL_Remise01REM_Valeur", 0.0)
)
if remise > 0:
ligne_cible.DL_Remise01REM_Valeur = remise
ligne_cible.DL_Remise01REM_Type = 0
except:
pass
ligne_cible.Write()
nb_lignes += 1
index += 1
except Exception as e:
erreurs_lignes.append(f"Ligne {index}: {str(e)}")
logger.debug(f"Erreur ligne {index}: {e}")
index += 1
if index > 1000:
break
if nb_lignes == 0:
raise RuntimeError(
f"Aucune ligne copiee. Erreurs: {'; '.join(erreurs_lignes)}"
)
logger.info(f"[TRANSFORM] {nb_lignes} lignes copiees")
# ===== VALIDATION =====
doc_cible.Write()
logger.info("[TRANSFORM] Document cible ecrit")
process.Process()
logger.info("[TRANSFORM] Process() execute")
# Récupérer numéro
numero_cible = None
try:
doc_result = process.DocumentResult
if doc_result:
doc_result = win32com.client.CastTo(
doc_result, "IBODocumentVente3"
)
doc_result.Read()
numero_cible = getattr(doc_result, "DO_Piece", "")
except:
pass
if not numero_cible:
numero_cible = getattr(doc_cible, "DO_Piece", "") numero_cible = getattr(doc_cible, "DO_Piece", "")
if not numero_cible: if not numero_cible:
raise RuntimeError("Numero document cible vide apres creation") raise RuntimeError(
"Numero document cible vide apres transformation"
)
# Commit # Compter les lignes
try:
factory_lignes = doc_cible.FactoryDocumentLigne
except:
factory_lignes = doc_cible.FactoryDocumentVenteLigne
nb_lignes = 0
index = 1
while index <= 1000:
try:
ligne_p = factory_lignes.List(index)
if ligne_p is None:
break
nb_lignes += 1
index += 1
except:
break
logger.info(
f"[TRANSFORM] Document cible cree: {numero_cible} avec {nb_lignes} lignes"
)
except AttributeError as e:
# TransformInto() n'existe pas sur cette version de Sage
logger.error(f"[TRANSFORM] TransformInto() non disponible: {e}")
raise RuntimeError(
f"La methode TransformInto() n'est pas disponible sur votre version de Sage 100c. "
f"Vous devez soit: "
f"(1) Mettre a jour Sage, ou "
f"(2) Activer le module de gestion commerciale pour les commandes, ou "
f"(3) Utiliser l'interface Sage manuellement pour les transformations."
)
except Exception as e:
logger.error(f"[TRANSFORM] TransformInto() echoue: {e}")
# Essayer de déterminer la cause
if "Valeur invalide" in str(e):
raise RuntimeError(
f"Sage refuse la transformation vers le type {type_cible}. "
f"Causes possibles:\n"
f"1. Le module 'Commandes' n'est pas active dans votre licence Sage\n"
f"2. L'utilisateur n'a pas les droits sur ce type de document\n"
f"3. La configuration Sage bloque ce type de transformation\n"
f"4. Il manque des parametres obligatoires (depot, tarif, etc.)\n\n"
f"Verifiez dans Sage: Fichier > Autorisations > Gestion Commerciale"
)
elif "Acces refuse" in str(e) or "Access denied" in str(e):
raise RuntimeError(
f"Acces refuse pour creer une commande (type {type_cible}). "
f"Verifiez les droits utilisateur dans Sage: "
f"Fichier > Autorisations > Votre utilisateur"
)
else:
raise RuntimeError(
f"Erreur Sage lors de la transformation: {e}\n"
f"Consultez les logs Sage pour plus de details."
)
# Commit transaction
if transaction_active: if transaction_active:
try:
self.cial.CptaApplication.CommitTrans() self.cial.CptaApplication.CommitTrans()
logger.info("[TRANSFORM] Transaction committee") logger.info("[TRANSFORM] Transaction committee")
except:
pass
# MAJ statut source -> Transformé # MAJ statut source -> Transformé
try: try:
doc_source.Read() # Re-lire au cas où
doc_source.DO_Statut = 5 doc_source.DO_Statut = 5
doc_source.Write() doc_source.Write()
logger.info( logger.info(
@ -1446,6 +1330,7 @@ class SageConnector:
getattr(doc, "DO_Type", -1) == type_doc getattr(doc, "DO_Type", -1) == type_doc
and getattr(doc, "DO_Piece", "") == numero and getattr(doc, "DO_Piece", "") == numero
): ):
logger.info(f"[TRANSFORM] Document trouve a l'index {index}")
return persist return persist
index += 1 index += 1
@ -1454,7 +1339,8 @@ class SageConnector:
continue continue
return None return None
except: except Exception as e:
logger.error(f"[TRANSFORM] Erreur recherche document: {e}")
return None return None
# ========================================================================= # =========================================================================