import json from fpdf import FPDF import os class Generator: def __init__(self, form_data, template_path="Templates/proposition_commercial.json"): self.form_data = form_data self.template_path = template_path def load_template(self): with open(self.template_path, 'r', encoding='utf-8') as file: template = json.load(file) return template def replace_variables(self, content, visited=None): if visited is None: visited = set() # Avoid infinite recursion by tracking visited objects if id(content) in visited: return content visited.add(id(content)) if isinstance(content, str): # Replace variables in a string for key, value in self.form_data.items(): if key == "client" and isinstance(value, dict): # Handle client dictionary directly for sub_key, sub_value in value.items(): placeholder = f"{{{sub_key}}}" content = content.replace(placeholder, str(sub_value)) elif isinstance(value, dict): # Flatten nested dictionaries like 'client' for sub_key, sub_value in value.items(): placeholder = f"{{{key}_{sub_key}}}" content = content.replace(placeholder, str(sub_value)) elif isinstance(value, list): # Handle lists (e.g., features or services) if key == "features": formatted_features = "\n".join(f"- {item['description']}" for item in value if isinstance(item, dict) and 'description' in item) content = content.replace(f"{{{key}}}", formatted_features) elif key == "services": formatted_services = "\n".join( f"- {item['description']} : {item['prix_unitaire']} euros" for item in value if isinstance(item, dict) and 'description' in item and 'prix_unitaire' in item ) content = content.replace(f"{{{key}}}", formatted_services) else: content = content.replace(f"{{{key}}}", str(value)) return content elif isinstance(content, list): # Replace variables in each item of a list return [self.replace_variables(item, visited) for item in content] elif isinstance(content, dict): # Replace variables in a dictionary return {k: self.replace_variables(v, visited) for k, v in content.items()} return content def generate_content(self): template = self.load_template() return self.replace_variables(template) def generate_pdf(self, output_path=None): content = self.generate_content() # Format the output file name based on the client name client_name = self.form_data.get("client_name", "client").replace(" ", "_").lower() if output_path is None: output_path = f"output/{client_name}_proposition_commercial.pdf" else: output_path = f"output/{output_path}/{client_name}_proposition_commercial.pdf" pdf = FPDF() pdf.add_page() # Ajouter un fond (image de fond) background_path = "Data/BG.png" # Chemin de l'image de fond if os.path.exists(background_path): pdf.image(background_path, x=0, y=0, w=210, h=297) # Dimensions pour une page A4 # Title title = content.get("title", "Proposition Commerciale") pdf.set_font("Helvetica", "B", 16) pdf.cell(0, 10, title, ln=True, align="C") pdf.ln(10) # Main content pdf.set_font("Helvetica", "", 12) for section in content.get("sections", []): # Section header pdf.set_font("Helvetica", "B", 14) pdf.cell(0, 10, section.get("header", ""), ln=True) pdf.ln(5) # Section content pdf.set_font("Helvetica", "", 12) section_content = section.get("content", []) if isinstance(section_content, list): for item in section_content: pdf.multi_cell(0, 10, str(item)) pdf.ln(2) elif isinstance(section_content, str): pdf.multi_cell(0, 10, section_content) pdf.ln(5) # Additional fields outside sections for key, value in content.items(): if key not in ["sections", "title"]: # Exclure "sections" et "title" pdf.set_font("Helvetica", "B", 12) pdf.cell(0, 10, f"{key} :", ln=True) pdf.set_font("Helvetica", "", 12) # Format the client data specially if key == "client" and isinstance(value, dict): client_str = "\n".join(f"{k.capitalize()}: {v}" for k, v in value.items()) pdf.multi_cell(0, 10, client_str) else: pdf.multi_cell(0, 10, str(value)) pdf.ln(5) # Ensure output folder exists os.makedirs(os.path.dirname(output_path), exist_ok=True) pdf.output(output_path) print(f"PDF saved to {output_path}") def generate_facture(self, output_path=None): content = self.generate_content() # Format the output file name based on the client name # On récupère le nom du client dans le dictionnaire 'client' client = content.get("client", {}) if isinstance(client, dict): client_name = client.get("nom", "client").replace(" ", "_").lower() else: # Fallback to a default name if 'client' is not a dictionary client_name = "client" print(f"Client name: {client_name}") if output_path is None: output_path = f"output/{client_name}_devis.pdf" else: output_path = f"output/{output_path}/{client_name}_devis.pdf" # Create PDF with invoice-like styling pdf = FPDF() pdf.add_page() # Add background if exists background_path = "Data/BG.png" if os.path.exists(background_path): pdf.image(background_path, x=0, y=0, w=210, h=297) # Document Title pdf.set_font("Helvetica", "B", 18) pdf.cell(0, 15, content.get("title", "Devis de service"), ln=True, align="C") # Add a horizontal line pdf.line(10, pdf.get_y(), 200, pdf.get_y()) pdf.ln(10) # Header Info (Devis number and date) pdf.set_font("Helvetica", "B", 10) pdf.cell(95, 10, f"DEVIS N° : {content.get('numero', '')}", 0, 0) pdf.cell(95, 10, f"DATE : {content.get('date', '')}", 0, 1, 'R') pdf.ln(5) # Client Information Box pdf.set_fill_color(240, 240, 240) # Light gray background pdf.rect(10, pdf.get_y(), 190, 30, style='F') pdf.set_font("Helvetica", "B", 12) pdf.cell(0, 10, "INFORMATIONS CLIENT", 0, 1, 'L') pdf.set_font("Helvetica", "", 10) client_data = content.get("client", {}) if isinstance(client_data, dict): pdf.cell(40, 5, f"Nom:", 0, 0) pdf.cell(150, 5, f"{client_data.get('nom', '')}", 0, 1) pdf.cell(40, 5, f"Email:", 0, 0) pdf.cell(150, 5, f"{client_data.get('email', '')}", 0, 1) pdf.cell(40, 5, f"Téléphone:", 0, 0) pdf.cell(150, 5, f"{client_data.get('telephone', '')}", 0, 1) pdf.cell(40, 5, f"Adresse:", 0, 0) pdf.cell(150, 5, f"{client_data.get('adresse', '')}", 0, 1) pdf.ln(10) # Services Table Header pdf.set_fill_color(60, 60, 60) # Dark gray for header pdf.set_text_color(255, 255, 255) # White text pdf.set_font("Helvetica", "B", 10) # Table header pdf.cell(120, 10, "DESCRIPTION", 1, 0, 'C', 1) pdf.cell(35, 10, "PRIX UNITAIRE", 1, 0, 'C', 1) pdf.cell(35, 10, "MONTANT", 1, 1, 'C', 1) # Reset color for table content pdf.set_text_color(0, 0, 0) pdf.set_font("Helvetica", "", 10) # Services Table Content services = self.form_data.get("services", []) total = 0 if isinstance(services, list): for service in services: if isinstance(service, dict): description = service.get("description", "") prix = service.get("prix_unitaire", 0) total += float(prix) pdf.cell(120, 8, description, 1, 0) pdf.cell(35, 8, f"{prix} E", 1, 0, 'R') pdf.cell(35, 8, f"{prix} E", 1, 1, 'R') # Total Amount pdf.set_font("Helvetica", "B", 10) pdf.cell(155, 10, "TOTAL", 1, 0, 'R') pdf.cell(35, 10, f"{total} E", 1, 1, 'R') # Payment Terms and Notes pdf.ln(10) pdf.set_font("Helvetica", "B", 10) pdf.cell(0, 10, "CONDITIONS DE PAIEMENT", 0, 1) pdf.set_font("Helvetica", "", 10) payment_terms = self.form_data.get("payment_terms", "50% à la signature, 50% à la livraison") pdf.multi_cell(0, 5, payment_terms) pdf.ln(5) pdf.set_font("Helvetica", "B", 10) pdf.cell(0, 10, "NOTES", 0, 1) pdf.set_font("Helvetica", "", 10) pdf.multi_cell(0, 5, "Ce devis est valable 30 jours à compter de la date d'émission.") # Footer pdf.ln(10) pdf.set_font("Helvetica", "I", 8) pdf.cell(0, 10, "Merci de votre confiance.", 0, 1, 'C') pdf.cell(0, 10, "Pour accepter ce devis, veuillez le signer et me le retourner par email.", 0, 1, 'C') # Ensure output folder exists os.makedirs(os.path.dirname(output_path), exist_ok=True) # Save the PDF pdf.output(output_path) print(f"PDF saved to {output_path}")