feat: add endpoint to list devis with filters and update devis status endpoint signature and logic.
This commit is contained in:
parent
8bdfdd0e01
commit
7ee3751ee2
1 changed files with 344 additions and 31 deletions
361
main.py
361
main.py
|
|
@ -15,14 +15,12 @@ from sage_connector import SageConnector
|
||||||
# =====================================================
|
# =====================================================
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.INFO,
|
level=logging.INFO,
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||||
handlers=[
|
handlers=[logging.FileHandler("sage_gateway.log"), logging.StreamHandler()],
|
||||||
logging.FileHandler("sage_gateway.log"),
|
|
||||||
logging.StreamHandler()
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
# =====================================================
|
||||||
# ENUMS
|
# ENUMS
|
||||||
# =====================================================
|
# =====================================================
|
||||||
|
|
@ -34,34 +32,43 @@ class TypeDocument(int, Enum):
|
||||||
PREPARATION = 4
|
PREPARATION = 4
|
||||||
FACTURE = 5
|
FACTURE = 5
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
# =====================================================
|
||||||
# MODÈLES
|
# MODÈLES
|
||||||
# =====================================================
|
# =====================================================
|
||||||
class FiltreRequest(BaseModel):
|
class FiltreRequest(BaseModel):
|
||||||
filtre: Optional[str] = ""
|
filtre: Optional[str] = ""
|
||||||
|
|
||||||
|
|
||||||
class CodeRequest(BaseModel):
|
class CodeRequest(BaseModel):
|
||||||
code: str
|
code: str
|
||||||
|
|
||||||
|
|
||||||
class ChampLibreRequest(BaseModel):
|
class ChampLibreRequest(BaseModel):
|
||||||
doc_id: str
|
doc_id: str
|
||||||
type_doc: int
|
type_doc: int
|
||||||
nom_champ: str
|
nom_champ: str
|
||||||
valeur: str
|
valeur: str
|
||||||
|
|
||||||
|
|
||||||
class DevisRequest(BaseModel):
|
class DevisRequest(BaseModel):
|
||||||
client_id: str
|
client_id: str
|
||||||
date_devis: Optional[date] = None
|
date_devis: Optional[date] = None
|
||||||
lignes: List[Dict] # Format: {article_code, quantite, prix_unitaire_ht, remise_pourcentage}
|
lignes: List[
|
||||||
|
Dict
|
||||||
|
] # Format: {article_code, quantite, prix_unitaire_ht, remise_pourcentage}
|
||||||
|
|
||||||
|
|
||||||
class TransformationRequest(BaseModel):
|
class TransformationRequest(BaseModel):
|
||||||
numero_source: str
|
numero_source: str
|
||||||
type_source: int
|
type_source: int
|
||||||
type_cible: int
|
type_cible: int
|
||||||
|
|
||||||
|
|
||||||
class StatutRequest(BaseModel):
|
class StatutRequest(BaseModel):
|
||||||
nouveau_statut: int
|
nouveau_statut: int
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
# =====================================================
|
||||||
# SÉCURITÉ
|
# SÉCURITÉ
|
||||||
# =====================================================
|
# =====================================================
|
||||||
|
|
@ -72,13 +79,14 @@ def verify_token(x_sage_token: str = Header(...)):
|
||||||
raise HTTPException(401, "Token invalide")
|
raise HTTPException(401, "Token invalide")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
# =====================================================
|
||||||
# APPLICATION
|
# APPLICATION
|
||||||
# =====================================================
|
# =====================================================
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="Sage Gateway - Windows Server",
|
title="Sage Gateway - Windows Server",
|
||||||
version="1.0.0",
|
version="1.0.0",
|
||||||
description="Passerelle d'accès à Sage 100c pour VPS Linux"
|
description="Passerelle d'accès à Sage 100c pour VPS Linux",
|
||||||
)
|
)
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
|
|
@ -86,11 +94,12 @@ app.add_middleware(
|
||||||
allow_origins=settings.cors_origins,
|
allow_origins=settings.cors_origins,
|
||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
allow_credentials=True
|
allow_credentials=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
sage: Optional[SageConnector] = None
|
sage: Optional[SageConnector] = None
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
# =====================================================
|
||||||
# LIFECYCLE
|
# LIFECYCLE
|
||||||
# =====================================================
|
# =====================================================
|
||||||
|
|
@ -110,9 +119,7 @@ def startup():
|
||||||
|
|
||||||
# Connexion Sage
|
# Connexion Sage
|
||||||
sage = SageConnector(
|
sage = SageConnector(
|
||||||
settings.chemin_base,
|
settings.chemin_base, settings.utilisateur, settings.mot_de_passe
|
||||||
settings.utilisateur,
|
|
||||||
settings.mot_de_passe
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not sage.connecter():
|
if not sage.connecter():
|
||||||
|
|
@ -120,12 +127,14 @@ def startup():
|
||||||
|
|
||||||
logger.info("✅ Sage Gateway démarré et connecté")
|
logger.info("✅ Sage Gateway démarré et connecté")
|
||||||
|
|
||||||
|
|
||||||
@app.on_event("shutdown")
|
@app.on_event("shutdown")
|
||||||
def shutdown():
|
def shutdown():
|
||||||
if sage:
|
if sage:
|
||||||
sage.deconnecter()
|
sage.deconnecter()
|
||||||
logger.info("👋 Sage Gateway arrêté")
|
logger.info("👋 Sage Gateway arrêté")
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
# =====================================================
|
||||||
# ENDPOINTS - SYSTÈME
|
# ENDPOINTS - SYSTÈME
|
||||||
# =====================================================
|
# =====================================================
|
||||||
|
|
@ -136,9 +145,10 @@ def health():
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"sage_connected": sage is not None and sage.cial is not None,
|
"sage_connected": sage is not None and sage.cial is not None,
|
||||||
"cache_info": sage.get_cache_info() if sage else None,
|
"cache_info": sage.get_cache_info() if sage else None,
|
||||||
"timestamp": datetime.now().isoformat()
|
"timestamp": datetime.now().isoformat(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
# =====================================================
|
||||||
# ENDPOINTS - CLIENTS
|
# ENDPOINTS - CLIENTS
|
||||||
# =====================================================
|
# =====================================================
|
||||||
|
|
@ -152,6 +162,7 @@ def clients_list(req: FiltreRequest):
|
||||||
logger.error(f"Erreur liste clients: {e}")
|
logger.error(f"Erreur liste clients: {e}")
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
@app.post("/sage/clients/get", dependencies=[Depends(verify_token)])
|
@app.post("/sage/clients/get", dependencies=[Depends(verify_token)])
|
||||||
def client_get(req: CodeRequest):
|
def client_get(req: CodeRequest):
|
||||||
"""Lecture d'un client par code"""
|
"""Lecture d'un client par code"""
|
||||||
|
|
@ -166,6 +177,7 @@ def client_get(req: CodeRequest):
|
||||||
logger.error(f"Erreur lecture client: {e}")
|
logger.error(f"Erreur lecture client: {e}")
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
# =====================================================
|
||||||
# ENDPOINTS - ARTICLES
|
# ENDPOINTS - ARTICLES
|
||||||
# =====================================================
|
# =====================================================
|
||||||
|
|
@ -179,6 +191,7 @@ def articles_list(req: FiltreRequest):
|
||||||
logger.error(f"Erreur liste articles: {e}")
|
logger.error(f"Erreur liste articles: {e}")
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
@app.post("/sage/articles/get", dependencies=[Depends(verify_token)])
|
@app.post("/sage/articles/get", dependencies=[Depends(verify_token)])
|
||||||
def article_get(req: CodeRequest):
|
def article_get(req: CodeRequest):
|
||||||
"""Lecture d'un article par référence"""
|
"""Lecture d'un article par référence"""
|
||||||
|
|
@ -193,6 +206,7 @@ def article_get(req: CodeRequest):
|
||||||
logger.error(f"Erreur lecture article: {e}")
|
logger.error(f"Erreur lecture article: {e}")
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
# =====================================================
|
||||||
# ENDPOINTS - DEVIS
|
# ENDPOINTS - DEVIS
|
||||||
# =====================================================
|
# =====================================================
|
||||||
|
|
@ -204,7 +218,7 @@ def creer_devis(req: DevisRequest):
|
||||||
devis_data = {
|
devis_data = {
|
||||||
"client": {"code": req.client_id, "intitule": ""},
|
"client": {"code": req.client_id, "intitule": ""},
|
||||||
"date_devis": req.date_devis or date.today(),
|
"date_devis": req.date_devis or date.today(),
|
||||||
"lignes": req.lignes
|
"lignes": req.lignes,
|
||||||
}
|
}
|
||||||
|
|
||||||
resultat = sage.creer_devis_enrichi(devis_data)
|
resultat = sage.creer_devis_enrichi(devis_data)
|
||||||
|
|
@ -213,6 +227,7 @@ def creer_devis(req: DevisRequest):
|
||||||
logger.error(f"Erreur création devis: {e}")
|
logger.error(f"Erreur création devis: {e}")
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
@app.post("/sage/devis/get", dependencies=[Depends(verify_token)])
|
@app.post("/sage/devis/get", dependencies=[Depends(verify_token)])
|
||||||
def lire_devis(req: CodeRequest):
|
def lire_devis(req: CodeRequest):
|
||||||
"""Lecture d'un devis"""
|
"""Lecture d'un devis"""
|
||||||
|
|
@ -227,6 +242,7 @@ def lire_devis(req: CodeRequest):
|
||||||
logger.error(f"Erreur lecture devis: {e}")
|
logger.error(f"Erreur lecture devis: {e}")
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
@app.post("/sage/devis/statut", dependencies=[Depends(verify_token)])
|
@app.post("/sage/devis/statut", dependencies=[Depends(verify_token)])
|
||||||
def changer_statut_devis(doc_id: str, req: StatutRequest):
|
def changer_statut_devis(doc_id: str, req: StatutRequest):
|
||||||
"""Changement de statut d'un devis"""
|
"""Changement de statut d'un devis"""
|
||||||
|
|
@ -238,6 +254,128 @@ def changer_statut_devis(doc_id: str, req: StatutRequest):
|
||||||
logger.error(f"Erreur MAJ statut: {e}")
|
logger.error(f"Erreur MAJ statut: {e}")
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/sage/devis/list", dependencies=[Depends(verify_token)])
|
||||||
|
def devis_list(limit: int = 100, statut: Optional[int] = None):
|
||||||
|
"""
|
||||||
|
Liste tous les devis avec filtres optionnels
|
||||||
|
Utilise le cache pour performances optimales
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with sage._com_context(), sage._lock_com:
|
||||||
|
factory = sage.cial.FactoryDocumentVente
|
||||||
|
devis_list = []
|
||||||
|
index = 1
|
||||||
|
max_iterations = limit * 3
|
||||||
|
erreurs_consecutives = 0
|
||||||
|
max_erreurs = 50
|
||||||
|
|
||||||
|
while (
|
||||||
|
len(devis_list) < 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 uniquement devis (type 0)
|
||||||
|
if getattr(doc, "DO_Type", -1) != 0:
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
doc_statut = getattr(doc, "DO_Statut", 0)
|
||||||
|
|
||||||
|
# Filtre statut
|
||||||
|
if statut is not None and doc_statut != statut:
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 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()
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Erreur chargement client: {e}")
|
||||||
|
|
||||||
|
devis_list.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,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
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:
|
||||||
|
break
|
||||||
|
|
||||||
|
logger.info(f"✅ {len(devis_list)} devis retournés")
|
||||||
|
return {"success": True, "data": devis_list}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur liste devis: {e}")
|
||||||
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/sage/devis/statut", dependencies=[Depends(verify_token)])
|
||||||
|
def changer_statut_devis_endpoint(numero: str, nouveau_statut: int):
|
||||||
|
"""Change le statut d'un devis"""
|
||||||
|
try:
|
||||||
|
with sage._com_context(), sage._lock_com:
|
||||||
|
factory = sage.cial.FactoryDocumentVente
|
||||||
|
persist = factory.ReadPiece(0, numero)
|
||||||
|
|
||||||
|
if not persist:
|
||||||
|
raise HTTPException(404, f"Devis {numero} introuvable")
|
||||||
|
|
||||||
|
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
|
||||||
|
doc.Read()
|
||||||
|
|
||||||
|
statut_actuel = getattr(doc, "DO_Statut", 0)
|
||||||
|
doc.DO_Statut = nouveau_statut
|
||||||
|
doc.Write()
|
||||||
|
|
||||||
|
logger.info(f"✅ Statut devis {numero}: {statut_actuel} → {nouveau_statut}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"data": {
|
||||||
|
"numero": numero,
|
||||||
|
"statut_ancien": statut_actuel,
|
||||||
|
"statut_nouveau": nouveau_statut,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur changement statut: {e}")
|
||||||
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
# =====================================================
|
||||||
# ENDPOINTS - DOCUMENTS
|
# ENDPOINTS - DOCUMENTS
|
||||||
# =====================================================
|
# =====================================================
|
||||||
|
|
@ -255,35 +393,33 @@ def lire_document(numero: str, type_doc: int):
|
||||||
logger.error(f"Erreur lecture document: {e}")
|
logger.error(f"Erreur lecture document: {e}")
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
@app.post("/sage/documents/transform", dependencies=[Depends(verify_token)])
|
@app.post("/sage/documents/transform", dependencies=[Depends(verify_token)])
|
||||||
def transformer_document(req: TransformationRequest):
|
def transformer_document(req: TransformationRequest):
|
||||||
"""Transformation de document (devis → commande, etc.)"""
|
"""Transformation de document (devis → commande, etc.)"""
|
||||||
try:
|
try:
|
||||||
resultat = sage.transformer_document(
|
resultat = sage.transformer_document(
|
||||||
req.numero_source,
|
req.numero_source, req.type_source, req.type_cible
|
||||||
req.type_source,
|
|
||||||
req.type_cible
|
|
||||||
)
|
)
|
||||||
return {"success": True, "data": resultat}
|
return {"success": True, "data": resultat}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur transformation: {e}")
|
logger.error(f"Erreur transformation: {e}")
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
@app.post("/sage/documents/champ-libre", dependencies=[Depends(verify_token)])
|
@app.post("/sage/documents/champ-libre", dependencies=[Depends(verify_token)])
|
||||||
def maj_champ_libre(req: ChampLibreRequest):
|
def maj_champ_libre(req: ChampLibreRequest):
|
||||||
"""Mise à jour d'un champ libre"""
|
"""Mise à jour d'un champ libre"""
|
||||||
try:
|
try:
|
||||||
success = sage.mettre_a_jour_champ_libre(
|
success = sage.mettre_a_jour_champ_libre(
|
||||||
req.doc_id,
|
req.doc_id, req.type_doc, req.nom_champ, req.valeur
|
||||||
req.type_doc,
|
|
||||||
req.nom_champ,
|
|
||||||
req.valeur
|
|
||||||
)
|
)
|
||||||
return {"success": success}
|
return {"success": success}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur MAJ champ libre: {e}")
|
logger.error(f"Erreur MAJ champ libre: {e}")
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
# =====================================================
|
||||||
# ENDPOINTS - CONTACTS
|
# ENDPOINTS - CONTACTS
|
||||||
# =====================================================
|
# =====================================================
|
||||||
|
|
@ -301,6 +437,181 @@ def contact_read(req: CodeRequest):
|
||||||
logger.error(f"Erreur lecture contact: {e}")
|
logger.error(f"Erreur lecture contact: {e}")
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/sage/commandes/list", dependencies=[Depends(verify_token)])
|
||||||
|
def commandes_list(limit: int = 100, statut: Optional[int] = None):
|
||||||
|
"""Liste toutes les commandes"""
|
||||||
|
try:
|
||||||
|
with sage._com_context(), sage._lock_com:
|
||||||
|
factory = sage.cial.FactoryDocumentVente
|
||||||
|
commandes = []
|
||||||
|
index = 1
|
||||||
|
max_iterations = limit * 3
|
||||||
|
|
||||||
|
while len(commandes) < limit and index < max_iterations:
|
||||||
|
try:
|
||||||
|
persist = factory.List(index)
|
||||||
|
if persist is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
|
||||||
|
doc.Read()
|
||||||
|
|
||||||
|
# Filtrer commandes (type 3)
|
||||||
|
if getattr(doc, "DO_Type", -1) != 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 = ""
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
except:
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
logger.info(f"✅ {len(commandes)} commandes retournées")
|
||||||
|
return {"success": True, "data": commandes}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur liste commandes: {e}")
|
||||||
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/sage/factures/list", dependencies=[Depends(verify_token)])
|
||||||
|
def factures_list(limit: int = 100, statut: Optional[int] = None):
|
||||||
|
"""Liste toutes les factures"""
|
||||||
|
try:
|
||||||
|
with sage._com_context(), sage._lock_com:
|
||||||
|
factory = sage.cial.FactoryDocumentVente
|
||||||
|
factures = []
|
||||||
|
index = 1
|
||||||
|
max_iterations = limit * 3
|
||||||
|
|
||||||
|
while len(factures) < limit and index < max_iterations:
|
||||||
|
try:
|
||||||
|
persist = factory.List(index)
|
||||||
|
if persist is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
doc = win32com.client.CastTo(persist, "IBODocumentVente3")
|
||||||
|
doc.Read()
|
||||||
|
|
||||||
|
# Filtrer factures (type 5)
|
||||||
|
if getattr(doc, "DO_Type", -1) != 5:
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
doc_statut = getattr(doc, "DO_Statut", 0)
|
||||||
|
|
||||||
|
if statut is None or doc_statut == statut:
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Champ libre dernière relance
|
||||||
|
derniere_relance = None
|
||||||
|
try:
|
||||||
|
derniere_relance = getattr(doc, "DO_DerniereRelance", None)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
factures.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,
|
||||||
|
"derniere_relance": (
|
||||||
|
str(derniere_relance) if derniere_relance else None
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
except:
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
logger.info(f"✅ {len(factures)} factures retournées")
|
||||||
|
return {"success": True, "data": factures}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur liste factures: {e}")
|
||||||
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/sage/client/remise-max", dependencies=[Depends(verify_token)])
|
||||||
|
def lire_remise_max_client(code: str):
|
||||||
|
"""Récupère la remise max autorisée pour un client"""
|
||||||
|
try:
|
||||||
|
client_obj = sage._lire_client_obj(code)
|
||||||
|
|
||||||
|
if not client_obj:
|
||||||
|
raise HTTPException(404, f"Client {code} introuvable")
|
||||||
|
|
||||||
|
remise_max = 10.0 # Défaut
|
||||||
|
|
||||||
|
try:
|
||||||
|
remise_max = float(getattr(client_obj, "CT_RemiseMax", 10.0))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
logger.info(f"✅ Remise max client {code}: {remise_max}%")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"data": {"client_code": code, "remise_max": remise_max},
|
||||||
|
}
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur lecture remise: {e}")
|
||||||
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
# =====================================================
|
||||||
# ENDPOINTS - ADMIN
|
# ENDPOINTS - ADMIN
|
||||||
# =====================================================
|
# =====================================================
|
||||||
|
|
@ -312,21 +623,23 @@ def refresh_cache():
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"message": "Cache actualisé",
|
"message": "Cache actualisé",
|
||||||
"info": sage.get_cache_info()
|
"info": sage.get_cache_info(),
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur refresh cache: {e}")
|
logger.error(f"Erreur refresh cache: {e}")
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
@app.get("/sage/cache/info", dependencies=[Depends(verify_token)])
|
@app.get("/sage/cache/info", dependencies=[Depends(verify_token)])
|
||||||
def cache_info():
|
def cache_info_get():
|
||||||
"""Informations sur le cache"""
|
"""Informations sur le cache (endpoint GET)"""
|
||||||
try:
|
try:
|
||||||
return {"success": True, "data": sage.get_cache_info()}
|
return {"success": True, "data": sage.get_cache_info()}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur info cache: {e}")
|
logger.error(f"Erreur info cache: {e}")
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
# =====================================================
|
||||||
# LANCEMENT
|
# LANCEMENT
|
||||||
# =====================================================
|
# =====================================================
|
||||||
|
|
@ -336,5 +649,5 @@ if __name__ == "__main__":
|
||||||
host=settings.api_host,
|
host=settings.api_host,
|
||||||
port=settings.api_port,
|
port=settings.api_port,
|
||||||
reload=False, # Pas de reload en production
|
reload=False, # Pas de reload en production
|
||||||
log_level="info"
|
log_level="info",
|
||||||
)
|
)
|
||||||
Loading…
Reference in a new issue