diff --git a/email_queue.py b/email_queue.py index dee4c03..6079ae8 100644 --- a/email_queue.py +++ b/email_queue.py @@ -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") - - 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 + y -= 7 * mm pdf.setFont("Helvetica", 9) + pdf.setFillColor(gray_600) - for ligne in doc.get("lignes", []): - if y < 3 * cm: + date_str = doc.get("date") or datetime.now().strftime("%d/%m/%Y") + 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() - 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}€") + # 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 - 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}€") + # Valeurs + y += 4 * mm # Remonter pour aligner avec la désignation + pdf.setFont("Helvetica", 8) + pdf.setFillColor(gray_800) - 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}€") + quantite = ligne.get("quantite") or 0 + pdf.drawRightString(col_quantite, y, str(quantite)) - 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") + 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)