feat(pdf-generation): redesign document layout with modern styling

This commit is contained in:
Fanilo-Nantenaina 2026-01-07 19:42:29 +03:00
parent 0be28f6744
commit 4a1960745a

View file

@ -12,9 +12,11 @@ from config.config import settings
import logging
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from reportlab.lib.units import cm
from io import BytesIO
from reportlab.lib.units import mm
from reportlab.lib.colors import HexColor
logger = logging.getLogger(__name__)
@ -207,102 +209,243 @@ class EmailQueue:
pdf = canvas.Canvas(buffer, pagesize=A4)
width, height = A4
# Couleurs
green_color = HexColor("#2A6F4F")
gray_400 = HexColor("#9CA3AF")
gray_600 = HexColor("#4B5563")
gray_800 = HexColor("#1F2937")
# Marges
margin = 8 * mm
content_width = width - 2 * margin
y = height - margin
# ===== HEADER =====
y -= 20 * mm
# Logo/Nom entreprise à gauche
pdf.setFont("Helvetica-Bold", 18)
pdf.setFillColor(green_color)
pdf.drawString(margin, y, "Bijou S.A.S")
# Informations document à droite
pdf.setFillColor(gray_800)
pdf.setFont("Helvetica-Bold", 20)
pdf.drawString(2 * cm, height - 3 * cm, f"Document N° {doc_id}")
numero = doc.get("numero") or "BROUILLON"
pdf.drawRightString(width - margin, y, numero.upper())
type_labels = {
0: "DEVIS",
1: "BON DE LIVRAISON",
2: "BON DE RETOUR",
3: "COMMANDE",
4: "PRÉPARATION",
5: "FACTURE",
}
type_label = type_labels.get(type_doc, "DOCUMENT")
y -= 7 * mm
pdf.setFont("Helvetica", 9)
pdf.setFillColor(gray_600)
pdf.setFont("Helvetica", 12)
pdf.drawString(2 * cm, height - 4 * cm, f"Type: {type_label}")
date_str = doc.get("date") or datetime.now().strftime("%d/%m/%Y")
pdf.drawRightString(width - margin, y, f"Date : {date_str}")
y = height - 5 * cm
pdf.setFont("Helvetica-Bold", 14)
pdf.drawString(2 * cm, y, "CLIENT")
y -= 5 * mm
date_livraison = (
doc.get("date_livraison") or doc.get("date_echeance") or date_str
)
pdf.drawRightString(width - margin, y, f"Validité : {date_livraison}")
y -= 0.8 * cm
pdf.setFont("Helvetica", 11)
pdf.drawString(2 * cm, y, f"Code: {doc.get('client_code') or ''}")
y -= 0.6 * cm
pdf.drawString(2 * cm, y, f"Nom: {doc.get('client_intitule') or ''}")
y -= 0.6 * cm
pdf.drawString(2 * cm, y, f"Date: {doc.get('date') or ''}")
y -= 5 * mm
reference = doc.get("reference") or ""
pdf.drawRightString(width - margin, y, f"Réf : {reference}")
y -= 1.5 * cm
pdf.setFont("Helvetica-Bold", 14)
pdf.drawString(2 * cm, y, "ARTICLES")
# ===== ADDRESSES =====
y -= 20 * mm
y -= 1 * cm
# Émetteur (gauche)
col1_x = margin
col2_x = margin + content_width / 2 + 6 * mm
col_width = content_width / 2 - 6 * mm
pdf.setFont("Helvetica-Bold", 8)
pdf.setFillColor(gray_400)
pdf.drawString(col1_x, y, "ÉMETTEUR")
y_emetteur = y - 5 * mm
pdf.setFont("Helvetica-Bold", 10)
pdf.drawString(2 * cm, y, "Désignation")
pdf.drawString(10 * cm, y, "Qté")
pdf.drawString(12 * cm, y, "Prix Unit.")
pdf.drawString(15 * cm, y, "Total HT")
pdf.setFillColor(gray_800)
pdf.drawString(col1_x, y_emetteur, "Bijou S.A.S")
y -= 0.5 * cm
pdf.line(2 * cm, y, width - 2 * cm, y)
y -= 0.7 * cm
y_emetteur -= 5 * mm
pdf.setFont("Helvetica", 9)
pdf.setFillColor(gray_600)
pdf.drawString(col1_x, y_emetteur, "123 Avenue de la République")
for ligne in doc.get("lignes", []):
if y < 3 * cm:
y_emetteur -= 4 * mm
pdf.drawString(col1_x, y_emetteur, "75011 Paris, France")
y_emetteur -= 5 * mm
pdf.drawString(col1_x, y_emetteur, "contact@bijou.com")
# Destinataire (droite, avec fond gris)
box_y = y - 4 * mm
box_height = 28 * mm
pdf.setFillColorRGB(0.97, 0.97, 0.97) # bg-gray-50
pdf.roundRect(
col2_x, box_y - box_height, col_width, box_height, 3 * mm, fill=1, stroke=0
)
pdf.setFillColor(gray_400)
pdf.setFont("Helvetica-Bold", 8)
pdf.drawString(col2_x + 4 * mm, y, "DESTINATAIRE")
y_dest = y - 5 * mm
pdf.setFont("Helvetica-Bold", 10)
pdf.setFillColor(gray_800)
client_name = doc.get("client_intitule") or "Client"
pdf.drawString(col2_x + 4 * mm, y_dest, client_name)
y_dest -= 5 * mm
pdf.setFont("Helvetica", 9)
pdf.setFillColor(gray_600)
pdf.drawString(col2_x + 4 * mm, y_dest, "10 rue des Clients")
y_dest -= 4 * mm
pdf.drawString(col2_x + 4 * mm, y_dest, "75001 Paris")
# ===== LIGNES D'ARTICLES =====
y = min(y_emetteur, y_dest) - 20 * mm
# En-têtes des colonnes
col_designation = margin
col_quantite = width - margin - 80 * mm
col_prix_unit = width - margin - 64 * mm
col_taux_taxe = width - margin - 40 * mm
col_montant = width - margin - 24 * mm
pdf.setFont("Helvetica-Bold", 9)
pdf.setFillColor(gray_800)
pdf.drawString(col_designation, y, "Désignation")
pdf.drawRightString(col_quantite, y, "Qté")
pdf.drawRightString(col_prix_unit, y, "Prix Unit. HT")
pdf.drawRightString(col_taux_taxe, y, "TVA")
pdf.drawRightString(col_montant, y, "Montant HT")
y -= 7 * mm
# Lignes d'articles
pdf.setFont("Helvetica", 8)
lignes = doc.get("lignes", [])
for ligne in lignes:
if y < 60 * mm: # Nouvelle page si nécessaire
pdf.showPage()
y = height - 3 * cm
pdf.setFont("Helvetica", 9)
y = height - margin - 20 * mm
pdf.setFont("Helvetica", 8)
designation = (
ligne.get("designation") or ligne.get("designation_article") or ""
)
if designation:
designation = str(designation)[:50]
else:
designation = ""
if len(designation) > 60:
designation = designation[:57] + "..."
pdf.drawString(2 * cm, y, designation)
pdf.drawString(10 * cm, y, str(ligne.get("quantite") or 0))
pdf.drawString(
12 * cm,
y,
f"{ligne.get('prix_unitaire_ht') or ligne.get('prix_unitaire', 0):.2f}",
)
pdf.drawString(
15 * cm,
y,
f"{ligne.get('montant_ligne_ht') or ligne.get('montant_ht', 0):.2f}",
)
y -= 0.6 * cm
pdf.setFillColor(gray_800)
pdf.setFont("Helvetica-Bold", 8)
pdf.drawString(col_designation, y, designation)
y -= 1 * cm
pdf.line(12 * cm, y, width - 2 * cm, y)
y -= 4 * mm
y -= 0.8 * cm
pdf.setFont("Helvetica-Bold", 11)
pdf.drawString(12 * cm, y, "Total HT NET:")
pdf.drawString(15 * cm, y, f"{doc.get('total_ht_net') or 0:.2f}")
y -= 0.6 * cm
pdf.drawString(12 * cm, y, "TVA (20%):")
tva = (doc.get("total_ttc") or 0) - (doc.get("total_ht_net") or 0)
pdf.drawString(15 * cm, y, f"{tva:.2f}")
y -= 0.6 * cm
pdf.setFont("Helvetica-Bold", 14)
pdf.drawString(12 * cm, y, "Total TTC:")
pdf.drawString(15 * cm, y, f"{doc.get('total_ttc') or 0:.2f}")
# Description (si différente)
description = ligne.get("description", "")
if description and description != designation:
pdf.setFont("Helvetica", 7)
pdf.setFillColor(gray_600)
if len(description) > 70:
description = description[:67] + "..."
pdf.drawString(col_designation, y, description)
y -= 4 * mm
# Valeurs
y += 4 * mm # Remonter pour aligner avec la désignation
pdf.setFont("Helvetica", 8)
pdf.drawString(
2 * cm, 2 * cm, f"Généré le {datetime.now().strftime('%d/%m/%Y %H:%M')}"
)
pdf.drawString(2 * cm, 1.5 * cm, "Sage 100c - API Dataven")
pdf.setFillColor(gray_800)
quantite = ligne.get("quantite") or 0
pdf.drawRightString(col_quantite, y, str(quantite))
prix_unit = ligne.get("prix_unitaire_ht") or ligne.get("prix_unitaire", 0)
pdf.drawRightString(col_prix_unit, y, f"{prix_unit:.2f}")
taux_taxe = ligne.get("taux_taxe1") or 20
pdf.drawRightString(col_taux_taxe, y, f"{taux_taxe}%")
montant = ligne.get("montant_ligne_ht") or ligne.get("montant_ht", 0)
pdf.setFont("Helvetica-Bold", 8)
pdf.drawRightString(col_montant, y, f"{montant:.2f}")
y -= 8 * mm
# Si aucune ligne
if not lignes:
pdf.setFont("Helvetica-Oblique", 9)
pdf.setFillColor(gray_400)
pdf.drawCentredString(width / 2, y, "Aucune ligne")
y -= 15 * mm
# ===== TOTAUX =====
y -= 10 * mm
totals_x = width - margin - 64 * mm
totals_label_width = 40 * mm
pdf.setFont("Helvetica", 9)
pdf.setFillColor(gray_600)
# Total HT
pdf.drawString(totals_x, y, "Total HT")
total_ht = doc.get("total_ht_net") or doc.get("total_ht") or 0
pdf.drawRightString(width - margin, y, f"{total_ht:.2f}")
y -= 6 * mm
# TVA
pdf.drawString(totals_x, y, "TVA")
total_ttc = doc.get("total_ttc") or 0
tva = total_ttc - total_ht
pdf.drawRightString(width - margin, y, f"{tva:.2f}")
y -= 8 * mm
# Ligne de séparation
pdf.setStrokeColor(gray_400)
pdf.line(totals_x, y + 2 * mm, width - margin, y + 2 * mm)
# Net à payer
pdf.setFont("Helvetica-Bold", 12)
pdf.setFillColor(green_color)
pdf.drawString(totals_x, y, "Net à payer")
pdf.drawRightString(width - margin, y, f"{total_ttc:.2f}")
# ===== NOTES =====
notes = doc.get("notes_publique") or doc.get("notes")
if notes:
y -= 15 * mm
pdf.setStrokeColor(gray_400)
pdf.line(margin, y + 5 * mm, width - margin, y + 5 * mm)
y -= 5 * mm
pdf.setFont("Helvetica-Bold", 8)
pdf.setFillColor(gray_400)
pdf.drawString(margin, y, "NOTES & CONDITIONS")
y -= 5 * mm
pdf.setFont("Helvetica", 8)
pdf.setFillColor(gray_600)
# Gérer les sauts de ligne dans les notes
for line in notes.split("\n"):
if y < 25 * mm:
break
pdf.drawString(margin, y, line[:100])
y -= 4 * mm
# ===== FOOTER =====
pdf.setFont("Helvetica", 7)
pdf.setFillColor(gray_400)
pdf.drawCentredString(width / 2, 15 * mm, "Page 1 / 1")
pdf.save()
buffer.seek(0)