Added better article's data handling and more enriched
This commit is contained in:
parent
c32a9171a5
commit
a0f9eeedec
4 changed files with 431 additions and 486 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -1,28 +1,66 @@
|
||||||
|
from pydantic import BaseModel, Field, validator
|
||||||
from pydantic import BaseModel, Field, validator, EmailStr, field_validator
|
|
||||||
from typing import Optional, List, Dict
|
from typing import Optional, List, Dict
|
||||||
from enum import Enum, IntEnum
|
from datetime import date
|
||||||
from datetime import datetime, date
|
|
||||||
|
|
||||||
class ArticleCreate(BaseModel):
|
class ArticleCreate(BaseModel):
|
||||||
reference: str = Field(..., description="Référence article (max 18 car)")
|
reference: str = Field(..., description="Référence article (max 18 car)")
|
||||||
designation: str = Field(..., description="Désignation (max 69 car)")
|
designation: str = Field(..., description="Désignation (max 69 car)")
|
||||||
|
|
||||||
famille: Optional[str] = Field(None, description="Code famille")
|
famille: Optional[str] = Field(None, description="Code famille")
|
||||||
|
|
||||||
prix_vente: Optional[float] = Field(None, ge=0, description="Prix vente HT")
|
prix_vente: Optional[float] = Field(None, ge=0, description="Prix vente HT")
|
||||||
prix_achat: Optional[float] = Field(None, ge=0, description="Prix achat HT")
|
prix_achat: Optional[float] = Field(None, ge=0, description="Prix achat HT")
|
||||||
|
coef: Optional[float] = Field(None, ge=0, description="Coefficient")
|
||||||
|
|
||||||
stock_reel: Optional[float] = Field(None, ge=0, description="Stock initial")
|
stock_reel: Optional[float] = Field(None, ge=0, description="Stock initial")
|
||||||
stock_mini: Optional[float] = Field(None, ge=0, description="Stock minimum")
|
stock_mini: Optional[float] = Field(None, ge=0, description="Stock minimum")
|
||||||
|
stock_maxi: Optional[float] = Field(None, ge=0, description="Stock maximum")
|
||||||
|
|
||||||
code_ean: Optional[str] = Field(None, description="Code-barres EAN")
|
code_ean: Optional[str] = Field(None, description="Code-barres EAN")
|
||||||
unite_vente: Optional[str] = Field("UN", description="Unité de vente")
|
unite_vente: Optional[str] = Field("UN", description="Unité de vente")
|
||||||
tva_code: Optional[str] = Field(None, description="Code TVA")
|
tva_code: Optional[str] = Field(None, description="Code TVA")
|
||||||
|
code_fiscal: Optional[str] = Field(None, description="Code fiscal")
|
||||||
|
|
||||||
description: Optional[str] = Field(None, description="Description/Commentaire")
|
description: Optional[str] = Field(None, description="Description/Commentaire")
|
||||||
|
|
||||||
|
pays: Optional[str] = Field(None, description="Pays d'origine")
|
||||||
|
garantie: Optional[int] = Field(None, ge=0, description="Garantie en mois")
|
||||||
|
delai: Optional[int] = Field(None, ge=0, description="Délai livraison jours")
|
||||||
|
|
||||||
|
poids_net: Optional[float] = Field(None, ge=0, description="Poids net kg")
|
||||||
|
poids_brut: Optional[float] = Field(None, ge=0, description="Poids brut kg")
|
||||||
|
|
||||||
|
stat_01: Optional[str] = Field(None, description="Statistique 1")
|
||||||
|
stat_02: Optional[str] = Field(None, description="Statistique 2")
|
||||||
|
stat_03: Optional[str] = Field(None, description="Statistique 3")
|
||||||
|
stat_04: Optional[str] = Field(None, description="Statistique 4")
|
||||||
|
stat_05: Optional[str] = Field(None, description="Statistique 5")
|
||||||
|
|
||||||
|
soumis_escompte: Optional[bool] = Field(None, description="Soumis à escompte")
|
||||||
|
publie: Optional[bool] = Field(None, description="Publié web/catalogue")
|
||||||
|
en_sommeil: Optional[bool] = Field(None, description="Article en sommeil")
|
||||||
|
|
||||||
|
|
||||||
class ArticleUpdate(BaseModel):
|
class ArticleUpdate(BaseModel):
|
||||||
"""Modèle pour modification article côté gateway"""
|
reference: str = Field(..., description="Référence de l'article à modifier")
|
||||||
|
article_data: Dict = Field(..., description="Données à modifier")
|
||||||
|
|
||||||
reference: str
|
class Config:
|
||||||
article_data: Dict
|
json_schema_extra = {
|
||||||
|
"example": {
|
||||||
|
"reference": "ART001",
|
||||||
|
"article_data": {
|
||||||
|
"designation": "Nouvelle désignation",
|
||||||
|
"prix_vente": 150.0,
|
||||||
|
"famille": "FAM01",
|
||||||
|
"stock_reel": 100.0,
|
||||||
|
"stock_mini": 10.0,
|
||||||
|
"code_fiscal": "V19",
|
||||||
|
"garantie": 24,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class MouvementStockLigneRequest(BaseModel):
|
class MouvementStockLigneRequest(BaseModel):
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,12 @@ from .enums import (
|
||||||
normalize_string_field,
|
normalize_string_field,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from .article_fields import (
|
||||||
|
valider_donnees_creation,
|
||||||
|
mapper_champ_api_vers_sage,
|
||||||
|
CHAMPS_STOCK_INITIAL,
|
||||||
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"TypeArticle",
|
"TypeArticle",
|
||||||
"TypeCompta",
|
"TypeCompta",
|
||||||
|
|
@ -24,4 +30,7 @@ __all__ = [
|
||||||
"normalize_enum_to_string",
|
"normalize_enum_to_string",
|
||||||
"normalize_enum_to_int",
|
"normalize_enum_to_int",
|
||||||
"normalize_string_field",
|
"normalize_string_field",
|
||||||
|
"valider_donnees_creation",
|
||||||
|
"mapper_champ_api_vers_sage",
|
||||||
|
"CHAMPS_STOCK_INITIAL",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
170
utils/article_fields.py
Normal file
170
utils/article_fields.py
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
CHAMPS_ASSIGNABLES_CREATION = {
|
||||||
|
"AR_Design": {"max_length": 69, "required": True, "description": "Désignation"},
|
||||||
|
"AR_PrixVen": {"type": float, "min": 0, "description": "Prix de vente HT"},
|
||||||
|
"AR_PrixAch": {"type": float, "min": 0, "description": "Prix achat HT"},
|
||||||
|
"AR_PrixAchat": {"type": float, "min": 0, "description": "Prix achat HT (alias)"},
|
||||||
|
"AR_CodeBarre": {"max_length": 13, "description": "Code-barres EAN"},
|
||||||
|
"AR_Commentaire": {"max_length": 255, "description": "Description/Commentaire"},
|
||||||
|
"AR_UniteVen": {"max_length": 10, "description": "Unité de vente"},
|
||||||
|
"AR_CodeFiscal": {"max_length": 10, "description": "Code fiscal/TVA"},
|
||||||
|
"AR_Pays": {"max_length": 3, "description": "Pays d'origine"},
|
||||||
|
"AR_Garantie": {"type": int, "min": 0, "description": "Garantie en mois"},
|
||||||
|
"AR_Delai": {"type": int, "min": 0, "description": "Délai livraison jours"},
|
||||||
|
"AR_Coef": {"type": float, "min": 0, "description": "Coefficient"},
|
||||||
|
"AR_PoidsNet": {"type": float, "min": 0, "description": "Poids net kg"},
|
||||||
|
"AR_PoidsBrut": {"type": float, "min": 0, "description": "Poids brut kg"},
|
||||||
|
"AR_Stat01": {"max_length": 20, "description": "Statistique 1"},
|
||||||
|
"AR_Stat02": {"max_length": 20, "description": "Statistique 2"},
|
||||||
|
"AR_Stat03": {"max_length": 20, "description": "Statistique 3"},
|
||||||
|
"AR_Stat04": {"max_length": 20, "description": "Statistique 4"},
|
||||||
|
"AR_Stat05": {"max_length": 20, "description": "Statistique 5"},
|
||||||
|
"AR_Escompte": {"type": bool, "description": "Soumis à escompte"},
|
||||||
|
"AR_Publie": {"type": bool, "description": "Publié web/catalogue"},
|
||||||
|
"AR_Sommeil": {"type": int, "values": [0, 1], "description": "Actif/Sommeil"},
|
||||||
|
}
|
||||||
|
|
||||||
|
CHAMPS_ASSIGNABLES_MODIFICATION = {
|
||||||
|
**CHAMPS_ASSIGNABLES_CREATION,
|
||||||
|
"AR_Stock": {"type": float, "min": 0, "description": "Stock réel"},
|
||||||
|
"AR_StockMini": {"type": float, "min": 0, "description": "Stock minimum"},
|
||||||
|
"AR_StockMaxi": {"type": float, "min": 0, "description": "Stock maximum"},
|
||||||
|
}
|
||||||
|
|
||||||
|
CHAMPS_OBJETS_SPECIAUX = {
|
||||||
|
"Unite": {"description": "Unité de vente (objet)", "copie_modele": True},
|
||||||
|
"Famille": {"description": "Famille article (objet)", "validation_sql": True},
|
||||||
|
}
|
||||||
|
|
||||||
|
CHAMPS_STOCK_INITIAL = {
|
||||||
|
"stock_reel": {"type": float, "min": 0, "description": "Stock initial"},
|
||||||
|
"stock_mini": {"type": float, "min": 0, "description": "Stock minimum"},
|
||||||
|
"stock_maxi": {"type": float, "min": 0, "description": "Stock maximum"},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def valider_champ(
|
||||||
|
nom_champ: str, valeur: Any, config: Dict
|
||||||
|
) -> tuple[bool, Optional[str]]:
|
||||||
|
if valeur is None:
|
||||||
|
if config.get("required"):
|
||||||
|
return False, f"Le champ {nom_champ} est obligatoire"
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
if "type" in config:
|
||||||
|
expected_type = config["type"]
|
||||||
|
try:
|
||||||
|
if expected_type is float:
|
||||||
|
valeur = float(valeur)
|
||||||
|
elif expected_type is int:
|
||||||
|
valeur = int(valeur)
|
||||||
|
elif expected_type is bool:
|
||||||
|
valeur = bool(valeur)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return (
|
||||||
|
False,
|
||||||
|
f"Le champ {nom_champ} doit être de type {expected_type.__name__}",
|
||||||
|
)
|
||||||
|
|
||||||
|
if "min" in config:
|
||||||
|
if isinstance(valeur, (int, float)) and valeur < config["min"]:
|
||||||
|
return False, f"Le champ {nom_champ} doit être >= {config['min']}"
|
||||||
|
|
||||||
|
if "max_length" in config:
|
||||||
|
if isinstance(valeur, str) and len(valeur) > config["max_length"]:
|
||||||
|
return (
|
||||||
|
False,
|
||||||
|
f"Le champ {nom_champ} ne peut dépasser {config['max_length']} caractères",
|
||||||
|
)
|
||||||
|
|
||||||
|
if "values" in config:
|
||||||
|
if valeur not in config["values"]:
|
||||||
|
return False, f"Le champ {nom_champ} doit être parmi {config['values']}"
|
||||||
|
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
|
||||||
|
def valider_donnees_creation(data: Dict) -> tuple[bool, Optional[str]]:
|
||||||
|
if "reference" not in data or not data["reference"]:
|
||||||
|
return False, "Le champ 'reference' est obligatoire"
|
||||||
|
|
||||||
|
if len(str(data["reference"])) > 18:
|
||||||
|
return False, "La référence ne peut dépasser 18 caractères"
|
||||||
|
|
||||||
|
if "designation" not in data or not data["designation"]:
|
||||||
|
return False, "Le champ 'designation' est obligatoire"
|
||||||
|
|
||||||
|
for champ, valeur in data.items():
|
||||||
|
if champ in CHAMPS_ASSIGNABLES_CREATION:
|
||||||
|
valide, erreur = valider_champ(
|
||||||
|
champ, valeur, CHAMPS_ASSIGNABLES_CREATION[champ]
|
||||||
|
)
|
||||||
|
if not valide:
|
||||||
|
return False, erreur
|
||||||
|
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
|
||||||
|
def valider_donnees_modification(data: Dict) -> tuple[bool, Optional[str]]:
|
||||||
|
if not data:
|
||||||
|
return False, "Aucun champ à modifier"
|
||||||
|
|
||||||
|
for champ, valeur in data.items():
|
||||||
|
if champ in ["famille", "stock_reel", "stock_mini", "stock_maxi"]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if champ in CHAMPS_ASSIGNABLES_MODIFICATION:
|
||||||
|
valide, erreur = valider_champ(
|
||||||
|
champ, valeur, CHAMPS_ASSIGNABLES_MODIFICATION[champ]
|
||||||
|
)
|
||||||
|
if not valide:
|
||||||
|
return False, erreur
|
||||||
|
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
|
||||||
|
def mapper_champ_api_vers_sage(champ_api: str) -> Optional[str]:
|
||||||
|
mapping = {
|
||||||
|
"designation": "AR_Design",
|
||||||
|
"prix_vente": "AR_PrixVen",
|
||||||
|
"prix_achat": "AR_PrixAch",
|
||||||
|
"code_ean": "AR_CodeBarre",
|
||||||
|
"code_barre": "AR_CodeBarre",
|
||||||
|
"description": "AR_Commentaire",
|
||||||
|
"unite_vente": "AR_UniteVen",
|
||||||
|
"code_fiscal": "AR_CodeFiscal",
|
||||||
|
"tva_code": "AR_CodeFiscal",
|
||||||
|
"pays": "AR_Pays",
|
||||||
|
"garantie": "AR_Garantie",
|
||||||
|
"delai": "AR_Delai",
|
||||||
|
"coef": "AR_Coef",
|
||||||
|
"coefficient": "AR_Coef",
|
||||||
|
"poids_net": "AR_PoidsNet",
|
||||||
|
"poids_brut": "AR_PoidsBrut",
|
||||||
|
"stat_01": "AR_Stat01",
|
||||||
|
"stat_02": "AR_Stat02",
|
||||||
|
"stat_03": "AR_Stat03",
|
||||||
|
"stat_04": "AR_Stat04",
|
||||||
|
"stat_05": "AR_Stat05",
|
||||||
|
"soumis_escompte": "AR_Escompte",
|
||||||
|
"publie": "AR_Publie",
|
||||||
|
"en_sommeil": "AR_Sommeil",
|
||||||
|
"stock_reel": "AR_Stock",
|
||||||
|
"stock_mini": "AR_StockMini",
|
||||||
|
"stock_maxi": "AR_StockMaxi",
|
||||||
|
}
|
||||||
|
return mapping.get(champ_api, champ_api)
|
||||||
|
|
||||||
|
|
||||||
|
def obtenir_champs_assignables() -> Dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"creation": list(CHAMPS_ASSIGNABLES_CREATION.keys()),
|
||||||
|
"modification": list(CHAMPS_ASSIGNABLES_MODIFICATION.keys()),
|
||||||
|
"objets_speciaux": list(CHAMPS_OBJETS_SPECIAUX.keys()),
|
||||||
|
"stock_initial": list(CHAMPS_STOCK_INITIAL.keys()),
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue