Merge branch 'fix/update_pdf_structure' into feat/update_pdf_structure
This commit is contained in:
commit
f3957dddcf
1 changed files with 225 additions and 82 deletions
307
email_queue.py
307
email_queue.py
|
|
@ -12,9 +12,11 @@ from config.config import settings
|
||||||
import logging
|
import logging
|
||||||
from reportlab.lib.pagesizes import A4
|
from reportlab.lib.pagesizes import A4
|
||||||
from reportlab.pdfgen import canvas
|
from reportlab.pdfgen import canvas
|
||||||
from reportlab.lib.units import cm
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
from reportlab.lib.units import mm
|
||||||
|
from reportlab.lib.colors import HexColor
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -207,102 +209,243 @@ class EmailQueue:
|
||||||
pdf = canvas.Canvas(buffer, pagesize=A4)
|
pdf = canvas.Canvas(buffer, pagesize=A4)
|
||||||
width, height = 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.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 = {
|
y -= 7 * mm
|
||||||
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")
|
|
||||||
|
|
||||||
pdf.setFont("Helvetica", 12)
|
|
||||||
pdf.drawString(2 * cm, height - 4 * cm, f"Type: {type_label}")
|
|
||||||
|
|
||||||
y = height - 5 * cm
|
|
||||||
pdf.setFont("Helvetica-Bold", 14)
|
|
||||||
pdf.drawString(2 * cm, y, "CLIENT")
|
|
||||||
|
|
||||||
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 -= 1.5 * cm
|
|
||||||
pdf.setFont("Helvetica-Bold", 14)
|
|
||||||
pdf.drawString(2 * cm, y, "ARTICLES")
|
|
||||||
|
|
||||||
y -= 1 * cm
|
|
||||||
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")
|
|
||||||
|
|
||||||
y -= 0.5 * cm
|
|
||||||
pdf.line(2 * cm, y, width - 2 * cm, y)
|
|
||||||
|
|
||||||
y -= 0.7 * cm
|
|
||||||
pdf.setFont("Helvetica", 9)
|
pdf.setFont("Helvetica", 9)
|
||||||
|
pdf.setFillColor(gray_600)
|
||||||
|
|
||||||
for ligne in doc.get("lignes", []):
|
date_str = doc.get("date") or datetime.now().strftime("%d/%m/%Y")
|
||||||
if y < 3 * cm:
|
pdf.drawRightString(width - margin, y, f"Date : {date_str}")
|
||||||
|
|
||||||
|
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 -= 5 * mm
|
||||||
|
reference = doc.get("reference") or "—"
|
||||||
|
pdf.drawRightString(width - margin, y, f"Réf : {reference}")
|
||||||
|
|
||||||
|
# ===== ADDRESSES =====
|
||||||
|
y -= 20 * mm
|
||||||
|
|
||||||
|
# É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.setFillColor(gray_800)
|
||||||
|
pdf.drawString(col1_x, y_emetteur, "Bijou S.A.S")
|
||||||
|
|
||||||
|
y_emetteur -= 5 * mm
|
||||||
|
pdf.setFont("Helvetica", 9)
|
||||||
|
pdf.setFillColor(gray_600)
|
||||||
|
pdf.drawString(col1_x, y_emetteur, "123 Avenue de la République")
|
||||||
|
|
||||||
|
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()
|
pdf.showPage()
|
||||||
y = height - 3 * cm
|
y = height - margin - 20 * mm
|
||||||
pdf.setFont("Helvetica", 9)
|
pdf.setFont("Helvetica", 8)
|
||||||
|
|
||||||
designation = (
|
designation = (
|
||||||
ligne.get("designation") or ligne.get("designation_article") or ""
|
ligne.get("designation") or ligne.get("designation_article") or ""
|
||||||
)
|
)
|
||||||
if designation:
|
if len(designation) > 60:
|
||||||
designation = str(designation)[:50]
|
designation = designation[:57] + "..."
|
||||||
else:
|
|
||||||
designation = ""
|
|
||||||
|
|
||||||
pdf.drawString(2 * cm, y, designation)
|
pdf.setFillColor(gray_800)
|
||||||
pdf.drawString(10 * cm, y, str(ligne.get("quantite") or 0))
|
pdf.setFont("Helvetica-Bold", 8)
|
||||||
pdf.drawString(
|
pdf.drawString(col_designation, y, designation)
|
||||||
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
|
|
||||||
|
|
||||||
y -= 1 * cm
|
y -= 4 * mm
|
||||||
pdf.line(12 * cm, y, width - 2 * cm, y)
|
|
||||||
|
|
||||||
y -= 0.8 * cm
|
# Description (si différente)
|
||||||
pdf.setFont("Helvetica-Bold", 11)
|
description = ligne.get("description", "")
|
||||||
pdf.drawString(12 * cm, y, "Total HT NET:")
|
if description and description != designation:
|
||||||
pdf.drawString(15 * cm, y, f"{doc.get('total_ht_net') or 0:.2f}€")
|
pdf.setFont("Helvetica", 7)
|
||||||
|
pdf.setFillColor(gray_600)
|
||||||
|
if len(description) > 70:
|
||||||
|
description = description[:67] + "..."
|
||||||
|
pdf.drawString(col_designation, y, description)
|
||||||
|
y -= 4 * mm
|
||||||
|
|
||||||
y -= 0.6 * cm
|
# Valeurs
|
||||||
pdf.drawString(12 * cm, y, "TVA (20%):")
|
y += 4 * mm # Remonter pour aligner avec la désignation
|
||||||
tva = (doc.get("total_ttc") or 0) - (doc.get("total_ht_net") or 0)
|
pdf.setFont("Helvetica", 8)
|
||||||
pdf.drawString(15 * cm, y, f"{tva:.2f}€")
|
pdf.setFillColor(gray_800)
|
||||||
|
|
||||||
y -= 0.6 * cm
|
quantite = ligne.get("quantite") or 0
|
||||||
pdf.setFont("Helvetica-Bold", 14)
|
pdf.drawRightString(col_quantite, y, str(quantite))
|
||||||
pdf.drawString(12 * cm, y, "Total TTC:")
|
|
||||||
pdf.drawString(15 * cm, y, f"{doc.get('total_ttc') or 0:.2f}€")
|
|
||||||
|
|
||||||
pdf.setFont("Helvetica", 8)
|
prix_unit = ligne.get("prix_unitaire_ht") or ligne.get("prix_unitaire", 0)
|
||||||
pdf.drawString(
|
pdf.drawRightString(col_prix_unit, y, f"{prix_unit:.2f} €")
|
||||||
2 * cm, 2 * cm, f"Généré le {datetime.now().strftime('%d/%m/%Y %H:%M')}"
|
|
||||||
)
|
taux_taxe = ligne.get("taux_taxe1") or 20
|
||||||
pdf.drawString(2 * cm, 1.5 * cm, "Sage 100c - API Dataven")
|
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()
|
pdf.save()
|
||||||
buffer.seek(0)
|
buffer.seek(0)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue