feat(pdf): add dynamic company info to PDF generator

This commit is contained in:
Fanilo-Nantenaina 2026-01-13 17:37:23 +03:00
parent 08665f15dd
commit 3f1dce918d

View file

@ -20,7 +20,6 @@ from io import BytesIO
from reportlab.lib.units import mm from reportlab.lib.units import mm
from reportlab.lib.colors import HexColor, Color from reportlab.lib.colors import HexColor, Color
from PIL import Image from PIL import Image
from sqlalchemy.exc import OperationalError
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -82,6 +81,8 @@ async def execute_with_retry(
base_delay: float = 0.1, base_delay: float = 0.1,
max_delay: float = 2.0, max_delay: float = 2.0,
): ):
from sqlalchemy.exc import OperationalError
last_exception = None last_exception = None
for attempt in range(max_retries): for attempt in range(max_retries):
@ -106,7 +107,7 @@ async def execute_with_retry(
else: else:
# Autre erreur OperationalError, ne pas retry # Autre erreur OperationalError, ne pas retry
raise raise
except Exception as e: except Exception:
# Autres exceptions, ne pas retry # Autres exceptions, ne pas retry
raise raise
@ -372,8 +373,18 @@ class EmailQueue:
if not doc: if not doc:
raise Exception(f"Document {doc_id} introuvable") raise Exception(f"Document {doc_id} introuvable")
# Récupérer les informations de la société émettrice
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'}"
)
except Exception as e:
logger.warning(f"Impossible de récupérer les infos société: {e}")
# Générer le PDF avec le nouveau générateur # Générer le PDF avec le nouveau générateur
generator = SagePDFGenerator(doc, type_doc) generator = SagePDFGenerator(doc, type_doc, societe_info=societe_info)
return generator.generate() return generator.generate()
@ -392,10 +403,11 @@ class SagePDFGenerator:
GRAY_800 = HexColor("#1F2937") GRAY_800 = HexColor("#1F2937")
GRAY_900 = HexColor("#111827") GRAY_900 = HexColor("#111827")
COMPANY_NAME = "Bijou S.A.S" # Valeurs par défaut (fallback si société non disponible)
COMPANY_ADDRESS_1 = "123 Avenue de la République" DEFAULT_COMPANY_NAME = "Entreprise"
COMPANY_ADDRESS_2 = "75011 Paris, France" DEFAULT_COMPANY_ADDRESS = ""
COMPANY_EMAIL = "contact@bijou.com" DEFAULT_COMPANY_CITY = ""
DEFAULT_COMPANY_EMAIL = ""
# Labels des types de documents # Labels des types de documents
TYPE_LABELS = { TYPE_LABELS = {
@ -406,10 +418,11 @@ class SagePDFGenerator:
60: "Facture", 60: "Facture",
} }
def __init__(self, doc_data: dict, type_doc: int): def __init__(self, doc_data: dict, type_doc: int, societe_info=None):
self.doc = doc_data self.doc = doc_data
self.type_doc = type_doc self.type_doc = type_doc
self.type_label = self.TYPE_LABELS.get(type_doc, "Document") self.type_label = self.TYPE_LABELS.get(type_doc, "Document")
self.societe_info = societe_info
# Configuration de la page # Configuration de la page
self.page_width, self.page_height = A4 self.page_width, self.page_height = A4
@ -427,6 +440,64 @@ class SagePDFGenerator:
_register_sage_font() _register_sage_font()
self.use_sage_font = _sage_font_registered 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."""
if self.societe_info is None:
return default
return getattr(self.societe_info, field, default) or default
def _get_societe_name(self) -> str:
"""Retourne la raison sociale de la société."""
return self._get_societe_field("raison_sociale", self.DEFAULT_COMPANY_NAME)
def _get_societe_address_line1(self) -> str:
"""Retourne la première ligne d'adresse."""
adresse = self._get_societe_field("adresse", "")
complement = self._get_societe_field("complement_adresse", "")
if adresse and complement:
return f"{adresse}, {complement}"
return adresse or complement or self.DEFAULT_COMPANY_ADDRESS
def _get_societe_address_line2(self) -> str:
"""Retourne la deuxième ligne d'adresse (CP + Ville + Pays)."""
cp = self._get_societe_field("code_postal", "")
ville = self._get_societe_field("ville", "")
pays = self._get_societe_field("pays", "")
parts = []
if cp:
parts.append(cp)
if ville:
parts.append(ville)
city_line = " ".join(parts)
if pays and pays.lower() not in ["france", "fr"]:
city_line = f"{city_line}, {pays}" if city_line else pays
return city_line or self.DEFAULT_COMPANY_CITY
def _get_societe_email(self) -> str:
"""Retourne l'email de la société."""
# Priorité à email_societe, puis email
email = self._get_societe_field("email_societe", "")
if not email:
email = self._get_societe_field("email", "")
return email or self.DEFAULT_COMPANY_EMAIL
def _get_societe_phone(self) -> str:
"""Retourne le téléphone de la société."""
return self._get_societe_field("telephone", "")
def _get_societe_siret(self) -> str:
"""Retourne le SIRET de la société."""
return self._get_societe_field("siret", "")
def _get_societe_tva(self) -> str:
"""Retourne le numéro de TVA de la société."""
return self._get_societe_field("numero_tva", "")
def _get_font(self, bold: bool = False) -> str: def _get_font(self, bold: bool = False) -> str:
"""Retourne le nom de la police à utiliser.""" """Retourne le nom de la police à utiliser."""
if self.use_sage_font: if self.use_sage_font:
@ -563,31 +634,57 @@ class SagePDFGenerator:
col1_x, y, "ÉMETTEUR", font_size=8, bold=True, color=self.GRAY_400 col1_x, y, "ÉMETTEUR", font_size=8, bold=True, color=self.GRAY_400
) )
# === INFORMATIONS SOCIÉTÉ (dynamiques) ===
y_emetteur = y - 6 * mm y_emetteur = y - 6 * mm
self._draw_text( self._draw_text(
col1_x, col1_x,
y_emetteur, y_emetteur,
self.COMPANY_NAME, self._get_societe_name(),
font_size=10, font_size=10,
bold=True, bold=True,
color=self.GRAY_800, color=self.GRAY_800,
) )
# Adresse ligne 1
address_line1 = self._get_societe_address_line1()
if address_line1:
y_emetteur -= 5 * mm y_emetteur -= 5 * mm
# Tronquer si trop long
if len(address_line1) > 45:
address_line1 = address_line1[:42] + "..."
self._draw_text( self._draw_text(
col1_x, y_emetteur, self.COMPANY_ADDRESS_1, font_size=9, color=self.GRAY_600 col1_x, y_emetteur, address_line1, font_size=9, color=self.GRAY_600
) )
# Adresse ligne 2 (CP + Ville)
address_line2 = self._get_societe_address_line2()
if address_line2:
y_emetteur -= 4 * mm y_emetteur -= 4 * mm
self._draw_text( self._draw_text(
col1_x, y_emetteur, self.COMPANY_ADDRESS_2, font_size=9, color=self.GRAY_600 col1_x, y_emetteur, address_line2, font_size=9, color=self.GRAY_600
) )
# Email
societe_email = self._get_societe_email()
if societe_email:
y_emetteur -= 5 * mm y_emetteur -= 5 * mm
self._draw_text( self._draw_text(
col1_x, y_emetteur, self.COMPANY_EMAIL, font_size=9, color=self.GRAY_600 col1_x, y_emetteur, societe_email, font_size=9, color=self.GRAY_600
) )
# Téléphone (optionnel)
societe_phone = self._get_societe_phone()
if societe_phone:
y_emetteur -= 4 * mm
self._draw_text(
col1_x,
y_emetteur,
f"Tél: {societe_phone}",
font_size=8,
color=self.GRAY_500,
)
# === DESTINATAIRE ===
box_padding = 4 * mm box_padding = 4 * mm
box_height = 26 * mm box_height = 26 * mm
box_y = y - 3 * mm box_y = y - 3 * mm
@ -912,8 +1009,48 @@ class SagePDFGenerator:
return y return y
def _draw_footer(self): def _draw_footer(self):
footer_y = 12 * mm footer_y = 15 * mm
# Informations légales de la société
legal_parts = []
societe_name = self._get_societe_name()
if societe_name and societe_name != self.DEFAULT_COMPANY_NAME:
legal_parts.append(societe_name)
siret = self._get_societe_siret()
if siret:
legal_parts.append(f"SIRET: {siret}")
tva = self._get_societe_tva()
if tva:
legal_parts.append(f"TVA: {tva}")
# 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:
legal_parts.append(
f"{forme} au capital de {capital:,.0f}".replace(",", " ")
)
# Dessiner les informations légales
if legal_parts:
legal_text = "".join(legal_parts)
# Tronquer si trop long
if len(legal_text) > 100:
legal_text = legal_text[:97] + "..."
self._draw_text(
self.page_width / 2,
footer_y + 4 * mm,
legal_text,
font_size=7,
color=self.GRAY_400,
align="center",
)
# Pagination
page_text = f"Page {self.page_number} / {self.total_pages}" page_text = f"Page {self.page_number} / {self.total_pages}"
self._draw_text( self._draw_text(
self.page_width / 2, self.page_width / 2,