diff --git a/email_queue.py b/email_queue.py index 1d59593..d235a72 100644 --- a/email_queue.py +++ b/email_queue.py @@ -74,6 +74,11 @@ def _register_sage_font(): return False +# ============================================================================ +# HELPERS POUR GESTION DES LOCKS SQLite +# ============================================================================ + + async def execute_with_retry( session, operation, @@ -81,6 +86,17 @@ async def execute_with_retry( base_delay: float = 0.1, max_delay: float = 2.0, ): + """ + Exécute une opération async avec retry en cas de lock SQLite. + + Args: + session: Session SQLAlchemy async + operation: Coroutine à exécuter + max_retries: Nombre max de tentatives + base_delay: Délai initial entre les tentatives (secondes) + max_delay: Délai maximum entre les tentatives + """ + import sqlite3 from sqlalchemy.exc import OperationalError last_exception = None @@ -107,7 +123,7 @@ async def execute_with_retry( else: # Autre erreur OperationalError, ne pas retry raise - except Exception: + except Exception as e: # Autres exceptions, ne pas retry raise @@ -148,6 +164,13 @@ class EmailQueue: pass def enqueue(self, email_log_id: str, delay_seconds: float = 0): + """ + Ajoute un email à la queue avec un délai optionnel. + + Args: + email_log_id: ID de l'email log + delay_seconds: Délai avant traitement (pour éviter les conflits) + """ if delay_seconds > 0: timer = threading.Timer(delay_seconds, lambda: self.queue.put(email_log_id)) timer.daemon = True @@ -377,9 +400,13 @@ class EmailQueue: societe_info = None try: societe_info = self.sage_client.lire_informations_societe() - logger.debug( - f"Infos société récupérées: {societe_info.raison_sociale if societe_info else 'N/A'}" - ) + # Log selon le type (dict ou objet) + if societe_info: + if isinstance(societe_info, dict): + raison = societe_info.get("raison_sociale", "N/A") + else: + raison = getattr(societe_info, "raison_sociale", "N/A") + logger.debug(f"Infos société récupérées: {raison}") except Exception as e: logger.warning(f"Impossible de récupérer les infos société: {e}") @@ -441,10 +468,17 @@ class SagePDFGenerator: self.use_sage_font = _sage_font_registered def _get_societe_field(self, field: str, default: str = "") -> str: - """Récupère un champ de la société avec fallback.""" + """Récupère un champ de la société avec fallback (supporte dict et objet).""" if self.societe_info is None: return default - return getattr(self.societe_info, field, default) or default + + # Support dict ou objet Pydantic + if isinstance(self.societe_info, dict): + value = self.societe_info.get(field) + else: + value = getattr(self.societe_info, field, None) + + return value if value is not None else default def _get_societe_name(self) -> str: """Retourne la raison sociale de la société.""" @@ -1029,10 +1063,16 @@ class SagePDFGenerator: # Forme juridique et capital si disponibles if self.societe_info: forme = self._get_societe_field("forme_juridique", "") - capital = getattr(self.societe_info, "capital", 0) - if forme and capital > 0: + capital = self._get_societe_field("capital", 0) + # Convertir en float si c'est une string + if isinstance(capital, str): + try: + capital = float(capital) + except (ValueError, TypeError): + capital = 0 + if forme and capital and float(capital) > 0: legal_parts.append( - f"{forme} au capital de {capital:,.0f} €".replace(",", " ") + f"{forme} au capital de {float(capital):,.0f} €".replace(",", " ") ) # Dessiner les informations légales