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()), }