Refactored code readability, modularity and usability
This commit is contained in:
parent
0788f46bde
commit
5e4231e115
14 changed files with 232 additions and 42 deletions
17
main.py
17
main.py
|
|
@ -1,18 +1,12 @@
|
|||
from fastapi import FastAPI, HTTPException, Header, Depends, Query
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from pydantic import BaseModel, Field, validator, EmailStr, field_validator
|
||||
from typing import Optional, List, Dict
|
||||
from typing import Optional
|
||||
from datetime import datetime, date
|
||||
from decimal import Decimal
|
||||
from enum import Enum, IntEnum
|
||||
import uvicorn
|
||||
import logging
|
||||
import win32com.client
|
||||
import time
|
||||
from config import settings, validate_settings
|
||||
from sage_connector import SageConnector
|
||||
import pyodbc
|
||||
import os
|
||||
from schemas import (
|
||||
TiersListRequest,
|
||||
ContactCreateRequest,
|
||||
|
|
@ -25,12 +19,8 @@ from schemas import (
|
|||
FiltreRequest,
|
||||
ChampLibreRequest,
|
||||
CodeRequest,
|
||||
TransformationRequest,
|
||||
TypeDocument,
|
||||
DevisRequest,
|
||||
DocumentGetRequest,
|
||||
StatutRequest,
|
||||
TypeTiers,
|
||||
FournisseurCreateRequest,
|
||||
FournisseurUpdateGatewayRequest,
|
||||
AvoirCreateGatewayRequest,
|
||||
|
|
@ -43,7 +33,6 @@ from schemas import (
|
|||
LivraisonUpdateGatewayRequest,
|
||||
ArticleCreateRequest,
|
||||
ArticleUpdateGatewayRequest,
|
||||
MouvementStockLigneRequest,
|
||||
EntreeStockRequest,
|
||||
SortieStockRequest,
|
||||
FamilleCreate,
|
||||
|
|
@ -636,7 +625,9 @@ def livraisons_list(
|
|||
livraisons = sage.lister_toutes_livraisons_cache(filtre)
|
||||
|
||||
if statut is not None:
|
||||
livraisons = [ligne for ligne in livraisons if ligne.get("statut") == statut]
|
||||
livraisons = [
|
||||
ligne for ligne in livraisons if ligne.get("statut") == statut
|
||||
]
|
||||
|
||||
livraisons = livraisons[:limit]
|
||||
|
||||
|
|
|
|||
27
utils/__init__.py
Normal file
27
utils/__init__.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
from .enums import (
|
||||
TypeArticle,
|
||||
TypeCompta,
|
||||
TypeRessource,
|
||||
TypeTiers,
|
||||
TypeEmplacement,
|
||||
TypeFamille,
|
||||
NomenclatureType,
|
||||
SuiviStockType,
|
||||
normalize_enum_to_string,
|
||||
normalize_enum_to_int,
|
||||
normalize_string_field,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"TypeArticle",
|
||||
"TypeCompta",
|
||||
"TypeRessource",
|
||||
"TypeTiers",
|
||||
"TypeEmplacement",
|
||||
"TypeFamille",
|
||||
"NomenclatureType",
|
||||
"SuiviStockType",
|
||||
"normalize_enum_to_string",
|
||||
"normalize_enum_to_int",
|
||||
"normalize_string_field",
|
||||
]
|
||||
0
utils/articles/__init__.py
Normal file
0
utils/articles/__init__.py
Normal file
|
|
@ -2,6 +2,13 @@ from typing import Dict, List
|
|||
import win32com.client
|
||||
import logging
|
||||
from utils.functions.functions import _safe_strip
|
||||
from utils import (
|
||||
NomenclatureType,
|
||||
SuiviStockType,
|
||||
TypeArticle,
|
||||
normalize_enum_to_int,
|
||||
normalize_string_field,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -922,9 +929,14 @@ def _enrichir_conditionnements(articles: List[Dict], cursor) -> List[Dict]:
|
|||
|
||||
|
||||
def _mapper_article_depuis_row(row_data: Dict, colonnes_config: Dict) -> Dict:
|
||||
"""
|
||||
Mappe les données brutes de la DB vers le format ArticleResponse
|
||||
avec normalisation des types et génération des libellés
|
||||
"""
|
||||
article = {}
|
||||
|
||||
def get_val(sql_col, default=None, convert_type=None):
|
||||
"""Récupère et convertit une valeur depuis row_data"""
|
||||
val = row_data.get(sql_col, default)
|
||||
if val is None:
|
||||
return default
|
||||
|
|
@ -940,6 +952,7 @@ def _mapper_article_depuis_row(row_data: Dict, colonnes_config: Dict) -> Dict:
|
|||
|
||||
return val
|
||||
|
||||
# === CHAMPS DE BASE ===
|
||||
article["reference"] = get_val("AR_Ref", convert_type=str)
|
||||
article["designation"] = get_val("AR_Design", convert_type=str)
|
||||
article["code_ean"] = get_val("AR_CodeBarre", convert_type=str)
|
||||
|
|
@ -947,6 +960,7 @@ def _mapper_article_depuis_row(row_data: Dict, colonnes_config: Dict) -> Dict:
|
|||
article["edi_code"] = get_val("AR_EdiCode", convert_type=str)
|
||||
article["raccourci"] = get_val("AR_Raccourci", convert_type=str)
|
||||
|
||||
# === PRIX ===
|
||||
article["prix_vente"] = get_val("AR_PrixVen", 0.0, float)
|
||||
article["prix_achat"] = get_val("AR_PrixAch", 0.0, float)
|
||||
article["coef"] = get_val("AR_Coef", 0.0, float)
|
||||
|
|
@ -960,40 +974,60 @@ def _mapper_article_depuis_row(row_data: Dict, colonnes_config: Dict) -> Dict:
|
|||
|
||||
article["cout_standard"] = get_val("AR_CoutStd", 0.0, float)
|
||||
|
||||
article["unite_vente"] = get_val("AR_UniteVen", convert_type=str)
|
||||
article["unite_poids"] = get_val("AR_UnitePoids", convert_type=str)
|
||||
# === UNITÉS ET POIDS (avec normalisation string) ===
|
||||
article["unite_vente"] = normalize_string_field(get_val("AR_UniteVen"))
|
||||
article["unite_poids"] = normalize_string_field(get_val("AR_UnitePoids"))
|
||||
article["poids_net"] = get_val("AR_PoidsNet", 0.0, float)
|
||||
article["poids_brut"] = get_val("AR_PoidsBrut", 0.0, float)
|
||||
|
||||
article["gamme_1"] = get_val("AR_Gamme1", convert_type=str)
|
||||
article["gamme_2"] = get_val("AR_Gamme2", convert_type=str)
|
||||
# === GAMMES (avec normalisation string) ===
|
||||
article["gamme_1"] = normalize_string_field(get_val("AR_Gamme1"))
|
||||
article["gamme_2"] = normalize_string_field(get_val("AR_Gamme2"))
|
||||
|
||||
# === TYPE ARTICLE (avec libellé) ===
|
||||
type_val = get_val("AR_Type", 0, int)
|
||||
article["type_article"] = type_val
|
||||
article["type_article_libelle"] = _get_type_article_libelle(type_val)
|
||||
article["type_article_libelle"] = TypeArticle.get_label(type_val)
|
||||
|
||||
# === FAMILLE ===
|
||||
article["famille_code"] = get_val("FA_CodeFamille", convert_type=str)
|
||||
|
||||
# === NATURE ET GARANTIE ===
|
||||
article["nature"] = get_val("AR_Nature", 0, int)
|
||||
article["garantie"] = get_val("AR_Garantie", 0, int)
|
||||
article["code_fiscal"] = get_val("AR_CodeFiscal", convert_type=str)
|
||||
article["pays"] = get_val("AR_Pays", convert_type=str)
|
||||
article["code_fiscal"] = normalize_string_field(get_val("AR_CodeFiscal"))
|
||||
article["pays"] = normalize_string_field(get_val("AR_Pays"))
|
||||
|
||||
# === FOURNISSEUR ===
|
||||
article["fournisseur_principal"] = get_val("CO_No", 0, int)
|
||||
article["conditionnement"] = get_val("AR_Condition", convert_type=str)
|
||||
|
||||
# === CONDITIONNEMENT (avec normalisation string) ===
|
||||
article["conditionnement"] = normalize_string_field(get_val("AR_Condition"))
|
||||
article["nb_colis"] = get_val("AR_NbColis", 0, int)
|
||||
article["prevision"] = get_val("AR_Prevision", False, bool)
|
||||
|
||||
article["suivi_stock"] = get_val("AR_SuiviStock", False, bool)
|
||||
article["nomenclature"] = get_val("AR_Nomencl", False, bool)
|
||||
# === SUIVI STOCK (avec libellé) ===
|
||||
suivi_stock_val = normalize_enum_to_int(get_val("AR_SuiviStock"))
|
||||
article["suivi_stock"] = suivi_stock_val
|
||||
article["suivi_stock_libelle"] = SuiviStockType.get_label(suivi_stock_val)
|
||||
|
||||
# === NOMENCLATURE (avec libellé) ===
|
||||
nomenclature_val = normalize_enum_to_int(get_val("AR_Nomencl"))
|
||||
article["nomenclature"] = nomenclature_val
|
||||
article["nomenclature_libelle"] = NomenclatureType.get_label(nomenclature_val)
|
||||
|
||||
article["qte_composant"] = get_val("AR_QteComp", 0.0, float)
|
||||
article["qte_operatoire"] = get_val("AR_QteOperatoire", 0.0, float)
|
||||
|
||||
# === STATUT ARTICLE ===
|
||||
sommeil = get_val("AR_Sommeil", 0, int)
|
||||
article["est_actif"] = sommeil == 0
|
||||
article["en_sommeil"] = sommeil == 1
|
||||
article["article_substitut"] = get_val("AR_Substitut", convert_type=str)
|
||||
article["article_substitut"] = normalize_string_field(get_val("AR_Substitut"))
|
||||
article["soumis_escompte"] = get_val("AR_Escompte", False, bool)
|
||||
article["delai"] = get_val("AR_Delai", 0, int)
|
||||
|
||||
# === STATISTIQUES ===
|
||||
article["stat_01"] = get_val("AR_Stat01", convert_type=str)
|
||||
article["stat_02"] = get_val("AR_Stat02", convert_type=str)
|
||||
article["stat_03"] = get_val("AR_Stat03", convert_type=str)
|
||||
|
|
@ -1001,14 +1035,17 @@ def _mapper_article_depuis_row(row_data: Dict, colonnes_config: Dict) -> Dict:
|
|||
article["stat_05"] = get_val("AR_Stat05", convert_type=str)
|
||||
article["hors_statistique"] = get_val("AR_HorsStat", False, bool)
|
||||
|
||||
# === CATÉGORIES COMPTABLES ===
|
||||
article["categorie_1"] = get_val("CL_No1", 0, int)
|
||||
article["categorie_2"] = get_val("CL_No2", 0, int)
|
||||
article["categorie_3"] = get_val("CL_No3", 0, int)
|
||||
article["categorie_4"] = get_val("CL_No4", 0, int)
|
||||
|
||||
# === DATE MODIFICATION ===
|
||||
date_modif = get_val("AR_DateModif")
|
||||
article["date_modification"] = str(date_modif) if date_modif else None
|
||||
|
||||
# === PARAMÈTRES DE VENTE ===
|
||||
article["vente_debit"] = get_val("AR_VteDebit", False, bool)
|
||||
article["non_imprimable"] = get_val("AR_NotImp", False, bool)
|
||||
article["transfere"] = get_val("AR_Transfere", False, bool)
|
||||
|
|
@ -1021,17 +1058,20 @@ def _mapper_article_depuis_row(row_data: Dict, colonnes_config: Dict) -> Dict:
|
|||
article["sous_traitance"] = get_val("AR_SousTraitance", False, bool)
|
||||
article["criticite"] = get_val("AR_Criticite", 0, int)
|
||||
|
||||
article["reprise_code_defaut"] = get_val("RP_CodeDefaut", convert_type=str)
|
||||
# === PARAMÈTRES DE PRODUCTION ===
|
||||
article["reprise_code_defaut"] = normalize_string_field(get_val("RP_CodeDefaut"))
|
||||
article["delai_fabrication"] = get_val("AR_DelaiFabrication", 0, int)
|
||||
article["delai_peremption"] = get_val("AR_DelaiPeremption", 0, int)
|
||||
article["delai_securite"] = get_val("AR_DelaiSecurite", 0, int)
|
||||
article["type_lancement"] = get_val("AR_TypeLancement", 0, int)
|
||||
article["cycle"] = get_val("AR_Cycle", 1, int)
|
||||
|
||||
# === MÉDIA ET LANGUES ===
|
||||
article["photo"] = get_val("AR_Photo", convert_type=str)
|
||||
article["langue_1"] = get_val("AR_Langue1", convert_type=str)
|
||||
article["langue_2"] = get_val("AR_Langue2", convert_type=str)
|
||||
|
||||
# === FRAIS ===
|
||||
article["frais_01_denomination"] = get_val(
|
||||
"AR_Frais01FR_Denomination", convert_type=str
|
||||
)
|
||||
|
|
@ -1042,6 +1082,7 @@ def _mapper_article_depuis_row(row_data: Dict, colonnes_config: Dict) -> Dict:
|
|||
"AR_Frais03FR_Denomination", convert_type=str
|
||||
)
|
||||
|
||||
# === CHAMPS PERSONNALISÉS ===
|
||||
article["marque_commerciale"] = get_val("Marque commerciale", convert_type=str)
|
||||
|
||||
objectif_val = get_val("Objectif / Qtés vendues")
|
||||
|
|
@ -1066,6 +1107,7 @@ def _mapper_article_depuis_row(row_data: Dict, colonnes_config: Dict) -> Dict:
|
|||
article["interdire_commande"] = get_val("AR_InterdireCommande", False, bool)
|
||||
article["exclure"] = get_val("AR_Exclure", False, bool)
|
||||
|
||||
# === INITIALISATION DES CHAMPS DE STOCK (remplis par enrichissement) ===
|
||||
article["stock_reel"] = 0.0
|
||||
article["stock_mini"] = 0.0
|
||||
article["stock_maxi"] = 0.0
|
||||
|
|
@ -1073,6 +1115,7 @@ def _mapper_article_depuis_row(row_data: Dict, colonnes_config: Dict) -> Dict:
|
|||
article["stock_commande"] = 0.0
|
||||
article["stock_disponible"] = 0.0
|
||||
|
||||
# === INITIALISATION DES CHAMPS DE FAMILLE (remplis par enrichissement) ===
|
||||
article["famille_libelle"] = None
|
||||
article["famille_type"] = None
|
||||
article["famille_unite_vente"] = None
|
||||
|
|
@ -1089,6 +1132,7 @@ def _mapper_article_depuis_row(row_data: Dict, colonnes_config: Dict) -> Dict:
|
|||
article["famille_hors_stat"] = None
|
||||
article["famille_pays"] = None
|
||||
|
||||
# === INITIALISATION DES CHAMPS FOURNISSEUR/TVA (remplis par enrichissement) ===
|
||||
article["fournisseur_nom"] = None
|
||||
article["tva_code"] = None
|
||||
article["tva_taux"] = None
|
||||
|
|
|
|||
0
utils/documents/__init__.py
Normal file
0
utils/documents/__init__.py
Normal file
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Dict, List, Optional, Any
|
||||
from typing import Dict
|
||||
|
||||
|
||||
def _extraire_infos_devis(doc, numero: str, champs_modifies: list) -> Dict:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import win32com.client
|
||||
from typing import Optional
|
||||
import logging
|
||||
|
||||
from utils.functions.functions import (
|
||||
|
|
|
|||
129
utils/enums.py
Normal file
129
utils/enums.py
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
from enum import IntEnum
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class SuiviStockType(IntEnum):
|
||||
AUCUN = 0
|
||||
CMUP = 1
|
||||
FIFO_LIFO = 2
|
||||
SERIALISE = 3
|
||||
|
||||
@classmethod
|
||||
def get_label(cls, value: Optional[int]) -> Optional[str]:
|
||||
labels = {0: "Aucun", 1: "CMUP", 2: "FIFO/LIFO", 3: "Sérialisé"}
|
||||
return labels.get(value) if value is not None else None
|
||||
|
||||
|
||||
class NomenclatureType(IntEnum):
|
||||
NON = 0
|
||||
FABRICATION = 1
|
||||
COMMERCIALE = 2
|
||||
|
||||
@classmethod
|
||||
def get_label(cls, value: Optional[int]) -> Optional[str]:
|
||||
labels = {0: "Non", 1: "Fabrication", 2: "Commerciale/Composé"}
|
||||
return labels.get(value) if value is not None else None
|
||||
|
||||
|
||||
class TypeArticle(IntEnum):
|
||||
ARTICLE = 0
|
||||
PRESTATION = 1
|
||||
DIVERS = 2
|
||||
NOMENCLATURE = 3
|
||||
|
||||
@classmethod
|
||||
def get_label(cls, value: Optional[int]) -> Optional[str]:
|
||||
labels = {
|
||||
0: "Article",
|
||||
1: "Prestation de service",
|
||||
2: "Divers / Frais",
|
||||
3: "Nomenclature",
|
||||
}
|
||||
return labels.get(value) if value is not None else None
|
||||
|
||||
|
||||
class TypeFamille(IntEnum):
|
||||
DETAIL = 0
|
||||
TOTAL = 1
|
||||
|
||||
@classmethod
|
||||
def get_label(cls, value: Optional[int]) -> Optional[str]:
|
||||
labels = {0: "Détail", 1: "Total"}
|
||||
return labels.get(value) if value is not None else None
|
||||
|
||||
|
||||
class TypeCompta(IntEnum):
|
||||
VENTE = 0
|
||||
ACHAT = 1
|
||||
STOCK = 2
|
||||
|
||||
@classmethod
|
||||
def get_label(cls, value: Optional[int]) -> Optional[str]:
|
||||
labels = {0: "Vente", 1: "Achat", 2: "Stock"}
|
||||
return labels.get(value) if value is not None else None
|
||||
|
||||
|
||||
class TypeRessource(IntEnum):
|
||||
MAIN_OEUVRE = 0
|
||||
MACHINE = 1
|
||||
SOUS_TRAITANCE = 2
|
||||
|
||||
@classmethod
|
||||
def get_label(cls, value: Optional[int]) -> Optional[str]:
|
||||
labels = {0: "Main d'œuvre", 1: "Machine", 2: "Sous-traitance"}
|
||||
return labels.get(value) if value is not None else None
|
||||
|
||||
|
||||
class TypeTiers(IntEnum):
|
||||
CLIENT = 0
|
||||
FOURNISSEUR = 1
|
||||
SALARIE = 2
|
||||
AUTRE = 3
|
||||
|
||||
@classmethod
|
||||
def get_label(cls, value: Optional[int]) -> Optional[str]:
|
||||
labels = {0: "Client", 1: "Fournisseur", 2: "Salarié", 3: "Autre"}
|
||||
return labels.get(value) if value is not None else None
|
||||
|
||||
|
||||
class TypeEmplacement(IntEnum):
|
||||
NORMAL = 0
|
||||
QUARANTAINE = 1
|
||||
REBUT = 2
|
||||
|
||||
@classmethod
|
||||
def get_label(cls, value: Optional[int]) -> Optional[str]:
|
||||
labels = {0: "Normal", 1: "Quarantaine", 2: "Rebut"}
|
||||
return labels.get(value) if value is not None else None
|
||||
|
||||
|
||||
def normalize_enum_to_string(value, default="0") -> Optional[str]:
|
||||
if value is None:
|
||||
return None
|
||||
if value == 0:
|
||||
return None
|
||||
return str(value)
|
||||
|
||||
|
||||
def normalize_enum_to_int(value, default=0) -> Optional[int]:
|
||||
if value is None:
|
||||
return None
|
||||
try:
|
||||
return int(value)
|
||||
except (ValueError, TypeError):
|
||||
return default
|
||||
|
||||
|
||||
def normalize_string_field(value) -> Optional[str]:
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, int):
|
||||
if value == 0:
|
||||
return None
|
||||
return str(value)
|
||||
if isinstance(value, str):
|
||||
stripped = value.strip()
|
||||
if stripped in ("", "0"):
|
||||
return None
|
||||
return stripped
|
||||
return str(value)
|
||||
0
utils/functions/__init__.py
Normal file
0
utils/functions/__init__.py
Normal file
|
|
@ -1,5 +1,5 @@
|
|||
from typing import Optional
|
||||
from datetime import datetime, timedelta, date
|
||||
from datetime import datetime, date
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Dict, List, Optional, Any
|
||||
from typing import Dict
|
||||
import logging
|
||||
from utils.functions.functions import _safe_strip
|
||||
|
||||
|
|
|
|||
0
utils/tiers/clients/__init__.py
Normal file
0
utils/tiers/clients/__init__.py
Normal file
0
utils/tiers/contacts/__init__.py
Normal file
0
utils/tiers/contacts/__init__.py
Normal file
0
utils/tiers/fournisseurs/__init__.py
Normal file
0
utils/tiers/fournisseurs/__init__.py
Normal file
Loading…
Reference in a new issue