enriched familles data and cleaned main file
This commit is contained in:
parent
679ae6a0e4
commit
659dac81c9
2 changed files with 371 additions and 453 deletions
114
main.py
114
main.py
|
|
@ -14,9 +14,6 @@ from sage_connector import SageConnector
|
||||||
import pyodbc
|
import pyodbc
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# =====================================================
|
|
||||||
# LOGGING
|
|
||||||
# =====================================================
|
|
||||||
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",
|
||||||
|
|
@ -25,9 +22,6 @@ logging.basicConfig(
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
|
||||||
# ENUMS
|
|
||||||
# =====================================================
|
|
||||||
class TypeDocument(int, Enum):
|
class TypeDocument(int, Enum):
|
||||||
DEVIS = 0
|
DEVIS = 0
|
||||||
BON_LIVRAISON = 1
|
BON_LIVRAISON = 1
|
||||||
|
|
@ -37,9 +31,6 @@ class TypeDocument(int, Enum):
|
||||||
FACTURE = 5
|
FACTURE = 5
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
|
||||||
# MODÈLES
|
|
||||||
# =====================================================
|
|
||||||
|
|
||||||
|
|
||||||
class DocumentGetRequest(BaseModel):
|
class DocumentGetRequest(BaseModel):
|
||||||
|
|
@ -866,9 +857,6 @@ class FamilleCreate(BaseModel):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
|
||||||
# SÉCURITÉ
|
|
||||||
# =====================================================
|
|
||||||
def verify_token(x_sage_token: str = Header(...)):
|
def verify_token(x_sage_token: str = Header(...)):
|
||||||
"""Vérification du token d'authentification"""
|
"""Vérification du token d'authentification"""
|
||||||
if x_sage_token != settings.sage_gateway_token:
|
if x_sage_token != settings.sage_gateway_token:
|
||||||
|
|
@ -877,9 +865,6 @@ def verify_token(x_sage_token: str = Header(...)):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
|
||||||
# APPLICATION
|
|
||||||
# =====================================================
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="Sage Gateway - Windows Server",
|
title="Sage Gateway - Windows Server",
|
||||||
version="1.0.0",
|
version="1.0.0",
|
||||||
|
|
@ -897,16 +882,12 @@ app.add_middleware(
|
||||||
sage: Optional[SageConnector] = None
|
sage: Optional[SageConnector] = None
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
|
||||||
# LIFECYCLE
|
|
||||||
# =====================================================
|
|
||||||
@app.on_event("startup")
|
@app.on_event("startup")
|
||||||
def startup():
|
def startup():
|
||||||
global sage
|
global sage
|
||||||
|
|
||||||
logger.info("🚀 Démarrage Sage Gateway Windows...")
|
logger.info("🚀 Démarrage Sage Gateway Windows...")
|
||||||
|
|
||||||
# Validation config
|
|
||||||
try:
|
try:
|
||||||
validate_settings()
|
validate_settings()
|
||||||
logger.info(" Configuration validée")
|
logger.info(" Configuration validée")
|
||||||
|
|
@ -914,7 +895,6 @@ def startup():
|
||||||
logger.error(f" Configuration invalide: {e}")
|
logger.error(f" Configuration invalide: {e}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# Connexion Sage
|
|
||||||
sage = SageConnector(
|
sage = SageConnector(
|
||||||
settings.chemin_base, settings.utilisateur, settings.mot_de_passe
|
settings.chemin_base, settings.utilisateur, settings.mot_de_passe
|
||||||
)
|
)
|
||||||
|
|
@ -932,9 +912,6 @@ def shutdown():
|
||||||
logger.info("👋 Sage Gateway arrêté")
|
logger.info("👋 Sage Gateway arrêté")
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
|
||||||
# ENDPOINTS - SYSTÈME
|
|
||||||
# =====================================================
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
def health():
|
def health():
|
||||||
"""Health check"""
|
"""Health check"""
|
||||||
|
|
@ -946,9 +923,6 @@ def health():
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
|
||||||
# ENDPOINTS - CLIENTS
|
|
||||||
# =====================================================
|
|
||||||
@app.post("/sage/clients/list", dependencies=[Depends(verify_token)])
|
@app.post("/sage/clients/list", dependencies=[Depends(verify_token)])
|
||||||
def clients_list(req: FiltreRequest):
|
def clients_list(req: FiltreRequest):
|
||||||
"""Liste des clients avec filtre optionnel"""
|
"""Liste des clients avec filtre optionnel"""
|
||||||
|
|
@ -989,27 +963,20 @@ def client_get(req: CodeRequest):
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
# DANS main.py
|
|
||||||
@app.post("/sage/clients/create", dependencies=[Depends(verify_token)])
|
@app.post("/sage/clients/create", dependencies=[Depends(verify_token)])
|
||||||
def create_client_endpoint(req: ClientCreateRequest):
|
def create_client_endpoint(req: ClientCreateRequest):
|
||||||
"""Création d'un client dans Sage"""
|
"""Création d'un client dans Sage"""
|
||||||
try:
|
try:
|
||||||
# L'appel au connecteur est fait ici
|
|
||||||
resultat = sage.creer_client(req.dict())
|
resultat = sage.creer_client(req.dict())
|
||||||
return {"success": True, "data": resultat}
|
return {"success": True, "data": resultat}
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
logger.warning(f"Erreur métier création client: {e}")
|
logger.warning(f"Erreur métier création client: {e}")
|
||||||
# Erreur métier (ex: doublon) -> 400 Bad Request
|
|
||||||
raise HTTPException(400, str(e))
|
raise HTTPException(400, str(e))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Erreur technique création client: {e}")
|
logger.error(f"Erreur technique création client: {e}")
|
||||||
# Erreur technique (ex: COM) -> 500 Internal Server Error
|
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
|
||||||
# ENDPOINTS - ARTICLES
|
|
||||||
# =====================================================
|
|
||||||
@app.post("/sage/articles/list", dependencies=[Depends(verify_token)])
|
@app.post("/sage/articles/list", dependencies=[Depends(verify_token)])
|
||||||
def articles_list(req: FiltreRequest):
|
def articles_list(req: FiltreRequest):
|
||||||
"""Liste des articles avec filtre optionnel"""
|
"""Liste des articles avec filtre optionnel"""
|
||||||
|
|
@ -1036,14 +1003,10 @@ def article_get(req: CodeRequest):
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
|
||||||
# ENDPOINTS - DEVIS
|
|
||||||
# =====================================================
|
|
||||||
@app.post("/sage/devis/create", dependencies=[Depends(verify_token)])
|
@app.post("/sage/devis/create", dependencies=[Depends(verify_token)])
|
||||||
def creer_devis(req: DevisRequest):
|
def creer_devis(req: DevisRequest):
|
||||||
"""Création d'un devis"""
|
"""Création d'un devis"""
|
||||||
try:
|
try:
|
||||||
# Transformer en format attendu par sage_connector
|
|
||||||
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(),
|
||||||
|
|
@ -1062,7 +1025,6 @@ def creer_devis(req: DevisRequest):
|
||||||
@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):
|
||||||
try:
|
try:
|
||||||
# Lecture complète depuis Sage (avec lignes)
|
|
||||||
devis = sage.lire_devis(req.code)
|
devis = sage.lire_devis(req.code)
|
||||||
if not devis:
|
if not devis:
|
||||||
raise HTTPException(404, f"Devis {req.code} non trouvé")
|
raise HTTPException(404, f"Devis {req.code} non trouvé")
|
||||||
|
|
@ -1081,14 +1043,11 @@ def devis_list(
|
||||||
filtre: str = Query("", description="Filtre texte (numero, client)"),
|
filtre: str = Query("", description="Filtre texte (numero, client)"),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
# Récupération depuis le cache (instantané)
|
|
||||||
devis_list = sage.lister_tous_devis_cache(filtre)
|
devis_list = sage.lister_tous_devis_cache(filtre)
|
||||||
|
|
||||||
# Filtrer par statut si demandé
|
|
||||||
if statut is not None:
|
if statut is not None:
|
||||||
devis_list = [d for d in devis_list if d.get("statut") == statut]
|
devis_list = [d for d in devis_list if d.get("statut") == statut]
|
||||||
|
|
||||||
# Limiter le nombre de résultats
|
|
||||||
devis_list = devis_list[:limit]
|
devis_list = devis_list[:limit]
|
||||||
|
|
||||||
logger.info(f" {len(devis_list)} devis retournés depuis le cache")
|
logger.info(f" {len(devis_list)} devis retournés depuis le cache")
|
||||||
|
|
@ -1135,9 +1094,6 @@ def changer_statut_devis_endpoint(numero: str, nouveau_statut: int):
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
|
||||||
# ENDPOINTS - DOCUMENTS
|
|
||||||
# =====================================================
|
|
||||||
@app.post("/sage/documents/get", dependencies=[Depends(verify_token)])
|
@app.post("/sage/documents/get", dependencies=[Depends(verify_token)])
|
||||||
def lire_document(req: DocumentGetRequest):
|
def lire_document(req: DocumentGetRequest):
|
||||||
"""Lecture d'un document (commande, facture, etc.)"""
|
"""Lecture d'un document (commande, facture, etc.)"""
|
||||||
|
|
@ -1165,7 +1121,6 @@ def transformer_document(
|
||||||
f"(type {type_source}) → type {type_cible}"
|
f"(type {type_source}) → type {type_cible}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Matrice des transformations valides pour VOTRE Sage
|
|
||||||
transformations_valides = {
|
transformations_valides = {
|
||||||
(0, 10), # Devis → Commande
|
(0, 10), # Devis → Commande
|
||||||
(10, 30), # Commande → Bon de livraison
|
(10, 30), # Commande → Bon de livraison
|
||||||
|
|
@ -1184,7 +1139,6 @@ def transformer_document(
|
||||||
f"Transformations valides: {transformations_valides}",
|
f"Transformations valides: {transformations_valides}",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Appel au connecteur Sage
|
|
||||||
resultat = sage.transformer_document(numero_source, type_source, type_cible)
|
resultat = sage.transformer_document(numero_source, type_source, type_cible)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|
@ -1227,9 +1181,6 @@ def maj_derniere_relance(doc_id: str, type_doc: int):
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
|
||||||
# ENDPOINTS - CONTACTS
|
|
||||||
# =====================================================
|
|
||||||
@app.post("/sage/contact/read", dependencies=[Depends(verify_token)])
|
@app.post("/sage/contact/read", dependencies=[Depends(verify_token)])
|
||||||
def contact_read(req: CodeRequest):
|
def contact_read(req: CodeRequest):
|
||||||
"""Lecture du contact principal d'un client"""
|
"""Lecture du contact principal d'un client"""
|
||||||
|
|
@ -1314,9 +1265,6 @@ def lire_remise_max_client(code: str):
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
|
||||||
# ENDPOINTS - ADMIN
|
|
||||||
# =====================================================
|
|
||||||
@app.post("/sage/cache/refresh", dependencies=[Depends(verify_token)])
|
@app.post("/sage/cache/refresh", dependencies=[Depends(verify_token)])
|
||||||
def refresh_cache():
|
def refresh_cache():
|
||||||
"""Force le rafraîchissement du cache"""
|
"""Force le rafraîchissement du cache"""
|
||||||
|
|
@ -1342,9 +1290,6 @@ def cache_info_get():
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
|
||||||
# ENDPOINTS - PROSPECTS
|
|
||||||
# =====================================================
|
|
||||||
@app.post("/sage/prospects/list", dependencies=[Depends(verify_token)])
|
@app.post("/sage/prospects/list", dependencies=[Depends(verify_token)])
|
||||||
def prospects_list(req: FiltreRequest):
|
def prospects_list(req: FiltreRequest):
|
||||||
try:
|
try:
|
||||||
|
|
@ -1369,13 +1314,9 @@ def prospect_get(req: CodeRequest):
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
|
||||||
# ENDPOINTS - FOURNISSEURS
|
|
||||||
# =====================================================
|
|
||||||
@app.post("/sage/fournisseurs/list", dependencies=[Depends(verify_token)])
|
@app.post("/sage/fournisseurs/list", dependencies=[Depends(verify_token)])
|
||||||
def fournisseurs_list(req: FiltreRequest):
|
def fournisseurs_list(req: FiltreRequest):
|
||||||
try:
|
try:
|
||||||
# Utiliser le cache au lieu de la lecture directe
|
|
||||||
fournisseurs = sage.lister_tous_fournisseurs_cache(req.filtre)
|
fournisseurs = sage.lister_tous_fournisseurs_cache(req.filtre)
|
||||||
|
|
||||||
logger.info(f" {len(fournisseurs)} fournisseurs retournés depuis le cache")
|
logger.info(f" {len(fournisseurs)} fournisseurs retournés depuis le cache")
|
||||||
|
|
@ -1390,7 +1331,6 @@ def fournisseurs_list(req: FiltreRequest):
|
||||||
@app.post("/sage/fournisseurs/create", dependencies=[Depends(verify_token)])
|
@app.post("/sage/fournisseurs/create", dependencies=[Depends(verify_token)])
|
||||||
def create_fournisseur_endpoint(req: FournisseurCreateRequest):
|
def create_fournisseur_endpoint(req: FournisseurCreateRequest):
|
||||||
try:
|
try:
|
||||||
# Appel au connecteur Sage
|
|
||||||
resultat = sage.creer_fournisseur(req.dict())
|
resultat = sage.creer_fournisseur(req.dict())
|
||||||
|
|
||||||
logger.info(f" Fournisseur créé: {resultat.get('numero')}")
|
logger.info(f" Fournisseur créé: {resultat.get('numero')}")
|
||||||
|
|
@ -1398,12 +1338,10 @@ def create_fournisseur_endpoint(req: FournisseurCreateRequest):
|
||||||
return {"success": True, "data": resultat}
|
return {"success": True, "data": resultat}
|
||||||
|
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
# Erreur métier (ex: doublon)
|
|
||||||
logger.warning(f"⚠️ Erreur métier création fournisseur: {e}")
|
logger.warning(f"⚠️ Erreur métier création fournisseur: {e}")
|
||||||
raise HTTPException(400, str(e))
|
raise HTTPException(400, str(e))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Erreur technique (ex: COM)
|
|
||||||
logger.error(f" Erreur technique création fournisseur: {e}")
|
logger.error(f" Erreur technique création fournisseur: {e}")
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
@ -1440,9 +1378,6 @@ def fournisseur_get(req: CodeRequest):
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
|
||||||
# ENDPOINTS - AVOIRS
|
|
||||||
# =====================================================
|
|
||||||
@app.post("/sage/avoirs/list", dependencies=[Depends(verify_token)])
|
@app.post("/sage/avoirs/list", dependencies=[Depends(verify_token)])
|
||||||
def avoirs_list(
|
def avoirs_list(
|
||||||
limit: int = Query(100, description="Nombre max d'avoirs"),
|
limit: int = Query(100, description="Nombre max d'avoirs"),
|
||||||
|
|
@ -1450,14 +1385,11 @@ def avoirs_list(
|
||||||
filtre: str = Query("", description="Filtre texte"),
|
filtre: str = Query("", description="Filtre texte"),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
# Récupération depuis le cache (instantané)
|
|
||||||
avoirs = sage.lister_tous_avoirs_cache(filtre)
|
avoirs = sage.lister_tous_avoirs_cache(filtre)
|
||||||
|
|
||||||
# Filtrer par statut si demandé
|
|
||||||
if statut is not None:
|
if statut is not None:
|
||||||
avoirs = [a for a in avoirs if a.get("statut") == statut]
|
avoirs = [a for a in avoirs if a.get("statut") == statut]
|
||||||
|
|
||||||
# Limiter le nombre de résultats
|
|
||||||
avoirs = avoirs[:limit]
|
avoirs = avoirs[:limit]
|
||||||
|
|
||||||
logger.info(f" {len(avoirs)} avoirs retournés depuis le cache")
|
logger.info(f" {len(avoirs)} avoirs retournés depuis le cache")
|
||||||
|
|
@ -1472,14 +1404,12 @@ def avoirs_list(
|
||||||
@app.post("/sage/avoirs/get", dependencies=[Depends(verify_token)])
|
@app.post("/sage/avoirs/get", dependencies=[Depends(verify_token)])
|
||||||
def avoir_get(req: CodeRequest):
|
def avoir_get(req: CodeRequest):
|
||||||
try:
|
try:
|
||||||
# Essayer le cache d'abord
|
|
||||||
avoir = sage.lire_avoir_cache(req.code)
|
avoir = sage.lire_avoir_cache(req.code)
|
||||||
|
|
||||||
if avoir:
|
if avoir:
|
||||||
logger.info(f" Avoir {req.code} retourné depuis le cache")
|
logger.info(f" Avoir {req.code} retourné depuis le cache")
|
||||||
return {"success": True, "data": avoir, "source": "cache"}
|
return {"success": True, "data": avoir, "source": "cache"}
|
||||||
|
|
||||||
# Pas dans le cache → Lecture directe depuis Sage
|
|
||||||
logger.info(f"⚠️ Avoir {req.code} absent du cache, lecture depuis Sage...")
|
logger.info(f"⚠️ Avoir {req.code} absent du cache, lecture depuis Sage...")
|
||||||
avoir = sage.lire_avoir(req.code)
|
avoir = sage.lire_avoir(req.code)
|
||||||
|
|
||||||
|
|
@ -1495,9 +1425,6 @@ def avoir_get(req: CodeRequest):
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
|
||||||
# ENDPOINTS - LIVRAISONS
|
|
||||||
# =====================================================
|
|
||||||
@app.post("/sage/livraisons/list", dependencies=[Depends(verify_token)])
|
@app.post("/sage/livraisons/list", dependencies=[Depends(verify_token)])
|
||||||
def livraisons_list(
|
def livraisons_list(
|
||||||
limit: int = Query(100, description="Nombre max de livraisons"),
|
limit: int = Query(100, description="Nombre max de livraisons"),
|
||||||
|
|
@ -1505,14 +1432,11 @@ def livraisons_list(
|
||||||
filtre: str = Query("", description="Filtre texte"),
|
filtre: str = Query("", description="Filtre texte"),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
# Récupération depuis le cache (instantané)
|
|
||||||
livraisons = sage.lister_toutes_livraisons_cache(filtre)
|
livraisons = sage.lister_toutes_livraisons_cache(filtre)
|
||||||
|
|
||||||
# Filtrer par statut si demandé
|
|
||||||
if statut is not None:
|
if statut is not None:
|
||||||
livraisons = [l for l in livraisons if l.get("statut") == statut]
|
livraisons = [l for l in livraisons if l.get("statut") == statut]
|
||||||
|
|
||||||
# Limiter le nombre de résultats
|
|
||||||
livraisons = livraisons[:limit]
|
livraisons = livraisons[:limit]
|
||||||
|
|
||||||
logger.info(f" {len(livraisons)} livraisons retournées depuis le cache")
|
logger.info(f" {len(livraisons)} livraisons retournées depuis le cache")
|
||||||
|
|
@ -1527,14 +1451,12 @@ def livraisons_list(
|
||||||
@app.post("/sage/livraisons/get", dependencies=[Depends(verify_token)])
|
@app.post("/sage/livraisons/get", dependencies=[Depends(verify_token)])
|
||||||
def livraison_get(req: CodeRequest):
|
def livraison_get(req: CodeRequest):
|
||||||
try:
|
try:
|
||||||
# Essayer le cache d'abord
|
|
||||||
livraison = sage.lire_livraison_cache(req.code)
|
livraison = sage.lire_livraison_cache(req.code)
|
||||||
|
|
||||||
if livraison:
|
if livraison:
|
||||||
logger.info(f" Livraison {req.code} retournée depuis le cache")
|
logger.info(f" Livraison {req.code} retournée depuis le cache")
|
||||||
return {"success": True, "data": livraison, "source": "cache"}
|
return {"success": True, "data": livraison, "source": "cache"}
|
||||||
|
|
||||||
# Pas dans le cache → Lecture directe depuis Sage
|
|
||||||
logger.info(f"⚠️ Livraison {req.code} absente du cache, lecture depuis Sage...")
|
logger.info(f"⚠️ Livraison {req.code} absente du cache, lecture depuis Sage...")
|
||||||
livraison = sage.lire_livraison(req.code)
|
livraison = sage.lire_livraison(req.code)
|
||||||
|
|
||||||
|
|
@ -1564,15 +1486,11 @@ def modifier_devis_endpoint(req: DevisUpdateGatewayRequest):
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
|
||||||
# ENDPOINTS - CRÉATION ET MODIFICATION COMMANDES
|
|
||||||
# =====================================================
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/sage/commandes/create", dependencies=[Depends(verify_token)])
|
@app.post("/sage/commandes/create", dependencies=[Depends(verify_token)])
|
||||||
def creer_commande_endpoint(req: CommandeCreateRequest):
|
def creer_commande_endpoint(req: CommandeCreateRequest):
|
||||||
try:
|
try:
|
||||||
# Transformer en format attendu par sage_connector
|
|
||||||
commande_data = {
|
commande_data = {
|
||||||
"client": {"code": req.client_id, "intitule": ""},
|
"client": {"code": req.client_id, "intitule": ""},
|
||||||
"date_commande": req.date_commande or date.today(),
|
"date_commande": req.date_commande or date.today(),
|
||||||
|
|
@ -1609,12 +1527,10 @@ def modifier_commande_endpoint(req: CommandeUpdateGatewayRequest):
|
||||||
@app.post("/sage/livraisons/create", dependencies=[Depends(verify_token)])
|
@app.post("/sage/livraisons/create", dependencies=[Depends(verify_token)])
|
||||||
def creer_livraison_endpoint(req: LivraisonCreateGatewayRequest):
|
def creer_livraison_endpoint(req: LivraisonCreateGatewayRequest):
|
||||||
try:
|
try:
|
||||||
# Vérifier que le client existe
|
|
||||||
client = sage.lire_client(req.client_id)
|
client = sage.lire_client(req.client_id)
|
||||||
if not client:
|
if not client:
|
||||||
raise HTTPException(404, f"Client {req.client_id} introuvable")
|
raise HTTPException(404, f"Client {req.client_id} introuvable")
|
||||||
|
|
||||||
# Préparer les données pour le connecteur
|
|
||||||
livraison_data = {
|
livraison_data = {
|
||||||
"client": {"code": req.client_id, "intitule": ""},
|
"client": {"code": req.client_id, "intitule": ""},
|
||||||
"date_livraison": req.date_livraison or date.today(),
|
"date_livraison": req.date_livraison or date.today(),
|
||||||
|
|
@ -1651,12 +1567,10 @@ def modifier_livraison_endpoint(req: LivraisonUpdateGatewayRequest):
|
||||||
@app.post("/sage/avoirs/create", dependencies=[Depends(verify_token)])
|
@app.post("/sage/avoirs/create", dependencies=[Depends(verify_token)])
|
||||||
def creer_avoir_endpoint(req: AvoirCreateGatewayRequest):
|
def creer_avoir_endpoint(req: AvoirCreateGatewayRequest):
|
||||||
try:
|
try:
|
||||||
# Vérifier que le client existe
|
|
||||||
client = sage.lire_client(req.client_id)
|
client = sage.lire_client(req.client_id)
|
||||||
if not client:
|
if not client:
|
||||||
raise HTTPException(404, f"Client {req.client_id} introuvable")
|
raise HTTPException(404, f"Client {req.client_id} introuvable")
|
||||||
|
|
||||||
# Préparer les données pour le connecteur
|
|
||||||
avoir_data = {
|
avoir_data = {
|
||||||
"client": {"code": req.client_id, "intitule": ""},
|
"client": {"code": req.client_id, "intitule": ""},
|
||||||
"date_avoir": req.date_avoir or date.today(),
|
"date_avoir": req.date_avoir or date.today(),
|
||||||
|
|
@ -1696,12 +1610,10 @@ def modifier_avoir_endpoint(req: AvoirUpdateGatewayRequest):
|
||||||
@app.post("/sage/factures/create", dependencies=[Depends(verify_token)])
|
@app.post("/sage/factures/create", dependencies=[Depends(verify_token)])
|
||||||
def creer_facture_endpoint(req: FactureCreateGatewayRequest):
|
def creer_facture_endpoint(req: FactureCreateGatewayRequest):
|
||||||
try:
|
try:
|
||||||
# Vérifier que le client existe
|
|
||||||
client = sage.lire_client(req.client_id)
|
client = sage.lire_client(req.client_id)
|
||||||
if not client:
|
if not client:
|
||||||
raise HTTPException(404, f"Client {req.client_id} introuvable")
|
raise HTTPException(404, f"Client {req.client_id} introuvable")
|
||||||
|
|
||||||
# Préparer les données pour le connecteur
|
|
||||||
facture_data = {
|
facture_data = {
|
||||||
"client": {"code": req.client_id, "intitule": ""},
|
"client": {"code": req.client_id, "intitule": ""},
|
||||||
"date_facture": req.date_facture or date.today(),
|
"date_facture": req.date_facture or date.today(),
|
||||||
|
|
@ -1788,9 +1700,6 @@ async def creer_famille(famille: FamilleCreate):
|
||||||
raise HTTPException(status_code=500, detail=f"Erreur serveur : {str(e)}")
|
raise HTTPException(status_code=500, detail=f"Erreur serveur : {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
# ========================================
|
|
||||||
# ROUTE GET : Lister toutes les familles
|
|
||||||
# ========================================
|
|
||||||
|
|
||||||
|
|
||||||
@app.get(
|
@app.get(
|
||||||
|
|
@ -1817,9 +1726,6 @@ async def lister_familles(filtre: str = ""):
|
||||||
raise HTTPException(status_code=500, detail=f"Erreur serveur : {str(e)}")
|
raise HTTPException(status_code=500, detail=f"Erreur serveur : {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
# ========================================
|
|
||||||
# ROUTE GET : Lire UNE famille par son code
|
|
||||||
# ========================================
|
|
||||||
|
|
||||||
|
|
||||||
@app.get(
|
@app.get(
|
||||||
|
|
@ -1844,9 +1750,6 @@ async def lire_famille(code: str):
|
||||||
raise HTTPException(status_code=500, detail=f"Erreur serveur : {str(e)}")
|
raise HTTPException(status_code=500, detail=f"Erreur serveur : {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
# ========================================
|
|
||||||
# ROUTE GET : Statistiques sur les familles
|
|
||||||
# ========================================
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/sage/familles/stats", response_model=dict)
|
@app.get("/sage/familles/stats", response_model=dict)
|
||||||
|
|
@ -1854,13 +1757,11 @@ async def stats_familles():
|
||||||
try:
|
try:
|
||||||
familles = sage.lister_toutes_familles()
|
familles = sage.lister_toutes_familles()
|
||||||
|
|
||||||
# Calculer les stats
|
|
||||||
nb_total = len(familles)
|
nb_total = len(familles)
|
||||||
nb_detail = sum(1 for f in familles if f["type"] == 0)
|
nb_detail = sum(1 for f in familles if f["type"] == 0)
|
||||||
nb_total_type = sum(1 for f in familles if f["type"] == 1)
|
nb_total_type = sum(1 for f in familles if f["type"] == 1)
|
||||||
nb_statistiques = sum(1 for f in familles if f["est_statistique"])
|
nb_statistiques = sum(1 for f in familles if f["est_statistique"])
|
||||||
|
|
||||||
# Top 10 familles par intitulé (alphabétique)
|
|
||||||
top_familles = sorted(familles, key=lambda f: f["intitule"])[:10]
|
top_familles = sorted(familles, key=lambda f: f["intitule"])[:10]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -1894,13 +1795,11 @@ def generer_pdf_document(req: PDFGenerationRequest):
|
||||||
try:
|
try:
|
||||||
logger.info(f" Génération PDF: {req.doc_id} (type={req.type_doc})")
|
logger.info(f" Génération PDF: {req.doc_id} (type={req.type_doc})")
|
||||||
|
|
||||||
# Appel au connecteur Sage
|
|
||||||
pdf_bytes = sage.generer_pdf_document(req.doc_id, req.type_doc)
|
pdf_bytes = sage.generer_pdf_document(req.doc_id, req.type_doc)
|
||||||
|
|
||||||
if not pdf_bytes:
|
if not pdf_bytes:
|
||||||
raise HTTPException(500, "PDF vide généré")
|
raise HTTPException(500, "PDF vide généré")
|
||||||
|
|
||||||
# Encoder en base64 pour le transport JSON
|
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
pdf_base64 = base64.b64encode(pdf_bytes).decode("utf-8")
|
pdf_base64 = base64.b64encode(pdf_bytes).decode("utf-8")
|
||||||
|
|
@ -1948,7 +1847,6 @@ def lister_depots():
|
||||||
depot = win32com.client.CastTo(persist, "IBODepot3")
|
depot = win32com.client.CastTo(persist, "IBODepot3")
|
||||||
depot.Read()
|
depot.Read()
|
||||||
|
|
||||||
# Lire les attributs identifiés
|
|
||||||
code = ""
|
code = ""
|
||||||
numero = 0
|
numero = 0
|
||||||
intitule = ""
|
intitule = ""
|
||||||
|
|
@ -1963,7 +1861,6 @@ def lister_depots():
|
||||||
try:
|
try:
|
||||||
numero = int(getattr(depot, "Compteur", 0))
|
numero = int(getattr(depot, "Compteur", 0))
|
||||||
except:
|
except:
|
||||||
# Fallback : convertir DE_Code en int
|
|
||||||
try:
|
try:
|
||||||
numero = int(code)
|
numero = int(code)
|
||||||
except:
|
except:
|
||||||
|
|
@ -1984,13 +1881,11 @@ def lister_depots():
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Validation : un dépôt doit avoir au moins un code
|
|
||||||
if not code:
|
if not code:
|
||||||
logger.warning(f" ⚠️ Dépôt à l'index {index} sans code")
|
logger.warning(f" ⚠️ Dépôt à l'index {index} sans code")
|
||||||
index += 1
|
index += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Récupérer adresse (objet COM complexe)
|
|
||||||
adresse_complete = ""
|
adresse_complete = ""
|
||||||
try:
|
try:
|
||||||
adresse_obj = getattr(depot, "Adresse", None)
|
adresse_obj = getattr(depot, "Adresse", None)
|
||||||
|
|
@ -2005,7 +1900,6 @@ def lister_depots():
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Déterminer si principal (premier non exclu = principal)
|
|
||||||
principal = False
|
principal = False
|
||||||
if not exclu and len(depots) == 0:
|
if not exclu and len(depots) == 0:
|
||||||
principal = True
|
principal = True
|
||||||
|
|
@ -2030,7 +1924,6 @@ def lister_depots():
|
||||||
index += 1
|
index += 1
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# ⚠️ CORRECTION : "Accès refusé" = fin de liste dans cette version Sage
|
|
||||||
error_msg = str(e)
|
error_msg = str(e)
|
||||||
if "Accès refusé" in error_msg or "-1073741819" in error_msg:
|
if "Accès refusé" in error_msg or "-1073741819" in error_msg:
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|
@ -2081,7 +1974,6 @@ def creer_entree_stock(req: EntreeStockRequest):
|
||||||
f"📦 [ENTREE STOCK] Création bon d'entrée : {len(req.lignes)} ligne(s)"
|
f"📦 [ENTREE STOCK] Création bon d'entrée : {len(req.lignes)} ligne(s)"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Préparer les données pour le connecteur
|
|
||||||
entree_data = {
|
entree_data = {
|
||||||
"date_mouvement": req.date_entree or date.today(),
|
"date_mouvement": req.date_entree or date.today(),
|
||||||
"reference": req.reference,
|
"reference": req.reference,
|
||||||
|
|
@ -2090,7 +1982,6 @@ def creer_entree_stock(req: EntreeStockRequest):
|
||||||
"commentaire": req.commentaire,
|
"commentaire": req.commentaire,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Appel au connecteur
|
|
||||||
resultat = sage.creer_entree_stock(entree_data)
|
resultat = sage.creer_entree_stock(entree_data)
|
||||||
|
|
||||||
logger.info(f" [ENTREE STOCK] Créé : {resultat.get('numero')}")
|
logger.info(f" [ENTREE STOCK] Créé : {resultat.get('numero')}")
|
||||||
|
|
@ -2113,7 +2004,6 @@ def creer_sortie_stock(req: SortieStockRequest):
|
||||||
f"📤 [SORTIE STOCK] Création bon de sortie : {len(req.lignes)} ligne(s)"
|
f"📤 [SORTIE STOCK] Création bon de sortie : {len(req.lignes)} ligne(s)"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Préparer les données pour le connecteur
|
|
||||||
sortie_data = {
|
sortie_data = {
|
||||||
"date_mouvement": req.date_sortie or date.today(),
|
"date_mouvement": req.date_sortie or date.today(),
|
||||||
"reference": req.reference,
|
"reference": req.reference,
|
||||||
|
|
@ -2122,7 +2012,6 @@ def creer_sortie_stock(req: SortieStockRequest):
|
||||||
"commentaire": req.commentaire,
|
"commentaire": req.commentaire,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Appel au connecteur
|
|
||||||
resultat = sage.creer_sortie_stock(sortie_data)
|
resultat = sage.creer_sortie_stock(sortie_data)
|
||||||
|
|
||||||
logger.info(f" [SORTIE STOCK] Créé : {resultat.get('numero')}")
|
logger.info(f" [SORTIE STOCK] Créé : {resultat.get('numero')}")
|
||||||
|
|
@ -2155,9 +2044,6 @@ def lire_mouvement_stock(numero: str):
|
||||||
raise HTTPException(500, str(e))
|
raise HTTPException(500, str(e))
|
||||||
|
|
||||||
|
|
||||||
# =====================================================
|
|
||||||
# LANCEMENT
|
|
||||||
# =====================================================
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
uvicorn.run(
|
uvicorn.run(
|
||||||
"main:app",
|
"main:app",
|
||||||
|
|
|
||||||
|
|
@ -10334,84 +10334,345 @@ class SageConnector:
|
||||||
raise RuntimeError(f"Erreur technique Sage : {error_message}")
|
raise RuntimeError(f"Erreur technique Sage : {error_message}")
|
||||||
|
|
||||||
def lister_toutes_familles(
|
def lister_toutes_familles(
|
||||||
self, filtre: str = "", inclure_totaux: bool = False
|
self, filtre: str = "", inclure_totaux: bool = True
|
||||||
) -> List[Dict]:
|
) -> List[Dict]:
|
||||||
try:
|
try:
|
||||||
with self._get_sql_connection() as conn:
|
with self._get_sql_connection() as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
logger.info("[SQL] Détection des colonnes de F_FAMILLE...")
|
logger.info("[SQL] Détection des colonnes de F_FAMILLE...")
|
||||||
|
|
||||||
cursor.execute("SELECT TOP 1 * FROM F_FAMILLE")
|
cursor.execute("SELECT TOP 1 * FROM F_FAMILLE")
|
||||||
colonnes_disponibles = [column[0] for column in cursor.description]
|
colonnes_disponibles = [column[0] for column in cursor.description]
|
||||||
|
|
||||||
logger.info(f"[SQL] Colonnes trouvées : {len(colonnes_disponibles)}")
|
logger.info(f"[SQL] Colonnes trouvées : {len(colonnes_disponibles)}")
|
||||||
|
|
||||||
colonnes_souhaitees = [
|
# Colonnes organisées par catégorie
|
||||||
"FA_CodeFamille",
|
colonnes_souhaitees = [
|
||||||
"FA_Intitule",
|
# Identification
|
||||||
"FA_Type",
|
"FA_CodeFamille",
|
||||||
"FA_UniteVen",
|
"FA_Intitule",
|
||||||
"FA_Coef",
|
"FA_Type",
|
||||||
"FA_Central",
|
|
||||||
"FA_Nature",
|
|
||||||
"CG_NumAch",
|
|
||||||
"CG_NumVte",
|
|
||||||
"FA_Stat",
|
|
||||||
"FA_Raccourci",
|
|
||||||
]
|
|
||||||
|
|
||||||
colonnes_a_lire = [
|
# Vente et stock
|
||||||
col for col in colonnes_souhaitees if col in colonnes_disponibles
|
"FA_UniteVen",
|
||||||
]
|
"FA_Coef",
|
||||||
|
"FA_SuiviStock",
|
||||||
|
"FA_Garantie",
|
||||||
|
"FA_UnitePoids",
|
||||||
|
"FA_Delai",
|
||||||
|
"FA_NbColis",
|
||||||
|
|
||||||
if not colonnes_a_lire:
|
# Comptabilité
|
||||||
colonnes_a_lire = colonnes_disponibles
|
"CG_NumAch",
|
||||||
|
"CG_NumVte",
|
||||||
|
"FA_CodeFiscal",
|
||||||
|
"FA_Escompte",
|
||||||
|
|
||||||
logger.info(f"[SQL] Colonnes sélectionnées : {len(colonnes_a_lire)}")
|
# Organisation et classification
|
||||||
|
"FA_Central",
|
||||||
|
"FA_Nature",
|
||||||
|
"CL_No1",
|
||||||
|
"CL_No2",
|
||||||
|
"CL_No3",
|
||||||
|
"CL_No4",
|
||||||
|
|
||||||
colonnes_str = ", ".join(colonnes_a_lire)
|
# Statistiques
|
||||||
|
"FA_Stat01",
|
||||||
|
"FA_Stat02",
|
||||||
|
"FA_Stat03",
|
||||||
|
"FA_Stat04",
|
||||||
|
"FA_Stat05",
|
||||||
|
"FA_HorsStat",
|
||||||
|
|
||||||
query = f"""
|
# Paramètres commerciaux
|
||||||
SELECT {colonnes_str}
|
"FA_Pays",
|
||||||
FROM F_FAMILLE
|
"FA_VteDebit",
|
||||||
WHERE 1=1
|
"FA_NotImp",
|
||||||
"""
|
"FA_Contremarque",
|
||||||
|
"FA_FactPoids",
|
||||||
|
"FA_FactForfait",
|
||||||
|
"FA_Publie",
|
||||||
|
|
||||||
params = []
|
# Références et codes
|
||||||
|
"FA_RacineRef",
|
||||||
|
"FA_RacineCB",
|
||||||
|
"FA_Raccourci",
|
||||||
|
|
||||||
if "FA_Type" in colonnes_disponibles:
|
# Gestion
|
||||||
if not inclure_totaux:
|
"FA_SousTraitance",
|
||||||
query += " AND FA_Type = 0" # Seulement Détail
|
"FA_Fictif",
|
||||||
|
"FA_Criticite"
|
||||||
|
]
|
||||||
|
|
||||||
|
colonnes_a_lire = [
|
||||||
|
col for col in colonnes_souhaitees if col in colonnes_disponibles
|
||||||
|
]
|
||||||
|
|
||||||
|
if not colonnes_a_lire:
|
||||||
|
colonnes_a_lire = colonnes_disponibles
|
||||||
|
|
||||||
|
logger.info(f"[SQL] Colonnes sélectionnées : {len(colonnes_a_lire)}")
|
||||||
|
|
||||||
|
colonnes_str = ", ".join([f"f.{col}" for col in colonnes_a_lire])
|
||||||
|
|
||||||
|
# Requête avec LEFT JOIN pour compter les articles par famille
|
||||||
|
query = f"""
|
||||||
|
SELECT {colonnes_str},
|
||||||
|
ISNULL(COUNT(a.AR_Ref), 0) as nb_articles
|
||||||
|
FROM F_FAMILLE f
|
||||||
|
LEFT JOIN F_ARTICLE a ON f.FA_CodeFamille = a.FA_CodeFamille
|
||||||
|
WHERE 1=1
|
||||||
|
"""
|
||||||
|
|
||||||
|
params = []
|
||||||
|
|
||||||
|
# Filtrage par FA_Type (si demandé)
|
||||||
|
if "FA_Type" in colonnes_disponibles and not inclure_totaux:
|
||||||
|
query += " AND f.FA_Type = 0" # Seulement Détail
|
||||||
logger.info("[SQL] Filtre : FA_Type = 0 (Détail uniquement)")
|
logger.info("[SQL] Filtre : FA_Type = 0 (Détail uniquement)")
|
||||||
else:
|
else:
|
||||||
logger.info("[SQL] Filtre : TOUS les types (Détail + Total)")
|
logger.info("[SQL] Filtre : TOUS les types (Détail + Total)")
|
||||||
|
|
||||||
if filtre:
|
# Filtrage par texte
|
||||||
conditions_filtre = []
|
if filtre:
|
||||||
|
conditions_filtre = []
|
||||||
|
|
||||||
if "FA_CodeFamille" in colonnes_a_lire:
|
if "FA_CodeFamille" in colonnes_a_lire:
|
||||||
conditions_filtre.append("FA_CodeFamille LIKE ?")
|
conditions_filtre.append("f.FA_CodeFamille LIKE ?")
|
||||||
params.append(f"%{filtre}%")
|
params.append(f"%{filtre}%")
|
||||||
|
|
||||||
|
if "FA_Intitule" in colonnes_a_lire:
|
||||||
|
conditions_filtre.append("f.FA_Intitule LIKE ?")
|
||||||
|
params.append(f"%{filtre}%")
|
||||||
|
|
||||||
|
if conditions_filtre:
|
||||||
|
query += " AND (" + " OR ".join(conditions_filtre) + ")"
|
||||||
|
|
||||||
|
# GROUP BY pour le COUNT
|
||||||
|
query += f" GROUP BY {colonnes_str}"
|
||||||
|
|
||||||
|
# ORDER BY
|
||||||
if "FA_Intitule" in colonnes_a_lire:
|
if "FA_Intitule" in colonnes_a_lire:
|
||||||
conditions_filtre.append("FA_Intitule LIKE ?")
|
query += " ORDER BY f.FA_Intitule"
|
||||||
params.append(f"%{filtre}%")
|
elif "FA_CodeFamille" in colonnes_a_lire:
|
||||||
|
query += " ORDER BY f.FA_CodeFamille"
|
||||||
|
|
||||||
if conditions_filtre:
|
cursor.execute(query, params)
|
||||||
query += " AND (" + " OR ".join(conditions_filtre) + ")"
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
if "FA_Intitule" in colonnes_a_lire:
|
familles = []
|
||||||
query += " ORDER BY FA_Intitule"
|
|
||||||
elif "FA_CodeFamille" in colonnes_a_lire:
|
|
||||||
query += " ORDER BY FA_CodeFamille"
|
|
||||||
|
|
||||||
cursor.execute(query, params)
|
for row in rows:
|
||||||
rows = cursor.fetchall()
|
famille = {}
|
||||||
|
|
||||||
familles = []
|
# Récupération des colonnes (sauf la dernière qui est nb_articles)
|
||||||
|
for idx, colonne in enumerate(colonnes_a_lire):
|
||||||
|
valeur = row[idx]
|
||||||
|
|
||||||
for row in rows:
|
if isinstance(valeur, str):
|
||||||
|
valeur = valeur.strip()
|
||||||
|
|
||||||
|
famille[colonne] = valeur
|
||||||
|
|
||||||
|
# Récupération du nb_articles (dernière colonne)
|
||||||
|
famille["nb_articles"] = row[-1]
|
||||||
|
|
||||||
|
# Champs de base (compatibilité)
|
||||||
|
if "FA_CodeFamille" in famille:
|
||||||
|
famille["code"] = famille["FA_CodeFamille"]
|
||||||
|
|
||||||
|
if "FA_Intitule" in famille:
|
||||||
|
famille["intitule"] = famille["FA_Intitule"]
|
||||||
|
|
||||||
|
if "FA_Type" in famille:
|
||||||
|
type_val = famille["FA_Type"]
|
||||||
|
famille["type"] = type_val
|
||||||
|
famille["type_libelle"] = "Total" if type_val == 1 else "Détail"
|
||||||
|
famille["est_total"] = type_val == 1
|
||||||
|
else:
|
||||||
|
famille["type"] = 0
|
||||||
|
famille["type_libelle"] = "Détail"
|
||||||
|
famille["est_total"] = False
|
||||||
|
|
||||||
|
# Vente et unités
|
||||||
|
famille["unite_vente"] = str(famille.get("FA_UniteVen", ""))
|
||||||
|
famille["unite_poids"] = str(famille.get("FA_UnitePoids", ""))
|
||||||
|
famille["coef"] = (
|
||||||
|
float(famille.get("FA_Coef", 0.0))
|
||||||
|
if famille.get("FA_Coef") is not None
|
||||||
|
else 0.0
|
||||||
|
)
|
||||||
|
|
||||||
|
# Stock et logistique
|
||||||
|
famille["suivi_stock"] = bool(famille.get("FA_SuiviStock", 0))
|
||||||
|
famille["garantie"] = int(famille.get("FA_Garantie", 0))
|
||||||
|
famille["delai"] = int(famille.get("FA_Delai", 0))
|
||||||
|
famille["nb_colis"] = int(famille.get("FA_NbColis", 0))
|
||||||
|
|
||||||
|
# Comptabilité
|
||||||
|
famille["compte_achat"] = famille.get("CG_NumAch", "")
|
||||||
|
famille["compte_vente"] = famille.get("CG_NumVte", "")
|
||||||
|
famille["code_fiscal"] = famille.get("FA_CodeFiscal", "")
|
||||||
|
famille["escompte"] = bool(famille.get("FA_Escompte", 0))
|
||||||
|
|
||||||
|
# Organisation
|
||||||
|
famille["est_centrale"] = bool(famille.get("FA_Central", 0))
|
||||||
|
famille["nature"] = famille.get("FA_Nature", 0)
|
||||||
|
famille["pays"] = famille.get("FA_Pays", "")
|
||||||
|
|
||||||
|
# Classifications
|
||||||
|
famille["categorie_1"] = famille.get("CL_No1", 0)
|
||||||
|
famille["categorie_2"] = famille.get("CL_No2", 0)
|
||||||
|
famille["categorie_3"] = famille.get("CL_No3", 0)
|
||||||
|
famille["categorie_4"] = famille.get("CL_No4", 0)
|
||||||
|
|
||||||
|
# Statistiques
|
||||||
|
famille["stat_01"] = famille.get("FA_Stat01", "")
|
||||||
|
famille["stat_02"] = famille.get("FA_Stat02", "")
|
||||||
|
famille["stat_03"] = famille.get("FA_Stat03", "")
|
||||||
|
famille["stat_04"] = famille.get("FA_Stat04", "")
|
||||||
|
famille["stat_05"] = famille.get("FA_Stat05", "")
|
||||||
|
famille["hors_statistique"] = bool(famille.get("FA_HorsStat", 0))
|
||||||
|
|
||||||
|
# Paramètres commerciaux
|
||||||
|
famille["vente_debit"] = bool(famille.get("FA_VteDebit", 0))
|
||||||
|
famille["non_imprimable"] = bool(famille.get("FA_NotImp", 0))
|
||||||
|
famille["contremarque"] = bool(famille.get("FA_Contremarque", 0))
|
||||||
|
famille["fact_poids"] = bool(famille.get("FA_FactPoids", 0))
|
||||||
|
famille["fact_forfait"] = bool(famille.get("FA_FactForfait", 0))
|
||||||
|
famille["publie"] = bool(famille.get("FA_Publie", 0))
|
||||||
|
|
||||||
|
# Références
|
||||||
|
famille["racine_reference"] = famille.get("FA_RacineRef", "")
|
||||||
|
famille["racine_code_barre"] = famille.get("FA_RacineCB", "")
|
||||||
|
famille["raccourci"] = famille.get("FA_Raccourci", "")
|
||||||
|
|
||||||
|
# Gestion
|
||||||
|
famille["sous_traitance"] = bool(famille.get("FA_SousTraitance", 0))
|
||||||
|
famille["fictif"] = bool(famille.get("FA_Fictif", 0))
|
||||||
|
famille["criticite"] = int(famille.get("FA_Criticite", 0))
|
||||||
|
|
||||||
|
familles.append(famille)
|
||||||
|
|
||||||
|
type_msg = "DÉTAIL uniquement" if not inclure_totaux else "TOUS types"
|
||||||
|
logger.info(f"SQL: {len(familles)} familles chargées ({type_msg})")
|
||||||
|
|
||||||
|
return familles
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur SQL familles: {e}", exc_info=True)
|
||||||
|
raise RuntimeError(f"Erreur lecture familles: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
def lire_famille(self, code: str) -> Dict:
|
||||||
|
"""
|
||||||
|
Lit une seule famille - même structure que lister_toutes_familles
|
||||||
|
|
||||||
|
Args:
|
||||||
|
code: Code de la famille à lire
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict avec la structure identique à lister_toutes_familles
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with self._get_sql_connection() as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
logger.info(f"[SQL] Lecture famille : {code}")
|
||||||
|
|
||||||
|
# Détection des colonnes disponibles
|
||||||
|
cursor.execute("SELECT TOP 1 * FROM F_FAMILLE")
|
||||||
|
colonnes_disponibles = [column[0] for column in cursor.description]
|
||||||
|
|
||||||
|
logger.info(f"[SQL] Colonnes trouvées : {len(colonnes_disponibles)}")
|
||||||
|
|
||||||
|
# Colonnes organisées par catégorie (IDENTIQUE à lister_toutes_familles)
|
||||||
|
colonnes_souhaitees = [
|
||||||
|
# Identification
|
||||||
|
"FA_CodeFamille",
|
||||||
|
"FA_Intitule",
|
||||||
|
"FA_Type",
|
||||||
|
|
||||||
|
# Vente et stock
|
||||||
|
"FA_UniteVen",
|
||||||
|
"FA_Coef",
|
||||||
|
"FA_SuiviStock",
|
||||||
|
"FA_Garantie",
|
||||||
|
"FA_UnitePoids",
|
||||||
|
"FA_Delai",
|
||||||
|
"FA_NbColis",
|
||||||
|
|
||||||
|
# Comptabilité
|
||||||
|
"CG_NumAch",
|
||||||
|
"CG_NumVte",
|
||||||
|
"FA_CodeFiscal",
|
||||||
|
"FA_Escompte",
|
||||||
|
|
||||||
|
# Organisation et classification
|
||||||
|
"FA_Central",
|
||||||
|
"FA_Nature",
|
||||||
|
"CL_No1",
|
||||||
|
"CL_No2",
|
||||||
|
"CL_No3",
|
||||||
|
"CL_No4",
|
||||||
|
|
||||||
|
# Statistiques
|
||||||
|
"FA_Stat01",
|
||||||
|
"FA_Stat02",
|
||||||
|
"FA_Stat03",
|
||||||
|
"FA_Stat04",
|
||||||
|
"FA_Stat05",
|
||||||
|
"FA_HorsStat",
|
||||||
|
|
||||||
|
# Paramètres commerciaux
|
||||||
|
"FA_Pays",
|
||||||
|
"FA_VteDebit",
|
||||||
|
"FA_NotImp",
|
||||||
|
"FA_Contremarque",
|
||||||
|
"FA_FactPoids",
|
||||||
|
"FA_FactForfait",
|
||||||
|
"FA_Publie",
|
||||||
|
|
||||||
|
# Références et codes
|
||||||
|
"FA_RacineRef",
|
||||||
|
"FA_RacineCB",
|
||||||
|
"FA_Raccourci",
|
||||||
|
|
||||||
|
# Gestion
|
||||||
|
"FA_SousTraitance",
|
||||||
|
"FA_Fictif",
|
||||||
|
"FA_Criticite"
|
||||||
|
]
|
||||||
|
|
||||||
|
colonnes_a_lire = [
|
||||||
|
col for col in colonnes_souhaitees if col in colonnes_disponibles
|
||||||
|
]
|
||||||
|
|
||||||
|
if not colonnes_a_lire:
|
||||||
|
colonnes_a_lire = colonnes_disponibles
|
||||||
|
|
||||||
|
logger.info(f"[SQL] Colonnes sélectionnées : {len(colonnes_a_lire)}")
|
||||||
|
|
||||||
|
colonnes_str = ", ".join([f"f.{col}" for col in colonnes_a_lire])
|
||||||
|
|
||||||
|
# Requête avec LEFT JOIN pour compter les articles (IDENTIQUE à lister_toutes_familles)
|
||||||
|
query = f"""
|
||||||
|
SELECT {colonnes_str},
|
||||||
|
ISNULL(COUNT(a.AR_Ref), 0) as nb_articles
|
||||||
|
FROM F_FAMILLE f
|
||||||
|
LEFT JOIN F_ARTICLE a ON f.FA_CodeFamille = a.FA_CodeFamille
|
||||||
|
WHERE UPPER(f.FA_CodeFamille) = ?
|
||||||
|
GROUP BY {colonnes_str}
|
||||||
|
"""
|
||||||
|
|
||||||
|
cursor.execute(query, (code.upper().strip(),))
|
||||||
|
row = cursor.fetchone()
|
||||||
|
|
||||||
|
if not row:
|
||||||
|
raise ValueError(f"Famille '{code}' introuvable dans Sage")
|
||||||
|
|
||||||
|
# Construction du dictionnaire (IDENTIQUE à lister_toutes_familles)
|
||||||
famille = {}
|
famille = {}
|
||||||
|
|
||||||
for idx, colonne in enumerate(colonnes_a_lire):
|
for idx, colonne in enumerate(colonnes_a_lire):
|
||||||
|
|
@ -10422,6 +10683,10 @@ class SageConnector:
|
||||||
|
|
||||||
famille[colonne] = valeur
|
famille[colonne] = valeur
|
||||||
|
|
||||||
|
# Récupération du nb_articles (dernière colonne)
|
||||||
|
famille["nb_articles"] = row[-1]
|
||||||
|
|
||||||
|
# Champs de base (compatibilité)
|
||||||
if "FA_CodeFamille" in famille:
|
if "FA_CodeFamille" in famille:
|
||||||
famille["code"] = famille["FA_CodeFamille"]
|
famille["code"] = famille["FA_CodeFamille"]
|
||||||
|
|
||||||
|
|
@ -10438,308 +10703,75 @@ class SageConnector:
|
||||||
famille["type_libelle"] = "Détail"
|
famille["type_libelle"] = "Détail"
|
||||||
famille["est_total"] = False
|
famille["est_total"] = False
|
||||||
|
|
||||||
|
# Vente et unités
|
||||||
famille["unite_vente"] = str(famille.get("FA_UniteVen", ""))
|
famille["unite_vente"] = str(famille.get("FA_UniteVen", ""))
|
||||||
|
famille["unite_poids"] = str(famille.get("FA_UnitePoids", ""))
|
||||||
famille["coef"] = (
|
famille["coef"] = (
|
||||||
float(famille.get("FA_Coef", 0.0))
|
float(famille.get("FA_Coef", 0.0))
|
||||||
if famille.get("FA_Coef") is not None
|
if famille.get("FA_Coef") is not None
|
||||||
else 0.0
|
else 0.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Stock et logistique
|
||||||
|
famille["suivi_stock"] = bool(famille.get("FA_SuiviStock", 0))
|
||||||
|
famille["garantie"] = int(famille.get("FA_Garantie", 0))
|
||||||
|
famille["delai"] = int(famille.get("FA_Delai", 0))
|
||||||
|
famille["nb_colis"] = int(famille.get("FA_NbColis", 0))
|
||||||
|
|
||||||
|
# Comptabilité
|
||||||
famille["compte_achat"] = famille.get("CG_NumAch", "")
|
famille["compte_achat"] = famille.get("CG_NumAch", "")
|
||||||
famille["compte_vente"] = famille.get("CG_NumVte", "")
|
famille["compte_vente"] = famille.get("CG_NumVte", "")
|
||||||
famille["est_statistique"] = (
|
famille["code_fiscal"] = famille.get("FA_CodeFiscal", "")
|
||||||
(famille.get("FA_Stat") == 1) if "FA_Stat" in famille else False
|
famille["escompte"] = bool(famille.get("FA_Escompte", 0))
|
||||||
)
|
|
||||||
famille["est_centrale"] = (
|
# Organisation
|
||||||
(famille.get("FA_Central") == 1)
|
famille["est_centrale"] = bool(famille.get("FA_Central", 0))
|
||||||
if "FA_Central" in famille
|
|
||||||
else False
|
|
||||||
)
|
|
||||||
famille["nature"] = famille.get("FA_Nature", 0)
|
famille["nature"] = famille.get("FA_Nature", 0)
|
||||||
|
famille["pays"] = famille.get("FA_Pays", "")
|
||||||
|
|
||||||
familles.append(famille)
|
# Classifications
|
||||||
|
famille["categorie_1"] = famille.get("CL_No1", 0)
|
||||||
|
famille["categorie_2"] = famille.get("CL_No2", 0)
|
||||||
|
famille["categorie_3"] = famille.get("CL_No3", 0)
|
||||||
|
famille["categorie_4"] = famille.get("CL_No4", 0)
|
||||||
|
|
||||||
type_msg = "DÉTAIL uniquement" if not inclure_totaux else "TOUS types"
|
# Statistiques
|
||||||
logger.info(f"SQL: {len(familles)} familles chargées ({type_msg})")
|
famille["stat_01"] = famille.get("FA_Stat01", "")
|
||||||
|
famille["stat_02"] = famille.get("FA_Stat02", "")
|
||||||
|
famille["stat_03"] = famille.get("FA_Stat03", "")
|
||||||
|
famille["stat_04"] = famille.get("FA_Stat04", "")
|
||||||
|
famille["stat_05"] = famille.get("FA_Stat05", "")
|
||||||
|
famille["hors_statistique"] = bool(famille.get("FA_HorsStat", 0))
|
||||||
|
|
||||||
return familles
|
# Paramètres commerciaux
|
||||||
|
famille["vente_debit"] = bool(famille.get("FA_VteDebit", 0))
|
||||||
|
famille["non_imprimable"] = bool(famille.get("FA_NotImp", 0))
|
||||||
|
famille["contremarque"] = bool(famille.get("FA_Contremarque", 0))
|
||||||
|
famille["fact_poids"] = bool(famille.get("FA_FactPoids", 0))
|
||||||
|
famille["fact_forfait"] = bool(famille.get("FA_FactForfait", 0))
|
||||||
|
famille["publie"] = bool(famille.get("FA_Publie", 0))
|
||||||
|
|
||||||
except Exception as e:
|
# Références
|
||||||
logger.error(f"Erreur SQL familles: {e}", exc_info=True)
|
famille["racine_reference"] = famille.get("FA_RacineRef", "")
|
||||||
raise RuntimeError(f"Erreur lecture familles: {str(e)}")
|
famille["racine_code_barre"] = famille.get("FA_RacineCB", "")
|
||||||
|
famille["raccourci"] = famille.get("FA_Raccourci", "")
|
||||||
|
|
||||||
def lire_famille(self, code: str) -> Dict:
|
# Gestion
|
||||||
try:
|
famille["sous_traitance"] = bool(famille.get("FA_SousTraitance", 0))
|
||||||
with self._com_context(), self._lock_com:
|
famille["fictif"] = bool(famille.get("FA_Fictif", 0))
|
||||||
logger.info(f"[FAMILLE] Lecture : {code}")
|
famille["criticite"] = int(famille.get("FA_Criticite", 0))
|
||||||
|
|
||||||
code_recherche = code.upper().strip()
|
logger.info(f"SQL: Famille '{famille['code']}' chargée ({famille['nb_articles']} articles)")
|
||||||
|
|
||||||
famille_existe_sql = False
|
return famille
|
||||||
famille_code_exact = None
|
|
||||||
famille_type_sql = None
|
|
||||||
famille_intitule_sql = None
|
|
||||||
|
|
||||||
try:
|
except ValueError as e:
|
||||||
with self._get_sql_connection() as conn:
|
logger.error(f"Erreur famille: {e}")
|
||||||
cursor = conn.cursor()
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur SQL famille: {e}", exc_info=True)
|
||||||
|
raise RuntimeError(f"Erreur lecture famille: {str(e)}")
|
||||||
|
|
||||||
cursor.execute("SELECT TOP 1 * FROM F_FAMILLE")
|
|
||||||
colonnes_disponibles = [col[0] for col in cursor.description]
|
|
||||||
|
|
||||||
colonnes_select = ["FA_CodeFamille", "FA_Intitule"]
|
|
||||||
|
|
||||||
if "FA_Type" in colonnes_disponibles:
|
|
||||||
colonnes_select.append("FA_Type")
|
|
||||||
|
|
||||||
colonnes_str = ", ".join(colonnes_select)
|
|
||||||
|
|
||||||
cursor.execute(
|
|
||||||
f"""
|
|
||||||
SELECT {colonnes_str}
|
|
||||||
FROM F_FAMILLE
|
|
||||||
WHERE UPPER(FA_CodeFamille) = ?
|
|
||||||
""",
|
|
||||||
(code_recherche,),
|
|
||||||
)
|
|
||||||
|
|
||||||
row = cursor.fetchone()
|
|
||||||
|
|
||||||
if row:
|
|
||||||
famille_existe_sql = True
|
|
||||||
famille_code_exact = self._safe_strip(row.FA_CodeFamille)
|
|
||||||
famille_intitule_sql = self._safe_strip(row.FA_Intitule)
|
|
||||||
|
|
||||||
if "FA_Type" in colonnes_disponibles and len(row) > 2:
|
|
||||||
famille_type_sql = row.FA_Type
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
f" [SQL] Trouvée : {famille_code_exact} - {famille_intitule_sql} (type={famille_type_sql})"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Famille '{code}' introuvable dans Sage")
|
|
||||||
|
|
||||||
except ValueError:
|
|
||||||
raise
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f" [SQL] Erreur : {e}")
|
|
||||||
|
|
||||||
if not famille_code_exact:
|
|
||||||
famille_code_exact = code_recherche
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
f" [COM] Recherche de '{famille_code_exact}' via scanner..."
|
|
||||||
)
|
|
||||||
|
|
||||||
factory_famille = self.cial.FactoryFamille
|
|
||||||
famille_obj = None
|
|
||||||
index_trouve = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
index = 1
|
|
||||||
max_scan = 2000 # Scanner jusqu'à 2000 familles
|
|
||||||
|
|
||||||
while index <= max_scan:
|
|
||||||
try:
|
|
||||||
persist_test = factory_famille.List(index)
|
|
||||||
if persist_test is None:
|
|
||||||
break
|
|
||||||
|
|
||||||
fam_test = win32com.client.CastTo(
|
|
||||||
persist_test, "IBOFamille3"
|
|
||||||
)
|
|
||||||
fam_test.Read()
|
|
||||||
|
|
||||||
code_test = (
|
|
||||||
getattr(fam_test, "FA_CodeFamille", "").strip().upper()
|
|
||||||
)
|
|
||||||
|
|
||||||
if code_test == famille_code_exact:
|
|
||||||
famille_obj = fam_test
|
|
||||||
index_trouve = index
|
|
||||||
logger.info(f" [OK] Famille trouvée à l'index {index}")
|
|
||||||
break
|
|
||||||
|
|
||||||
index += 1
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
if "Accès refusé" in str(e) or "Access" in str(e):
|
|
||||||
break
|
|
||||||
index += 1
|
|
||||||
|
|
||||||
if not famille_obj:
|
|
||||||
if famille_existe_sql:
|
|
||||||
raise ValueError(
|
|
||||||
f"Famille '{code}' trouvée en SQL mais inaccessible via COM. "
|
|
||||||
f"Vérifiez les permissions."
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Famille '{code}' introuvable")
|
|
||||||
|
|
||||||
except ValueError:
|
|
||||||
raise
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f" [COM] Erreur scanner : {e}")
|
|
||||||
raise RuntimeError(f"Erreur chargement famille : {str(e)}")
|
|
||||||
|
|
||||||
logger.info("[FAMILLE] Extraction des informations...")
|
|
||||||
|
|
||||||
famille_obj.Read()
|
|
||||||
|
|
||||||
resultat = {
|
|
||||||
"code": getattr(famille_obj, "FA_CodeFamille", "").strip(),
|
|
||||||
"intitule": getattr(famille_obj, "FA_Intitule", "").strip(),
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
fa_type = getattr(famille_obj, "FA_Type", 0)
|
|
||||||
resultat["type"] = fa_type
|
|
||||||
resultat["type_libelle"] = "Total" if fa_type == 1 else "Détail"
|
|
||||||
resultat["est_total"] = fa_type == 1
|
|
||||||
resultat["est_detail"] = fa_type == 0
|
|
||||||
|
|
||||||
if fa_type == 1:
|
|
||||||
resultat["avertissement"] = (
|
|
||||||
"Cette famille est de type 'Total' (agrégation comptable) "
|
|
||||||
"et ne peut pas contenir d'articles directement."
|
|
||||||
)
|
|
||||||
logger.warning(
|
|
||||||
f" [TYPE] Famille Total détectée : {resultat['code']}"
|
|
||||||
)
|
|
||||||
except:
|
|
||||||
resultat["type"] = 0
|
|
||||||
resultat["type_libelle"] = "Détail"
|
|
||||||
resultat["est_total"] = False
|
|
||||||
resultat["est_detail"] = True
|
|
||||||
|
|
||||||
try:
|
|
||||||
resultat["unite_vente"] = getattr(
|
|
||||||
famille_obj, "FA_UniteVen", ""
|
|
||||||
).strip()
|
|
||||||
except:
|
|
||||||
resultat["unite_vente"] = ""
|
|
||||||
|
|
||||||
try:
|
|
||||||
coef = getattr(famille_obj, "FA_Coef", None)
|
|
||||||
resultat["coef"] = float(coef) if coef is not None else 0.0
|
|
||||||
except:
|
|
||||||
resultat["coef"] = 0.0
|
|
||||||
|
|
||||||
try:
|
|
||||||
resultat["nature"] = getattr(famille_obj, "FA_Nature", 0)
|
|
||||||
except:
|
|
||||||
resultat["nature"] = 0
|
|
||||||
|
|
||||||
try:
|
|
||||||
central = getattr(famille_obj, "FA_Central", None)
|
|
||||||
resultat["est_centrale"] = (
|
|
||||||
(central == 1) if central is not None else False
|
|
||||||
)
|
|
||||||
except:
|
|
||||||
resultat["est_centrale"] = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
stat = getattr(famille_obj, "FA_Stat", None)
|
|
||||||
resultat["est_statistique"] = (
|
|
||||||
(stat == 1) if stat is not None else False
|
|
||||||
)
|
|
||||||
except:
|
|
||||||
resultat["est_statistique"] = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
resultat["raccourci"] = getattr(
|
|
||||||
famille_obj, "FA_Raccourci", ""
|
|
||||||
).strip()
|
|
||||||
except:
|
|
||||||
resultat["raccourci"] = ""
|
|
||||||
|
|
||||||
try:
|
|
||||||
compte_achat_obj = getattr(famille_obj, "CompteGAchat", None)
|
|
||||||
if compte_achat_obj:
|
|
||||||
compte_achat_obj.Read()
|
|
||||||
resultat["compte_achat"] = getattr(
|
|
||||||
compte_achat_obj, "CG_Num", ""
|
|
||||||
).strip()
|
|
||||||
else:
|
|
||||||
resultat["compte_achat"] = ""
|
|
||||||
except:
|
|
||||||
resultat["compte_achat"] = ""
|
|
||||||
|
|
||||||
try:
|
|
||||||
compte_vente_obj = getattr(famille_obj, "CompteGVente", None)
|
|
||||||
if compte_vente_obj:
|
|
||||||
compte_vente_obj.Read()
|
|
||||||
resultat["compte_vente"] = getattr(
|
|
||||||
compte_vente_obj, "CG_Num", ""
|
|
||||||
).strip()
|
|
||||||
else:
|
|
||||||
resultat["compte_vente"] = ""
|
|
||||||
except:
|
|
||||||
resultat["compte_vente"] = ""
|
|
||||||
|
|
||||||
resultat["index_com"] = index_trouve
|
|
||||||
|
|
||||||
try:
|
|
||||||
date_creation = getattr(famille_obj, "cbCreation", None)
|
|
||||||
resultat["date_creation"] = (
|
|
||||||
str(date_creation) if date_creation else ""
|
|
||||||
)
|
|
||||||
except:
|
|
||||||
resultat["date_creation"] = ""
|
|
||||||
|
|
||||||
try:
|
|
||||||
date_modif = getattr(famille_obj, "cbModification", None)
|
|
||||||
resultat["date_modification"] = (
|
|
||||||
str(date_modif) if date_modif else ""
|
|
||||||
)
|
|
||||||
except:
|
|
||||||
resultat["date_modification"] = ""
|
|
||||||
|
|
||||||
try:
|
|
||||||
with self._get_sql_connection() as conn:
|
|
||||||
cursor = conn.cursor()
|
|
||||||
|
|
||||||
cursor.execute(
|
|
||||||
"""
|
|
||||||
SELECT COUNT(*)
|
|
||||||
FROM F_ARTICLE
|
|
||||||
WHERE FA_CodeFamille = ?
|
|
||||||
""",
|
|
||||||
(resultat["code"],),
|
|
||||||
)
|
|
||||||
|
|
||||||
row = cursor.fetchone()
|
|
||||||
if row:
|
|
||||||
resultat["nb_articles"] = row[0]
|
|
||||||
logger.info(
|
|
||||||
f" [STAT] {resultat['nb_articles']} article(s) dans cette famille"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f" [STAT] Impossible de compter les articles : {e}")
|
|
||||||
resultat["nb_articles"] = None
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
f"[FAMILLE] Lecture complète : {resultat['code']} - {resultat['intitule']}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return resultat
|
|
||||||
|
|
||||||
except ValueError as e:
|
|
||||||
logger.error(f"[FAMILLE] Erreur métier : {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"[FAMILLE] Erreur technique : {e}", exc_info=True)
|
|
||||||
|
|
||||||
error_message = str(e)
|
|
||||||
if self.cial:
|
|
||||||
try:
|
|
||||||
err = self.cial.CptaApplication.LastError
|
|
||||||
if err:
|
|
||||||
error_message = f"Erreur Sage: {err.Description}"
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
raise RuntimeError(f"Erreur technique Sage : {error_message}")
|
|
||||||
|
|
||||||
def creer_entree_stock(self, entree_data: Dict) -> Dict:
|
def creer_entree_stock(self, entree_data: Dict) -> Dict:
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue