Resolving error on getting all command list and creating a "command"

This commit is contained in:
Fanilo-Nantenaina 2025-11-28 06:14:19 +03:00
parent 8ce32fe8df
commit c522aa5a64
2 changed files with 282 additions and 73 deletions

126
main.py
View file

@ -610,68 +610,124 @@ def contact_read(req: CodeRequest):
@app.post("/sage/commandes/list", dependencies=[Depends(verify_token)]) @app.post("/sage/commandes/list", dependencies=[Depends(verify_token)])
def commandes_list(limit: int = 100, statut: Optional[int] = None): def commandes_list(limit: int = 100, statut: Optional[int] = None):
"""Liste toutes les commandes""" """
📋 Liste toutes les commandes
CORRECTIONS: Gestion robuste des erreurs + logging détaillé
"""
try: try:
if not sage or not sage.cial:
raise HTTPException(503, "Service Sage indisponible")
with sage._com_context(), sage._lock_com: with sage._com_context(), sage._lock_com:
factory = sage.cial.FactoryDocumentVente factory = sage.cial.FactoryDocumentVente
commandes = [] commandes = []
index = 1 index = 1
max_iterations = limit * 3 max_iterations = limit * 10 # Plus de marge
erreurs_consecutives = 0
max_erreurs = 100
while len(commandes) < limit and index < max_iterations: logger.info(f"🔍 Recherche commandes (limit={limit}, statut={statut})")
while (
len(commandes) < limit
and index < max_iterations
and erreurs_consecutives < max_erreurs
):
try: try:
persist = factory.List(index) persist = factory.List(index)
if persist is None: if persist is None:
logger.debug(f"Fin de liste à l'index {index}")
break break
doc = win32com.client.CastTo(persist, "IBODocumentVente3") doc = win32com.client.CastTo(persist, "IBODocumentVente3")
doc.Read() doc.Read()
# Filtrer commandes (type 3) # ✅ CORRECTION 1: Vérifier type de document
if getattr(doc, "DO_Type", -1) != 3: doc_type = getattr(doc, "DO_Type", -1)
# ⚠️ CRITIQUE: Vérifier que c'est bien une commande (type 3)
if doc_type != 3:
index += 1 index += 1
continue continue
doc_statut = getattr(doc, "DO_Statut", 0) doc_statut = getattr(doc, "DO_Statut", 0)
if statut is None or doc_statut == statut: logger.debug(
# Charger client f"Index {index}: Type={doc_type}, Statut={doc_statut}, "
client_code = "" f"Numéro={getattr(doc, 'DO_Piece', '?')}"
client_intitule = "" )
try: # Filtre statut
client_obj = getattr(doc, "Client", None) if statut is not None and doc_statut != statut:
if client_obj: index += 1
client_obj.Read() continue
client_code = getattr(client_obj, "CT_Num", "").strip()
client_intitule = getattr(
client_obj, "CT_Intitule", ""
).strip()
except:
pass
commandes.append( # ✅ CORRECTION 2: Charger client via .Client
{ client_code = ""
"numero": getattr(doc, "DO_Piece", ""), client_intitule = ""
"date": str(getattr(doc, "DO_Date", "")),
"client_code": client_code, try:
"client_intitule": client_intitule, client_obj = getattr(doc, "Client", None)
"total_ht": float(getattr(doc, "DO_TotalHT", 0.0)), if client_obj:
"total_ttc": float(getattr(doc, "DO_TotalTTC", 0.0)), client_obj.Read()
"statut": doc_statut, client_code = getattr(client_obj, "CT_Num", "").strip()
} client_intitule = getattr(
client_obj, "CT_Intitule", ""
).strip()
logger.debug(f" Client: {client_code} - {client_intitule}")
except Exception as e:
logger.debug(f" Erreur chargement client: {e}")
# Fallback sur cache si code disponible
if not client_code:
try:
client_code = getattr(doc, "CT_Num", "").strip()
except:
pass
if client_code and not client_intitule:
client_cache = sage.lire_client(client_code)
if client_cache:
client_intitule = client_cache.get("intitule", "")
commande = {
"numero": getattr(doc, "DO_Piece", ""),
"date": str(getattr(doc, "DO_Date", "")),
"client_code": client_code,
"client_intitule": client_intitule,
"total_ht": float(getattr(doc, "DO_TotalHT", 0.0)),
"total_ttc": float(getattr(doc, "DO_TotalTTC", 0.0)),
"statut": doc_statut,
}
commandes.append(commande)
logger.debug(f" ✅ Commande ajoutée: {commande['numero']}")
erreurs_consecutives = 0
index += 1
except Exception as e:
erreurs_consecutives += 1
logger.debug(f"⚠️ Erreur index {index}: {e}")
index += 1
if erreurs_consecutives >= max_erreurs:
logger.warning(
f"⚠️ Arrêt après {max_erreurs} erreurs consécutives"
) )
break
index += 1 nb_avec_client = sum(1 for c in commandes if c["client_intitule"])
except: logger.info(
index += 1 f"{len(commandes)} commandes retournées "
continue f"({nb_avec_client} avec client)"
)
logger.info(f"{len(commandes)} commandes retournées")
return {"success": True, "data": commandes} return {"success": True, "data": commandes}
except HTTPException:
raise
except Exception as e: except Exception as e:
logger.error(f"Erreur liste commandes: {e}") logger.error(f"Erreur liste commandes: {e}", exc_info=True)
raise HTTPException(500, str(e)) raise HTTPException(500, str(e))

View file

@ -1027,11 +1027,52 @@ class SageConnector:
def transformer_document(self, numero_source, type_source, type_cible): def transformer_document(self, numero_source, type_source, type_cible):
""" """
Transformation avec transaction Transformation avec transaction
CORRIGÉ: Gestion du statut avant transformation CORRECTIONS:
- Validation stricte des types
- Gestion explicite des statuts Sage
- Meilleure gestion d'erreurs
""" """
if not self.cial: if not self.cial:
raise RuntimeError("Connexion Sage non établie") raise RuntimeError("Connexion Sage non établie")
# ✅ CORRECTION 1: Convertir en int si enum passé
type_source = int(type_source)
type_cible = int(type_cible)
logger.info(
f"🔄 Transformation demandée: {numero_source} "
f"(type {type_source}) → type {type_cible}"
)
# ✅ CORRECTION 2: Validation des types AVANT d'accéder à Sage
types_valides = {0, 1, 2, 3, 4, 5}
if type_source not in types_valides or type_cible not in types_valides:
raise ValueError(
f"Types invalides: source={type_source}, cible={type_cible}. "
f"Valeurs valides: {types_valides}"
)
# ✅ CORRECTION 3: Matrice de transformations autorisées par Sage
# Basé sur la doc Sage 100c
transformations_autorisees = {
(0, 3): "Devis → Commande",
(0, 1): "Devis → Bon de livraison",
(3, 1): "Commande → Bon de livraison",
(3, 4): "Commande → Préparation",
(1, 5): "Bon de livraison → Facture",
(4, 1): "Préparation → Bon de livraison",
}
if (type_source, type_cible) not in transformations_autorisees:
raise ValueError(
f"❌ Transformation non autorisée par Sage: "
f"{type_source}{type_cible}. "
f"Transformations valides:\n"
+ "\n".join(
f" - {k}: {v}" for k, v in transformations_autorisees.items()
)
)
try: try:
with self._com_context(), self._lock_com: with self._com_context(), self._lock_com:
# ===== LECTURE SOURCE ===== # ===== LECTURE SOURCE =====
@ -1039,49 +1080,89 @@ 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:
raise ValueError(f"Document {numero_source} introuvable") # ✅ CORRECTION 4: Chercher dans List() si ReadPiece échoue
logger.warning(f"ReadPiece échoué, recherche dans List()...")
persist_source = self._find_document_in_list(
numero_source, type_source
)
if not persist_source:
raise ValueError(
f"Document {numero_source} (type {type_source}) introuvable"
)
doc_source = win32com.client.CastTo(persist_source, "IBODocumentVente3") doc_source = win32com.client.CastTo(persist_source, "IBODocumentVente3")
doc_source.Read() doc_source.Read()
# ✅ VÉRIFICATION STATUT # ✅ CORRECTION 5: Vérifications de statut AVANT transformation
statut_actuel = getattr(doc_source, "DO_Statut", 0) statut_actuel = getattr(doc_source, "DO_Statut", 0)
type_reel = getattr(doc_source, "DO_Type", -1)
logger.info( logger.info(
f"📊 Statut actuel du document {numero_source}: {statut_actuel}" f"📊 Document source: type={type_reel}, statut={statut_actuel}, "
f"numéro={numero_source}"
) )
# ✅ BLOQUER SI DÉJÀ TRANSFORMÉ # Vérifier cohérence type
if statut_actuel == 5: if type_reel != type_source:
raise ValueError( raise ValueError(
f"❌ Le document {numero_source} a déjà été transformé (statut=5)" f"Incohérence: document {numero_source} est de type {type_reel}, "
f"pas de type {type_source}"
) )
# ✅ FORCER STATUT "ACCEPTÉ" (2) SI BROUILLON (0) # ✅ CORRECTION 6: Règles de statut Sage pour transformations
# Statuts Sage: 0=Brouillon, 1=Soumis, 2=Accepté, 3=Réalisé partiellement,
# 4=Réalisé totalement, 5=Transformé, 6=Annulé
if statut_actuel == 5:
raise ValueError(
f"Document {numero_source} déjà transformé (statut=5). "
f"Impossible de le transformer à nouveau."
)
if statut_actuel == 6:
raise ValueError(
f"Document {numero_source} annulé (statut=6). "
f"Impossible de le transformer."
)
# ✅ CORRECTION 7: Forcer statut "Accepté" si nécessaire
if type_source == 0 and statut_actuel == 0: # Devis brouillon if type_source == 0 and statut_actuel == 0: # Devis brouillon
logger.warning( logger.warning(
f"⚠️ Devis {numero_source} en brouillon (statut=0), " f"⚠️ Devis en brouillon (statut=0), "
f"passage à 'Accepté' (statut=2)" f"passage à 'Accepté' (statut=2) requis pour transformation"
) )
try: try:
doc_source.DO_Statut = 2 # Accepté doc_source.DO_Statut = 2 # Accepté
doc_source.Write() doc_source.Write()
logger.info(f"✅ Statut changé: 0 → 2") logger.info(f"✅ Statut changé: 0 → 2")
except Exception as e:
logger.warning(f"Impossible de changer le statut: {e}")
# Récupérer le client # Re-lire pour confirmer
doc_source.Read()
nouveau_statut = getattr(doc_source, "DO_Statut", 0)
if nouveau_statut != 2:
raise RuntimeError(
f"Échec changement statut: toujours à {nouveau_statut}"
)
except Exception as e:
raise RuntimeError(
f"Impossible de changer le statut du devis: {e}. "
f"Le devis doit être accepté avant transformation."
)
# Récupérer client
client_code = "" client_code = ""
try: try:
client_obj = getattr(doc_source, "Client", None) client_obj = getattr(doc_source, "Client", None)
if client_obj: if client_obj:
client_obj.Read() client_obj.Read()
client_code = getattr(client_obj, "CT_Num", "") client_code = getattr(client_obj, "CT_Num", "").strip()
except: except Exception as e:
pass logger.error(f"Erreur lecture client: {e}")
if not client_code: if not client_code:
raise ValueError( raise ValueError(
f"Impossible de récupérer le client du document {numero_source}" f"Client introuvable pour document {numero_source}"
) )
# ===== TRANSACTION ===== # ===== TRANSACTION =====
@ -1094,8 +1175,20 @@ class SageConnector:
logger.warning(f"⚠️ BeginTrans échoué: {e}") logger.warning(f"⚠️ BeginTrans échoué: {e}")
try: try:
# ✅ CRÉATION DOCUMENT CIBLE # ✅ CORRECTION 8: Créer le process avec le type cible VALIDÉ
process = self.cial.CreateProcess_Document(type_cible) logger.info(f"🔨 CreateProcess_Document({type_cible})...")
try:
process = self.cial.CreateProcess_Document(type_cible)
except Exception as e:
logger.error(
f"❌ CreateProcess_Document échoué pour type {type_cible}: {e}"
)
raise RuntimeError(
f"Sage refuse de créer un document de type {type_cible}. "
f"Erreur: {e}"
)
doc_cible = process.Document doc_cible = process.Document
try: try:
@ -1107,19 +1200,21 @@ class SageConnector:
logger.info(f"📄 Document cible créé (type {type_cible})") logger.info(f"📄 Document cible créé (type {type_cible})")
# Associer le client # Associer client
try: try:
factory_client = self.cial.CptaApplication.FactoryClient factory_client = self.cial.CptaApplication.FactoryClient
persist_client = factory_client.ReadNumero(client_code) persist_client = factory_client.ReadNumero(client_code)
if persist_client: if not persist_client:
client_obj_cible = win32com.client.CastTo( raise ValueError(f"Client {client_code} introuvable")
persist_client, "IBOClient3"
) client_obj_cible = win32com.client.CastTo(
client_obj_cible.Read() persist_client, "IBOClient3"
doc_cible.SetDefaultClient(client_obj_cible) )
doc_cible.Write() client_obj_cible.Read()
logger.info(f"👤 Client {client_code} associé") doc_cible.SetDefaultClient(client_obj_cible)
doc_cible.Write()
logger.info(f"👤 Client {client_code} associé")
except Exception as e: except Exception as e:
logger.error(f"❌ Erreur association client: {e}") logger.error(f"❌ Erreur association client: {e}")
raise raise
@ -1146,6 +1241,7 @@ class SageConnector:
factory_article = self.cial.FactoryArticle factory_article = self.cial.FactoryArticle
index = 1 index = 1
nb_lignes = 0 nb_lignes = 0
erreurs_lignes = []
while index <= 1000: while index <= 1000:
try: try:
@ -1164,7 +1260,7 @@ class SageConnector:
ligne_cible_p, "IBODocumentLigne3" ligne_cible_p, "IBODocumentLigne3"
) )
# Récupérer référence article # Récupérer article
article_ref = "" article_ref = ""
try: try:
article_ref = getattr( article_ref = getattr(
@ -1180,7 +1276,7 @@ class SageConnector:
except: except:
pass pass
# Associer article si disponible # Associer article
if article_ref: if article_ref:
try: try:
persist_article = factory_article.ReadReference( persist_article = factory_article.ReadReference(
@ -1236,37 +1332,61 @@ class SageConnector:
index += 1 index += 1
except Exception as e: except Exception as e:
erreurs_lignes.append(f"Ligne {index}: {str(e)}")
logger.debug(f"Erreur ligne {index}: {e}") logger.debug(f"Erreur ligne {index}: {e}")
index += 1 index += 1
if index > 1000: if index > 1000:
break break
if nb_lignes == 0:
raise RuntimeError(
f"Aucune ligne copiée. Erreurs: {'; '.join(erreurs_lignes)}"
)
logger.info(f"{nb_lignes} lignes copiées")
# ===== VALIDATION ===== # ===== VALIDATION =====
doc_cible.Write() doc_cible.Write()
process.Process() logger.info("💾 Document cible écrit")
numero_cible = getattr(doc_cible, "DO_Piece", "") process.Process()
logger.info("⚙️ Process() exécuté")
# 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: if not numero_cible:
raise RuntimeError("Numéro document cible vide") numero_cible = getattr(doc_cible, "DO_Piece", "")
if not numero_cible:
raise RuntimeError("Numéro document cible vide après création")
# Commit # Commit
if transaction_active: if transaction_active:
self.cial.CptaApplication.CommitTrans() self.cial.CptaApplication.CommitTrans()
logger.info("✅ Transaction committée") logger.info("✅ Transaction committée")
# ✅ MAJ STATUT SOURCE → TRANSFORMÉ (5) # MAJ statut source → Transformé
try: try:
doc_source.DO_Statut = 5 # Transformé doc_source.DO_Statut = 5 # Transformé
doc_source.Write() doc_source.Write()
logger.info( logger.info(f"✅ Statut source mis à jour: → 5 (TRANSFORMÉ)")
f"✅ Statut source mis à jour: {statut_actuel} → 5 (TRANSFORMÉ)"
)
except Exception as e: except Exception as e:
logger.warning(f"⚠️ Impossible de MAJ statut source: {e}") logger.warning(f"⚠️ Impossible de MAJ statut source: {e}")
logger.info( logger.info(
f"✅ Transformation: {numero_source} ({type_source}) → " f"✅✅✅ TRANSFORMATION RÉUSSIE: "
f"{numero_source} ({type_source}) → "
f"{numero_cible} ({type_cible}) - {nb_lignes} lignes" f"{numero_cible} ({type_cible}) - {nb_lignes} lignes"
) )
@ -1290,6 +1410,39 @@ class SageConnector:
logger.error(f"❌ Erreur transformation: {e}", exc_info=True) logger.error(f"❌ Erreur transformation: {e}", exc_info=True)
raise RuntimeError(f"Échec transformation: {str(e)}") raise RuntimeError(f"Échec transformation: {str(e)}")
def _find_document_in_list(self, numero, type_doc):
"""
NOUVEAU: Cherche un document dans List() si ReadPiece échoue
Utile pour les documents en brouillon
"""
try:
factory = self.cial.FactoryDocumentVente
index = 1
while index < 10000:
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
and getattr(doc, "DO_Piece", "") == numero
):
return persist
index += 1
except:
index += 1
continue
return None
except:
return None
# ========================================================================= # =========================================================================
# CHAMPS LIBRES (US-A3) # CHAMPS LIBRES (US-A3)
# ========================================================================= # =========================================================================