from datetime import datetime from pathlib import Path from typing import Optional from jinja2 import Environment, FileSystemLoader from weasyprint import HTML from app.models.invoice import Invoice class InvoiceGenerator: """Service for generating invoice PDFs.""" def __init__(self): # Set up templates directory self.templates_dir = Path(__file__).parent.parent / "templates" self.templates_dir.mkdir(exist_ok=True) # Set up output directory self.output_dir = Path("/app/storage/invoices") self.output_dir.mkdir(parents=True, exist_ok=True) # Set up Jinja2 environment self.env = Environment( loader=FileSystemLoader(str(self.templates_dir)), autoescape=True ) def _ensure_template_exists(self): """Ensure the invoice template exists.""" template_path = self.templates_dir / "invoice.html" if not template_path.exists(): # Create a simple invoice template template_content = """ Invoice {{ invoice.invoice_number }}

INVOICE

From

{{ user.company_name or user.full_name }}

{{ user.address }}

{{ user.email }}

{{ user.phone_number }}

To

{{ customer.name }}

{{ customer.address }}

{{ customer.email }}

{{ customer.phone }}

Invoice Details

Invoice Number: {{ invoice.invoice_number }}

Issue Date: {{ invoice.issue_date.strftime('%d %b %Y') }}

Due Date: {{ invoice.due_date.strftime('%d %b %Y') }}

Status: {{ invoice.status.value.upper() }}

{% for item in invoice.items %} {% endfor %}
Description Quantity Unit Price Tax Rate Tax Amount Total
{{ item.description }} {{ item.quantity }} ${{ "%.2f"|format(item.unit_price) }} {{ "%.2f"|format(item.tax_rate) }}% ${{ "%.2f"|format(item.tax_amount) }} ${{ "%.2f"|format(item.total) }}
Subtotal ${{ "%.2f"|format(invoice.subtotal) }}
Tax ${{ "%.2f"|format(invoice.tax_amount) }}
Discount ${{ "%.2f"|format(invoice.discount) }}
Total ${{ "%.2f"|format(invoice.total) }}
{% if invoice.notes %}

Notes

{{ invoice.notes }}

{% endif %} {% if invoice.terms %}

Terms & Conditions

{{ invoice.terms }}

{% endif %} """ with open(template_path, "w") as f: f.write(template_content) def generate_invoice_pdf(self, invoice: Invoice) -> Optional[str]: """Generate a PDF invoice.""" try: self._ensure_template_exists() # Get template template = self.env.get_template("invoice.html") # Prepare context context = { "invoice": invoice, "user": invoice.user, "customer": invoice.customer, "now": datetime.now() } # Render HTML html_content = template.render(**context) # Generate PDF filename filename = f"invoice_{invoice.invoice_number}_{datetime.now().strftime('%Y%m%d%H%M%S')}.pdf" output_path = self.output_dir / filename # Generate PDF HTML(string=html_content).write_pdf(str(output_path)) return str(output_path) except Exception as e: # Log error print(f"Error generating invoice PDF: {e}") return None