Resolving error on getting all command list and creating a "command"
This commit is contained in:
parent
8ce32fe8df
commit
c522aa5a64
2 changed files with 282 additions and 73 deletions
126
main.py
126
main.py
|
|
@ -610,68 +610,124 @@ def contact_read(req: CodeRequest):
|
|||
|
||||
@app.post("/sage/commandes/list", dependencies=[Depends(verify_token)])
|
||||
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:
|
||||
if not sage or not sage.cial:
|
||||
raise HTTPException(503, "Service Sage indisponible")
|
||||
|
||||
with sage._com_context(), sage._lock_com:
|
||||
factory = sage.cial.FactoryDocumentVente
|
||||
commandes = []
|
||||
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:
|
||||
persist = factory.List(index)
|
||||
if persist is None:
|
||||
logger.debug(f"Fin de liste à l'index {index}")
|
||||
break
|
||||
|
||||
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
|
||||
doc.Read()
|
||||
|
||||
# Filtrer commandes (type 3)
|
||||
if getattr(doc, "DO_Type", -1) != 3:
|
||||
# ✅ CORRECTION 1: Vérifier type de document
|
||||
doc_type = getattr(doc, "DO_Type", -1)
|
||||
|
||||
# ⚠️ CRITIQUE: Vérifier que c'est bien une commande (type 3)
|
||||
if doc_type != 3:
|
||||
index += 1
|
||||
continue
|
||||
|
||||
doc_statut = getattr(doc, "DO_Statut", 0)
|
||||
|
||||
if statut is None or doc_statut == statut:
|
||||
# Charger client
|
||||
client_code = ""
|
||||
client_intitule = ""
|
||||
logger.debug(
|
||||
f"Index {index}: Type={doc_type}, Statut={doc_statut}, "
|
||||
f"Numéro={getattr(doc, 'DO_Piece', '?')}"
|
||||
)
|
||||
|
||||
try:
|
||||
client_obj = getattr(doc, "Client", None)
|
||||
if client_obj:
|
||||
client_obj.Read()
|
||||
client_code = getattr(client_obj, "CT_Num", "").strip()
|
||||
client_intitule = getattr(
|
||||
client_obj, "CT_Intitule", ""
|
||||
).strip()
|
||||
except:
|
||||
pass
|
||||
# Filtre statut
|
||||
if statut is not None and doc_statut != statut:
|
||||
index += 1
|
||||
continue
|
||||
|
||||
commandes.append(
|
||||
{
|
||||
"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,
|
||||
}
|
||||
# ✅ CORRECTION 2: Charger client via .Client
|
||||
client_code = ""
|
||||
client_intitule = ""
|
||||
|
||||
try:
|
||||
client_obj = getattr(doc, "Client", None)
|
||||
if client_obj:
|
||||
client_obj.Read()
|
||||
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
|
||||
except:
|
||||
index += 1
|
||||
continue
|
||||
nb_avec_client = sum(1 for c in commandes if c["client_intitule"])
|
||||
logger.info(
|
||||
f"✅ {len(commandes)} commandes retournées "
|
||||
f"({nb_avec_client} avec client)"
|
||||
)
|
||||
|
||||
logger.info(f"✅ {len(commandes)} commandes retournées")
|
||||
return {"success": True, "data": commandes}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
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))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1027,11 +1027,52 @@ class SageConnector:
|
|||
def transformer_document(self, numero_source, type_source, type_cible):
|
||||
"""
|
||||
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:
|
||||
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:
|
||||
with self._com_context(), self._lock_com:
|
||||
# ===== LECTURE SOURCE =====
|
||||
|
|
@ -1039,49 +1080,89 @@ class SageConnector:
|
|||
persist_source = factory.ReadPiece(type_source, numero_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.Read()
|
||||
|
||||
# ✅ VÉRIFICATION STATUT
|
||||
# ✅ CORRECTION 5: Vérifications de statut AVANT transformation
|
||||
statut_actuel = getattr(doc_source, "DO_Statut", 0)
|
||||
type_reel = getattr(doc_source, "DO_Type", -1)
|
||||
|
||||
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É
|
||||
if statut_actuel == 5:
|
||||
# Vérifier cohérence type
|
||||
if type_reel != type_source:
|
||||
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
|
||||
logger.warning(
|
||||
f"⚠️ Devis {numero_source} en brouillon (statut=0), "
|
||||
f"passage à 'Accepté' (statut=2)"
|
||||
f"⚠️ Devis en brouillon (statut=0), "
|
||||
f"passage à 'Accepté' (statut=2) requis pour transformation"
|
||||
)
|
||||
try:
|
||||
doc_source.DO_Statut = 2 # Accepté
|
||||
doc_source.Write()
|
||||
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 = ""
|
||||
try:
|
||||
client_obj = getattr(doc_source, "Client", None)
|
||||
if client_obj:
|
||||
client_obj.Read()
|
||||
client_code = getattr(client_obj, "CT_Num", "")
|
||||
except:
|
||||
pass
|
||||
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"Impossible de récupérer le client du document {numero_source}"
|
||||
f"Client introuvable pour document {numero_source}"
|
||||
)
|
||||
|
||||
# ===== TRANSACTION =====
|
||||
|
|
@ -1094,8 +1175,20 @@ class SageConnector:
|
|||
logger.warning(f"⚠️ BeginTrans échoué: {e}")
|
||||
|
||||
try:
|
||||
# ✅ CRÉATION DOCUMENT CIBLE
|
||||
process = self.cial.CreateProcess_Document(type_cible)
|
||||
# ✅ CORRECTION 8: Créer le process avec le type cible VALIDÉ
|
||||
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
|
||||
|
||||
try:
|
||||
|
|
@ -1107,19 +1200,21 @@ class SageConnector:
|
|||
|
||||
logger.info(f"📄 Document cible créé (type {type_cible})")
|
||||
|
||||
# Associer le client
|
||||
# Associer client
|
||||
try:
|
||||
factory_client = self.cial.CptaApplication.FactoryClient
|
||||
persist_client = factory_client.ReadNumero(client_code)
|
||||
|
||||
if persist_client:
|
||||
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"👤 Client {client_code} associé")
|
||||
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"👤 Client {client_code} associé")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Erreur association client: {e}")
|
||||
raise
|
||||
|
|
@ -1146,6 +1241,7 @@ class SageConnector:
|
|||
factory_article = self.cial.FactoryArticle
|
||||
index = 1
|
||||
nb_lignes = 0
|
||||
erreurs_lignes = []
|
||||
|
||||
while index <= 1000:
|
||||
try:
|
||||
|
|
@ -1164,7 +1260,7 @@ class SageConnector:
|
|||
ligne_cible_p, "IBODocumentLigne3"
|
||||
)
|
||||
|
||||
# Récupérer référence article
|
||||
# Récupérer article
|
||||
article_ref = ""
|
||||
try:
|
||||
article_ref = getattr(
|
||||
|
|
@ -1180,7 +1276,7 @@ class SageConnector:
|
|||
except:
|
||||
pass
|
||||
|
||||
# Associer article si disponible
|
||||
# Associer article
|
||||
if article_ref:
|
||||
try:
|
||||
persist_article = factory_article.ReadReference(
|
||||
|
|
@ -1236,37 +1332,61 @@ class SageConnector:
|
|||
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 copiée. Erreurs: {'; '.join(erreurs_lignes)}"
|
||||
)
|
||||
|
||||
logger.info(f"✅ {nb_lignes} lignes copiées")
|
||||
|
||||
# ===== VALIDATION =====
|
||||
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:
|
||||
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
|
||||
if transaction_active:
|
||||
self.cial.CptaApplication.CommitTrans()
|
||||
logger.info("✅ Transaction committée")
|
||||
|
||||
# ✅ MAJ STATUT SOURCE → TRANSFORMÉ (5)
|
||||
# MAJ statut source → Transformé
|
||||
try:
|
||||
doc_source.DO_Statut = 5 # Transformé
|
||||
doc_source.Write()
|
||||
logger.info(
|
||||
f"✅ Statut source mis à jour: {statut_actuel} → 5 (TRANSFORMÉ)"
|
||||
)
|
||||
logger.info(f"✅ Statut source mis à jour: → 5 (TRANSFORMÉ)")
|
||||
except Exception as e:
|
||||
logger.warning(f"⚠️ Impossible de MAJ statut source: {e}")
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
|
|
@ -1290,6 +1410,39 @@ class SageConnector:
|
|||
logger.error(f"❌ Erreur transformation: {e}", exc_info=True)
|
||||
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)
|
||||
# =========================================================================
|
||||
|
|
|
|||
Loading…
Reference in a new issue