feat: Add API endpoints and Sage connector methods for managing prospects, suppliers, credit notes, and delivery notes.
This commit is contained in:
parent
53ecccd712
commit
ae5fa9e0be
2 changed files with 657 additions and 0 deletions
117
main.py
117
main.py
|
|
@ -37,6 +37,7 @@ class TypeDocument(int, Enum):
|
||||||
# MODÈLES
|
# MODÈLES
|
||||||
# =====================================================
|
# =====================================================
|
||||||
|
|
||||||
|
|
||||||
class DocumentGetRequest(BaseModel):
|
class DocumentGetRequest(BaseModel):
|
||||||
numero: str
|
numero: str
|
||||||
type_doc: int
|
type_doc: int
|
||||||
|
|
@ -2450,6 +2451,122 @@ def diagnostiquer_erreur_transformation(
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
# =====================================================
|
||||||
|
# ENDPOINTS - PROSPECTS
|
||||||
|
# =====================================================
|
||||||
|
@app.post("/sage/prospects/list", dependencies=[Depends(verify_token)])
|
||||||
|
def prospects_list(req: FiltreRequest):
|
||||||
|
"""📋 Liste tous les prospects (CT_Type=0 AND CT_Prospect=1)"""
|
||||||
|
try:
|
||||||
|
prospects = sage.lister_tous_prospects(req.filtre)
|
||||||
|
return {"success": True, "data": prospects}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur liste prospects: {e}")
|
||||||
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/sage/prospects/get", dependencies=[Depends(verify_token)])
|
||||||
|
def prospect_get(req: CodeRequest):
|
||||||
|
"""📄 Lecture d'un prospect par code"""
|
||||||
|
try:
|
||||||
|
prospect = sage.lire_prospect(req.code)
|
||||||
|
if not prospect:
|
||||||
|
raise HTTPException(404, f"Prospect {req.code} non trouvé")
|
||||||
|
return {"success": True, "data": prospect}
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur lecture prospect: {e}")
|
||||||
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
# =====================================================
|
||||||
|
# ENDPOINTS - FOURNISSEURS
|
||||||
|
# =====================================================
|
||||||
|
@app.post("/sage/fournisseurs/list", dependencies=[Depends(verify_token)])
|
||||||
|
def fournisseurs_list(req: FiltreRequest):
|
||||||
|
"""📋 Liste tous les fournisseurs (CT_Type=1)"""
|
||||||
|
try:
|
||||||
|
fournisseurs = sage.lister_tous_fournisseurs(req.filtre)
|
||||||
|
return {"success": True, "data": fournisseurs}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur liste fournisseurs: {e}")
|
||||||
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/sage/fournisseurs/get", dependencies=[Depends(verify_token)])
|
||||||
|
def fournisseur_get(req: CodeRequest):
|
||||||
|
"""📄 Lecture d'un fournisseur par code"""
|
||||||
|
try:
|
||||||
|
fournisseur = sage.lire_fournisseur(req.code)
|
||||||
|
if not fournisseur:
|
||||||
|
raise HTTPException(404, f"Fournisseur {req.code} non trouvé")
|
||||||
|
return {"success": True, "data": fournisseur}
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur lecture fournisseur: {e}")
|
||||||
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
# =====================================================
|
||||||
|
# ENDPOINTS - AVOIRS
|
||||||
|
# =====================================================
|
||||||
|
@app.post("/sage/avoirs/list", dependencies=[Depends(verify_token)])
|
||||||
|
def avoirs_list(limit: int = 100, statut: Optional[int] = None):
|
||||||
|
"""📋 Liste tous les avoirs (DO_Domaine=0 AND DO_Type=5)"""
|
||||||
|
try:
|
||||||
|
avoirs = sage.lister_avoirs(limit=limit, statut=statut)
|
||||||
|
return {"success": True, "data": avoirs}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur liste avoirs: {e}")
|
||||||
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/sage/avoirs/get", dependencies=[Depends(verify_token)])
|
||||||
|
def avoir_get(req: CodeRequest):
|
||||||
|
"""📄 Lecture d'un avoir avec ses lignes"""
|
||||||
|
try:
|
||||||
|
avoir = sage.lire_avoir(req.code)
|
||||||
|
if not avoir:
|
||||||
|
raise HTTPException(404, f"Avoir {req.code} non trouvé")
|
||||||
|
return {"success": True, "data": avoir}
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur lecture avoir: {e}")
|
||||||
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
# =====================================================
|
||||||
|
# ENDPOINTS - LIVRAISONS
|
||||||
|
# =====================================================
|
||||||
|
@app.post("/sage/livraisons/list", dependencies=[Depends(verify_token)])
|
||||||
|
def livraisons_list(limit: int = 100, statut: Optional[int] = None):
|
||||||
|
"""📋 Liste tous les bons de livraison (DO_Domaine=0 AND DO_Type=30)"""
|
||||||
|
try:
|
||||||
|
livraisons = sage.lister_livraisons(limit=limit, statut=statut)
|
||||||
|
return {"success": True, "data": livraisons}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur liste livraisons: {e}")
|
||||||
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/sage/livraisons/get", dependencies=[Depends(verify_token)])
|
||||||
|
def livraison_get(req: CodeRequest):
|
||||||
|
"""📄 Lecture d'une livraison avec ses lignes"""
|
||||||
|
try:
|
||||||
|
livraison = sage.lire_livraison(req.code)
|
||||||
|
if not livraison:
|
||||||
|
raise HTTPException(404, f"Livraison {req.code} non trouvée")
|
||||||
|
return {"success": True, "data": livraison}
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur lecture livraison: {e}")
|
||||||
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
# =====================================================
|
||||||
# LANCEMENT
|
# LANCEMENT
|
||||||
# =====================================================
|
# =====================================================
|
||||||
|
|
|
||||||
|
|
@ -1805,3 +1805,543 @@ class SageConnector:
|
||||||
return self.mettre_a_jour_champ_libre(
|
return self.mettre_a_jour_champ_libre(
|
||||||
doc_id, type_doc, "DerniereRelance", date_relance
|
doc_id, type_doc, "DerniereRelance", date_relance
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# PROSPECTS (CT_Type = 0 AND CT_Prospect = 1)
|
||||||
|
# =========================================================================
|
||||||
|
def lister_tous_prospects(self, filtre=""):
|
||||||
|
"""Liste tous les prospects depuis le cache"""
|
||||||
|
with self._lock_clients:
|
||||||
|
if not filtre:
|
||||||
|
return [
|
||||||
|
c
|
||||||
|
for c in self._cache_clients
|
||||||
|
if c.get("type") == 0 and c.get("est_prospect")
|
||||||
|
]
|
||||||
|
|
||||||
|
filtre_lower = filtre.lower()
|
||||||
|
return [
|
||||||
|
c
|
||||||
|
for c in self._cache_clients
|
||||||
|
if c.get("type") == 0
|
||||||
|
and c.get("est_prospect")
|
||||||
|
and (
|
||||||
|
filtre_lower in c["numero"].lower()
|
||||||
|
or filtre_lower in c["intitule"].lower()
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
def lire_prospect(self, code_prospect):
|
||||||
|
"""Retourne un prospect depuis le cache"""
|
||||||
|
with self._lock_clients:
|
||||||
|
prospect = self._cache_clients_dict.get(code_prospect)
|
||||||
|
if prospect and prospect.get("type") == 0 and prospect.get("est_prospect"):
|
||||||
|
return prospect
|
||||||
|
return None
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# FOURNISSEURS (CT_Type = 1)
|
||||||
|
# =========================================================================
|
||||||
|
def lister_tous_fournisseurs(self, filtre=""):
|
||||||
|
"""Liste tous les fournisseurs depuis le cache"""
|
||||||
|
with self._lock_clients:
|
||||||
|
if not filtre:
|
||||||
|
return [c for c in self._cache_clients if c.get("type") == 1]
|
||||||
|
|
||||||
|
filtre_lower = filtre.lower()
|
||||||
|
return [
|
||||||
|
c
|
||||||
|
for c in self._cache_clients
|
||||||
|
if c.get("type") == 1
|
||||||
|
and (
|
||||||
|
filtre_lower in c["numero"].lower()
|
||||||
|
or filtre_lower in c["intitule"].lower()
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
def lire_fournisseur(self, code_fournisseur):
|
||||||
|
"""Retourne un fournisseur depuis le cache"""
|
||||||
|
with self._lock_clients:
|
||||||
|
fournisseur = self._cache_clients_dict.get(code_fournisseur)
|
||||||
|
if fournisseur and fournisseur.get("type") == 1:
|
||||||
|
return fournisseur
|
||||||
|
return None
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# EXTRACTION CLIENTS (Mise à jour pour inclure prospects)
|
||||||
|
# =========================================================================
|
||||||
|
def _extraire_client(self, client_obj):
|
||||||
|
"""MISE À JOUR : Extraction avec détection prospect"""
|
||||||
|
data = {
|
||||||
|
"numero": getattr(client_obj, "CT_Num", ""),
|
||||||
|
"intitule": getattr(client_obj, "CT_Intitule", ""),
|
||||||
|
"type": getattr(
|
||||||
|
client_obj, "CT_Type", 0
|
||||||
|
), # 0=Client/Prospect, 1=Fournisseur
|
||||||
|
"est_prospect": getattr(client_obj, "CT_Prospect", 0) == 1, # ✅ NOUVEAU
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
adresse = getattr(client_obj, "Adresse", None)
|
||||||
|
if adresse:
|
||||||
|
data["adresse"] = getattr(adresse, "Adresse", "")
|
||||||
|
data["code_postal"] = getattr(adresse, "CodePostal", "")
|
||||||
|
data["ville"] = getattr(adresse, "Ville", "")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
telecom = getattr(client_obj, "Telecom", None)
|
||||||
|
if telecom:
|
||||||
|
data["telephone"] = getattr(telecom, "Telephone", "")
|
||||||
|
data["email"] = getattr(telecom, "EMail", "")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# AVOIRS (DO_Domaine = 0 AND DO_Type = 5)
|
||||||
|
# =========================================================================
|
||||||
|
def lister_avoirs(self, limit=100, statut=None):
|
||||||
|
"""Liste tous les avoirs de vente"""
|
||||||
|
if not self.cial:
|
||||||
|
return []
|
||||||
|
|
||||||
|
avoirs = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
with self._com_context(), self._lock_com:
|
||||||
|
factory = self.cial.FactoryDocumentVente
|
||||||
|
index = 1
|
||||||
|
max_iterations = limit * 3
|
||||||
|
erreurs_consecutives = 0
|
||||||
|
max_erreurs = 50
|
||||||
|
|
||||||
|
while (
|
||||||
|
len(avoirs) < limit
|
||||||
|
and index < max_iterations
|
||||||
|
and erreurs_consecutives < max_erreurs
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
persist = factory.List(index)
|
||||||
|
if persist is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
|
||||||
|
doc.Read()
|
||||||
|
|
||||||
|
# ✅ Filtrer : DO_Domaine = 0 (Vente) AND DO_Type = 5 (Avoir)
|
||||||
|
doc_type = getattr(doc, "DO_Type", -1)
|
||||||
|
doc_domaine = getattr(doc, "DO_Domaine", -1)
|
||||||
|
|
||||||
|
if doc_domaine != 0 or doc_type != settings.SAGE_TYPE_BON_AVOIR:
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
doc_statut = getattr(doc, "DO_Statut", 0)
|
||||||
|
|
||||||
|
# Filtre statut optionnel
|
||||||
|
if statut is not None and doc_statut != statut:
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Charger 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()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
avoirs.append(
|
||||||
|
{
|
||||||
|
"numero": getattr(doc, "DO_Piece", ""),
|
||||||
|
"reference": getattr(doc, "DO_Ref", ""),
|
||||||
|
"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,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
erreurs_consecutives = 0
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
except:
|
||||||
|
erreurs_consecutives += 1
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
if erreurs_consecutives >= max_erreurs:
|
||||||
|
break
|
||||||
|
|
||||||
|
logger.info(f"✅ {len(avoirs)} avoirs retournés")
|
||||||
|
return avoirs
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Erreur liste avoirs: {e}", exc_info=True)
|
||||||
|
return []
|
||||||
|
|
||||||
|
def lire_avoir(self, numero):
|
||||||
|
"""Lecture d'un avoir avec ses lignes"""
|
||||||
|
if not self.cial:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
with self._com_context(), self._lock_com:
|
||||||
|
factory = self.cial.FactoryDocumentVente
|
||||||
|
|
||||||
|
# Essayer ReadPiece
|
||||||
|
persist = factory.ReadPiece(settings.SAGE_TYPE_BON_AVOIR, numero)
|
||||||
|
|
||||||
|
if not persist:
|
||||||
|
# Chercher dans List()
|
||||||
|
index = 1
|
||||||
|
while index < 10000:
|
||||||
|
try:
|
||||||
|
persist_test = factory.List(index)
|
||||||
|
if persist_test is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
doc_test = win32com.client.CastTo(
|
||||||
|
persist_test, "IBODocumentVente3"
|
||||||
|
)
|
||||||
|
doc_test.Read()
|
||||||
|
|
||||||
|
if (
|
||||||
|
getattr(doc_test, "DO_Type", -1)
|
||||||
|
== settings.SAGE_TYPE_BON_AVOIR
|
||||||
|
and getattr(doc_test, "DO_Piece", "") == numero
|
||||||
|
):
|
||||||
|
persist = persist_test
|
||||||
|
break
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
except:
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
if not persist:
|
||||||
|
return None
|
||||||
|
|
||||||
|
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
|
||||||
|
doc.Read()
|
||||||
|
|
||||||
|
# Charger 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()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
avoir = {
|
||||||
|
"numero": getattr(doc, "DO_Piece", ""),
|
||||||
|
"reference": getattr(doc, "DO_Ref", ""),
|
||||||
|
"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": getattr(doc, "DO_Statut", 0),
|
||||||
|
"lignes": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Charger lignes
|
||||||
|
try:
|
||||||
|
factory_lignes = getattr(
|
||||||
|
doc, "FactoryDocumentLigne", None
|
||||||
|
) or getattr(doc, "FactoryDocumentVenteLigne", None)
|
||||||
|
|
||||||
|
if factory_lignes:
|
||||||
|
index = 1
|
||||||
|
while index <= 100:
|
||||||
|
try:
|
||||||
|
ligne_persist = factory_lignes.List(index)
|
||||||
|
if ligne_persist is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
ligne = win32com.client.CastTo(
|
||||||
|
ligne_persist, "IBODocumentLigne3"
|
||||||
|
)
|
||||||
|
ligne.Read()
|
||||||
|
|
||||||
|
article_ref = ""
|
||||||
|
try:
|
||||||
|
article_ref = getattr(ligne, "AR_Ref", "").strip()
|
||||||
|
if not article_ref:
|
||||||
|
article_obj = getattr(ligne, "Article", None)
|
||||||
|
if article_obj:
|
||||||
|
article_obj.Read()
|
||||||
|
article_ref = getattr(
|
||||||
|
article_obj, "AR_Ref", ""
|
||||||
|
).strip()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
avoir["lignes"].append(
|
||||||
|
{
|
||||||
|
"article": article_ref,
|
||||||
|
"designation": getattr(ligne, "DL_Design", ""),
|
||||||
|
"quantite": float(
|
||||||
|
getattr(ligne, "DL_Qte", 0.0)
|
||||||
|
),
|
||||||
|
"prix_unitaire": float(
|
||||||
|
getattr(ligne, "DL_PrixUnitaire", 0.0)
|
||||||
|
),
|
||||||
|
"montant_ht": float(
|
||||||
|
getattr(ligne, "DL_MontantHT", 0.0)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
except:
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
logger.info(f"✅ Avoir {numero} lu: {len(avoir['lignes'])} lignes")
|
||||||
|
return avoir
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Erreur lecture avoir {numero}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# LIVRAISONS (DO_Domaine = 0 AND DO_Type = 3)
|
||||||
|
# =========================================================================
|
||||||
|
def lister_livraisons(self, limit=100, statut=None):
|
||||||
|
"""Liste tous les bons de livraison"""
|
||||||
|
if not self.cial:
|
||||||
|
return []
|
||||||
|
|
||||||
|
livraisons = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
with self._com_context(), self._lock_com:
|
||||||
|
factory = self.cial.FactoryDocumentVente
|
||||||
|
index = 1
|
||||||
|
max_iterations = limit * 3
|
||||||
|
erreurs_consecutives = 0
|
||||||
|
max_erreurs = 50
|
||||||
|
|
||||||
|
while (
|
||||||
|
len(livraisons) < limit
|
||||||
|
and index < max_iterations
|
||||||
|
and erreurs_consecutives < max_erreurs
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
persist = factory.List(index)
|
||||||
|
if persist is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
|
||||||
|
doc.Read()
|
||||||
|
|
||||||
|
# ✅ Filtrer : DO_Domaine = 0 (Vente) AND DO_Type = 30 (Livraison)
|
||||||
|
doc_type = getattr(doc, "DO_Type", -1)
|
||||||
|
doc_domaine = getattr(doc, "DO_Domaine", -1)
|
||||||
|
|
||||||
|
if (
|
||||||
|
doc_domaine != 0
|
||||||
|
or doc_type != settings.SAGE_TYPE_BON_LIVRAISON
|
||||||
|
):
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
doc_statut = getattr(doc, "DO_Statut", 0)
|
||||||
|
|
||||||
|
if statut is not None and doc_statut != statut:
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Charger 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()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
livraisons.append(
|
||||||
|
{
|
||||||
|
"numero": getattr(doc, "DO_Piece", ""),
|
||||||
|
"reference": getattr(doc, "DO_Ref", ""),
|
||||||
|
"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,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
erreurs_consecutives = 0
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
except:
|
||||||
|
erreurs_consecutives += 1
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
if erreurs_consecutives >= max_erreurs:
|
||||||
|
break
|
||||||
|
|
||||||
|
logger.info(f"✅ {len(livraisons)} livraisons retournées")
|
||||||
|
return livraisons
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Erreur liste livraisons: {e}", exc_info=True)
|
||||||
|
return []
|
||||||
|
|
||||||
|
def lire_livraison(self, numero):
|
||||||
|
"""Lecture d'une livraison avec ses lignes"""
|
||||||
|
if not self.cial:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
with self._com_context(), self._lock_com:
|
||||||
|
factory = self.cial.FactoryDocumentVente
|
||||||
|
|
||||||
|
# Essayer ReadPiece
|
||||||
|
persist = factory.ReadPiece(settings.SAGE_TYPE_BON_LIVRAISON, numero)
|
||||||
|
|
||||||
|
if not persist:
|
||||||
|
# Chercher dans List()
|
||||||
|
index = 1
|
||||||
|
while index < 10000:
|
||||||
|
try:
|
||||||
|
persist_test = factory.List(index)
|
||||||
|
if persist_test is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
doc_test = win32com.client.CastTo(
|
||||||
|
persist_test, "IBODocumentVente3"
|
||||||
|
)
|
||||||
|
doc_test.Read()
|
||||||
|
|
||||||
|
if (
|
||||||
|
getattr(doc_test, "DO_Type", -1)
|
||||||
|
== settings.SAGE_TYPE_BON_LIVRAISON
|
||||||
|
and getattr(doc_test, "DO_Piece", "") == numero
|
||||||
|
):
|
||||||
|
persist = persist_test
|
||||||
|
break
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
except:
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
if not persist:
|
||||||
|
return None
|
||||||
|
|
||||||
|
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
|
||||||
|
doc.Read()
|
||||||
|
|
||||||
|
# Charger 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()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
livraison = {
|
||||||
|
"numero": getattr(doc, "DO_Piece", ""),
|
||||||
|
"reference": getattr(doc, "DO_Ref", ""),
|
||||||
|
"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": getattr(doc, "DO_Statut", 0),
|
||||||
|
"lignes": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Charger lignes
|
||||||
|
try:
|
||||||
|
factory_lignes = getattr(
|
||||||
|
doc, "FactoryDocumentLigne", None
|
||||||
|
) or getattr(doc, "FactoryDocumentVenteLigne", None)
|
||||||
|
|
||||||
|
if factory_lignes:
|
||||||
|
index = 1
|
||||||
|
while index <= 100:
|
||||||
|
try:
|
||||||
|
ligne_persist = factory_lignes.List(index)
|
||||||
|
if ligne_persist is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
ligne = win32com.client.CastTo(
|
||||||
|
ligne_persist, "IBODocumentLigne3"
|
||||||
|
)
|
||||||
|
ligne.Read()
|
||||||
|
|
||||||
|
article_ref = ""
|
||||||
|
try:
|
||||||
|
article_ref = getattr(ligne, "AR_Ref", "").strip()
|
||||||
|
if not article_ref:
|
||||||
|
article_obj = getattr(ligne, "Article", None)
|
||||||
|
if article_obj:
|
||||||
|
article_obj.Read()
|
||||||
|
article_ref = getattr(
|
||||||
|
article_obj, "AR_Ref", ""
|
||||||
|
).strip()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
livraison["lignes"].append(
|
||||||
|
{
|
||||||
|
"article": article_ref,
|
||||||
|
"designation": getattr(ligne, "DL_Design", ""),
|
||||||
|
"quantite": float(
|
||||||
|
getattr(ligne, "DL_Qte", 0.0)
|
||||||
|
),
|
||||||
|
"prix_unitaire": float(
|
||||||
|
getattr(ligne, "DL_PrixUnitaire", 0.0)
|
||||||
|
),
|
||||||
|
"montant_ht": float(
|
||||||
|
getattr(ligne, "DL_MontantHT", 0.0)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
except:
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"✅ Livraison {numero} lue: {len(livraison['lignes'])} lignes"
|
||||||
|
)
|
||||||
|
return livraison
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Erreur lecture livraison {numero}: {e}")
|
||||||
|
return None
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue