Vortex-Flux / src /DocumentGen /InvoiceRepayment.py
klydekushy's picture
Update src/DocumentGen/InvoiceRepayment.py
84b142e verified
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import cm
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, HRFlowable
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from io import BytesIO
from datetime import datetime
def generer_recu(data):
"""
Génère un reçu de remboursement au format PDF
Args:
data: dict contenant toutes les informations nécessaires
Returns:
BytesIO: Buffer contenant le PDF (ou None en cas d'erreur)
"""
# === 1. LOGS ET VALIDATION (Pas besoin de try ici) ===
print("=== DÉBUT GÉNÉRATION REÇU ===")
print(f"Données reçues : {data.keys()}")
print(f"Numéro reçu : {data.get('numero_recu')}")
print(f"Transaction ID : {data.get('trans_id')}")
# Vérification des données minimales
if not data.get('numero_recu'):
print("ERREUR : numero_recu manquant")
return None
if not data.get('client'):
print("ERREUR : données client manquantes")
return None
if not data.get('paiement'):
print("ERREUR : données paiement manquantes")
return None
# === 2. GÉNÉRATION PDF (Protégé par try/except) ===
try:
# Création du buffer
buffer = BytesIO()
# Configuration du document
doc = SimpleDocTemplate(
buffer,
pagesize=A4,
rightMargin=2*cm,
leftMargin=2*cm,
topMargin=2*cm,
bottomMargin=2*cm
)
# Container pour les éléments
elements = []
# Styles
styles = getSampleStyleSheet()
# Style personnalisé pour le titre
title_style = ParagraphStyle(
'CustomTitle',
parent=styles['Heading1'],
fontSize=20,
textColor=colors.HexColor('#58a6ff'),
spaceAfter=12,
alignment=TA_CENTER,
fontName='Helvetica-Bold'
)
# Style pour les sous-titres
subtitle_style = ParagraphStyle(
'CustomSubtitle',
parent=styles['Heading2'],
fontSize=14,
textColor=colors.HexColor('#8b949e'),
spaceAfter=10,
spaceBefore=15,
fontName='Helvetica-Bold'
)
# Style pour le texte normal
normal_style = ParagraphStyle(
'CustomNormal',
parent=styles['Normal'],
fontSize=10,
textColor=colors.black,
spaceAfter=6
)
# Style pour les montants importants
amount_style = ParagraphStyle(
'AmountStyle',
parent=styles['Normal'],
fontSize=16,
textColor=colors.HexColor('#54bd4b'),
alignment=TA_CENTER,
fontName='Helvetica-Bold',
spaceAfter=10
)
# === EN-TÊTE ===
elements.append(Paragraph("REÇU DE REMBOURSEMENT", title_style))
elements.append(Spacer(1, 0.3*cm))
# Numéro de reçu et date
date_str = data['date_paiement'].strftime('%d/%m/%Y') if hasattr(data['date_paiement'], 'strftime') else str(data['date_paiement'])
header_data = [
[f"N° {data['numero_recu']}", f"Date: {date_str}"]
]
header_table = Table(header_data, colWidths=[8*cm, 8*cm])
header_table.setStyle(TableStyle([
('FONTNAME', (0, 0), (-1, -1), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, -1), 11),
('TEXTCOLOR', (0, 0), (0, 0), colors.HexColor('#58a6ff')),
('TEXTCOLOR', (1, 0), (1, 0), colors.HexColor('#8b949e')),
('ALIGN', (0, 0), (0, 0), 'LEFT'),
('ALIGN', (1, 0), (1, 0), 'RIGHT'),
]))
elements.append(header_table)
elements.append(Spacer(1, 0.5*cm))
# Ligne de séparation
elements.append(HRFlowable(width="100%", thickness=2, color=colors.HexColor('#58a6ff')))
elements.append(Spacer(1, 0.5*cm))
# === INFORMATIONS CLIENT ===
elements.append(Paragraph("INFORMATIONS CLIENT", subtitle_style))
client = data['client']
client_data = [
['Nom Complet:', client.get('Nom_Complet', 'N/A')],
['N° Client:', client.get('ID_Client', 'N/A')],
['Téléphone:', str(client.get('Telephone', 'N/A'))],
['Adresse:', client.get('Adresse', 'N/A')]
]
client_table = Table(client_data, colWidths=[4*cm, 12*cm])
client_table.setStyle(TableStyle([
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
('FONTNAME', (1, 0), (1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 10),
('TEXTCOLOR', (0, 0), (0, -1), colors.HexColor('#8b949e')),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('LEFTPADDING', (0, 0), (-1, -1), 0),
('RIGHTPADDING', (0, 0), (-1, -1), 0),
]))
elements.append(client_table)
elements.append(Spacer(1, 0.5*cm))
# === INFORMATIONS PRÊT ===
elements.append(Paragraph("INFORMATIONS PRÊT", subtitle_style))
loan = data['loan']
loan_data = [
['N° Prêt:', loan.get('ID_Pret', 'N/A')],
['Type de Prêt:', loan.get('Type_Pret', 'N/A')],
['Capital Initial:', f"{loan.get('Montant_Capital', 0):,.0f} XOF"],
['Montant Total Dû:', f"{loan.get('Montant_Total', 0):,.0f} XOF"]
]
loan_table = Table(loan_data, colWidths=[4*cm, 12*cm])
loan_table.setStyle(TableStyle([
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
('FONTNAME', (1, 0), (1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 10),
('TEXTCOLOR', (0, 0), (0, -1), colors.HexColor('#8b949e')),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('LEFTPADDING', (0, 0), (-1, -1), 0),
('RIGHTPADDING', (0, 0), (-1, -1), 0),
]))
elements.append(loan_table)
elements.append(Spacer(1, 0.5*cm))
# === DÉTAILS DU PAIEMENT ===
elements.append(Paragraph("DÉTAILS DU PAIEMENT", subtitle_style))
paiement = data['paiement']
# Gestion sûre de la date d'échéance
date_echeance = paiement.get('date_echeance_prevue', 'N/A')
if hasattr(date_echeance, 'strftime'):
date_echeance_str = date_echeance.strftime('%d/%m/%Y')
else:
date_echeance_str = str(date_echeance)
payment_data = [
['Échéance:', str(paiement.get('numero_echeance', 'N/A'))],
['Date Échéance Prévue:', date_echeance_str],
['Retard (jours):', str(paiement.get('jours_retard', 0))],
['Statut:', paiement.get('statut_paiement', 'N/A')]
]
payment_table = Table(payment_data, colWidths=[4*cm, 12*cm])
payment_table.setStyle(TableStyle([
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
('FONTNAME', (1, 0), (1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 10),
('TEXTCOLOR', (0, 0), (0, -1), colors.HexColor('#8b949e')),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('LEFTPADDING', (0, 0), (-1, -1), 0),
('RIGHTPADDING', (0, 0), (-1, -1), 0),
]))
elements.append(payment_table)
elements.append(Spacer(1, 0.5*cm))
# === DÉCOMPOSITION DU MONTANT ===
elements.append(Paragraph("DÉCOMPOSITION DU MONTANT", subtitle_style))
decomp_data = [
['Principal remboursé', f"{paiement.get('montant_principal', 0):,.0f} XOF"],
['Intérêts payés', f"{paiement.get('montant_interets', 0):,.0f} XOF"],
['Pénalités de retard', f"{paiement.get('penalites_retard', 0):,.0f} XOF"]
]
decomp_table = Table(decomp_data, colWidths=[10*cm, 6*cm])
decomp_table.setStyle(TableStyle([
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 10),
('TEXTCOLOR', (0, 0), (0, -1), colors.HexColor('#8b949e')),
('ALIGN', (1, 0), (1, -1), 'RIGHT'),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('LINEBELOW', (0, 0), (-1, -2), 0.5, colors.grey),
('LEFTPADDING', (0, 0), (-1, -1), 5),
('RIGHTPADDING', (0, 0), (-1, -1), 5),
]))
elements.append(decomp_table)
elements.append(Spacer(1, 0.3*cm))
# === MONTANT TOTAL VERSÉ ===
montant_verse = paiement.get('montant_verse',
paiement.get('montant_principal', 0) +
paiement.get('montant_interets', 0) +
paiement.get('penalites_retard', 0))
total_data = [
['MONTANT TOTAL VERSÉ', f"{montant_verse:,.0f} XOF"]
]
total_table = Table(total_data, colWidths=[10*cm, 6*cm])
total_table.setStyle(TableStyle([
('FONTNAME', (0, 0), (-1, -1), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, -1), 12),
('TEXTCOLOR', (0, 0), (0, -1), colors.black),
('TEXTCOLOR', (1, 0), (1, -1), colors.HexColor('#54bd4b')),
('ALIGN', (1, 0), (1, -1), 'RIGHT'),
('BACKGROUND', (0, 0), (-1, -1), colors.HexColor('#e8f5e9')),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('LINEABOVE', (0, 0), (-1, 0), 2, colors.HexColor('#54bd4b')),
('LEFTPADDING', (0, 0), (-1, -1), 5),
('RIGHTPADDING', (0, 0), (-1, -1), 5),
('TOPPADDING', (0, 0), (-1, -1), 8),
('BOTTOMPADDING', (0, 0), (-1, -1), 8),
]))
elements.append(total_table)
elements.append(Spacer(1, 0.5*cm))
# === SOLDES ===
elements.append(Paragraph("SOLDES", subtitle_style))
soldes_data = [
['Solde avant paiement', f"{paiement.get('solde_avant', 0):,.0f} XOF"],
['Solde après paiement', f"{paiement.get('solde_apres', 0):,.0f} XOF"]
]
soldes_table = Table(soldes_data, colWidths=[10*cm, 6*cm])
soldes_table.setStyle(TableStyle([
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 10),
('TEXTCOLOR', (0, 0), (0, -1), colors.HexColor('#8b949e')),
('ALIGN', (1, 0), (1, -1), 'RIGHT'),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('LEFTPADDING', (0, 0), (-1, -1), 5),
('RIGHTPADDING', (0, 0), (-1, -1), 5),
]))
elements.append(soldes_table)
elements.append(Spacer(1, 0.5*cm))
# === MOYEN DE PAIEMENT ===
elements.append(Paragraph("MOYEN DE PAIEMENT", subtitle_style))
moyen_data = [
['Mode de paiement:', data.get('moyen', 'N/A')],
['Référence externe:', data.get('reference', 'N/A')],
['Transaction ID:', data.get('trans_id', 'N/A')]
]
moyen_table = Table(moyen_data, colWidths=[4*cm, 12*cm])
moyen_table.setStyle(TableStyle([
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
('FONTNAME', (1, 0), (1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 10),
('TEXTCOLOR', (0, 0), (0, -1), colors.HexColor('#8b949e')),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('LEFTPADDING', (0, 0), (-1, -1), 0),
('RIGHTPADDING', (0, 0), (-1, -1), 0),
]))
elements.append(moyen_table)
elements.append(Spacer(1, 1*cm))
# === FOOTER ===
elements.append(HRFlowable(width="100%", thickness=1, color=colors.grey))
elements.append(Spacer(1, 0.3*cm))
footer_text = f"Reçu généré le {datetime.now().strftime('%d/%m/%Y à %H:%M')} | Document officiel"
footer_style = ParagraphStyle(
'Footer',
parent=styles['Normal'],
fontSize=8,
textColor=colors.grey,
alignment=TA_CENTER
)
elements.append(Paragraph(footer_text, footer_style))
# === SIGNATURE ===
elements.append(Spacer(1, 1*cm))
signature_data = [
['Signature du Prêteur:', ''],
['', ''],
['', '_________________________']
]
signature_table = Table(signature_data, colWidths=[8*cm, 8*cm])
signature_table.setStyle(TableStyle([
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 10),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('ALIGN', (1, 0), (1, -1), 'CENTER'),
]))
elements.append(signature_table)
# Construction du PDF
doc.build(elements)
# Retour du buffer
buffer.seek(0)
print("=== PDF GÉNÉRÉ AVEC SUCCÈS ===")
return buffer
except Exception as e:
print(f"❌ ERREUR LORS DE LA GÉNÉRATION : {e}")
import traceback
traceback.print_exc()
return None