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))
@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
# =====================================================

View file

@ -1026,13 +1026,15 @@ class SageConnector:
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:
- Pas d'émojis dans les logs
- Validation stricte des statuts Sage
- Gestion des documents "Réalisé partiellement"
- Meilleure détection d'erreurs
CHANGEMENT MAJEUR:
- Utilise TransformInto() au lieu de CreateProcess_Document()
- Méthode officielle Sage pour les transformations
- Gère automatiquement les numéros, statuts, et lignes
Documentation Sage:
IBODocumentVente3.TransformInto(DO_Type: int) -> IBODocumentVente3
"""
if not self.cial:
raise RuntimeError("Connexion Sage non etablie")
@ -1058,8 +1060,10 @@ class SageConnector:
transformations_autorisees = {
(0, 3): "Devis -> Commande",
(0, 1): "Devis -> Bon de livraison",
(0, 5): "Devis -> Facture", # Peut être supporté selon config
(3, 1): "Commande -> Bon de livraison",
(3, 4): "Commande -> Preparation",
(3, 5): "Commande -> Facture", # Direct si autorisé
(1, 5): "Bon de livraison -> Facture",
(4, 1): "Preparation -> Bon de livraison",
}
@ -1081,7 +1085,9 @@ class SageConnector:
persist_source = factory.ReadPiece(type_source, numero_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(
numero_source, type_source
)
@ -1110,14 +1116,7 @@ class SageConnector:
f"pas de type {type_source}"
)
# RÈGLES DE STATUT SAGE
# 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.
# RÈGLES DE STATUT
if statut_actuel == 5:
raise ValueError(
f"Document {numero_source} deja transforme (statut=5). "
@ -1130,12 +1129,10 @@ class SageConnector:
f"Impossible de le transformer."
)
# CORRECTION: Statut 3 ou 4 = document déjà réalisé/livré
if statut_actuel in [3, 4]:
raise ValueError(
f"Document {numero_source} deja realise (statut={statut_actuel}). "
f"Ce document a deja ete transforme partiellement ou totalement. "
f"Verifiez si une commande/BL/facture n'existe pas deja pour ce document."
f"Ce document a deja ete transforme partiellement ou totalement."
)
# Forcer statut "Accepté" si brouillon
@ -1157,25 +1154,7 @@ class SageConnector:
f"Echec changement statut: toujours a {nouveau_statut}"
)
except Exception as e:
raise RuntimeError(
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}"
)
raise RuntimeError(f"Impossible de changer le statut: {e}")
# ===== TRANSACTION =====
transaction_active = False
@ -1183,214 +1162,119 @@ class SageConnector:
self.cial.CptaApplication.BeginTrans()
transaction_active = True
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:
logger.warning(f"[TRANSFORM] BeginTrans echoue: {e}")
try:
# CRÉATION DOCUMENT CIBLE
logger.info(f"[TRANSFORM] CreateProcess_Document({type_cible})...")
# ✅✅✅ MÉTHODE NATIVE SAGE: TransformInto() ✅✅✅
logger.info(f"[TRANSFORM] Appel TransformInto({type_cible})...")
try:
process = self.cial.CreateProcess_Document(type_cible)
except Exception as e:
logger.error(
f"[TRANSFORM] CreateProcess_Document echoue pour type {type_cible}: {e}"
)
raise RuntimeError(
f"Sage refuse de creer un document de type {type_cible}. "
f"Verifiez la configuration Sage et les permissions. "
f"Erreur: {e}"
)
# La méthode TransformInto() retourne le nouveau document
doc_cible = doc_source.TransformInto(type_cible)
doc_cible = process.Document
if doc_cible is None:
raise RuntimeError(
"TransformInto() a retourne None. "
"Verifiez la configuration Sage et les autorisations."
)
try:
doc_cible = win32com.client.CastTo(
doc_cible, "IBODocumentVente3"
)
except:
pass
logger.info("[TRANSFORM] TransformInto() execute avec succes")
logger.info(f"[TRANSFORM] Document cible cree (type {type_cible})")
# Associer client
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:
# Cast vers le bon type
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"
doc_cible = win32com.client.CastTo(
doc_cible, "IBODocumentVente3"
)
ligne_source.Read()
except:
pass
# Créer ligne cible
ligne_cible_p = factory_lignes_cible.Create()
ligne_cible = win32com.client.CastTo(
ligne_cible_p, "IBODocumentLigne3"
)
# Lire le document cible
doc_cible.Read()
# 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:
# Récupérer le numéro
numero_cible = getattr(doc_cible, "DO_Piece", "")
if not numero_cible:
raise RuntimeError("Numero document cible vide apres creation")
if not numero_cible:
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:
self.cial.CptaApplication.CommitTrans()
logger.info("[TRANSFORM] Transaction committee")
try:
self.cial.CptaApplication.CommitTrans()
logger.info("[TRANSFORM] Transaction committee")
except:
pass
# MAJ statut source -> Transformé
try:
doc_source.Read() # Re-lire au cas où
doc_source.DO_Statut = 5
doc_source.Write()
logger.info(
@ -1446,6 +1330,7 @@ class SageConnector:
getattr(doc, "DO_Type", -1) == type_doc
and getattr(doc, "DO_Piece", "") == numero
):
logger.info(f"[TRANSFORM] Document trouve a l'index {index}")
return persist
index += 1
@ -1454,7 +1339,8 @@ class SageConnector:
continue
return None
except:
except Exception as e:
logger.error(f"[TRANSFORM] Erreur recherche document: {e}")
return None
# =========================================================================