from datetime import datetime from pathlib import Path from typing import List from weasyprint import HTML from app.core.config import settings from app.core.logging import app_logger from app.models.invoice import Invoice, InvoiceItem from app.models.client import Client from app.models.user import User def generate_invoice_pdf( invoice: Invoice, items: List[InvoiceItem], client: Client, user: User, ) -> Path: """ Generate a PDF for an invoice and save it to the storage directory """ try: # Create a unique filename timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"invoice_{invoice.id}_{timestamp}.pdf" file_path = settings.INVOICE_STORAGE_DIR / filename # Make sure the directory exists settings.INVOICE_STORAGE_DIR.mkdir(parents=True, exist_ok=True) # Generate HTML content for the invoice html_content = _generate_invoice_html(invoice, items, client, user) # Convert HTML to PDF HTML(string=html_content).write_pdf(file_path) app_logger.info(f"Generated PDF invoice: {file_path}") return file_path except Exception as e: app_logger.error(f"Error generating PDF invoice: {str(e)}") raise def _generate_invoice_html( invoice: Invoice, items: List[InvoiceItem], client: Client, user: User, ) -> str: """ Generate HTML content for an invoice """ # Calculate totals subtotal = sum(item.unit_price * item.quantity for item in items) total = subtotal # Add tax calculations if needed # Format dates issued_date = invoice.issued_date.strftime("%Y-%m-%d") if invoice.issued_date else "" due_date = invoice.due_date.strftime("%Y-%m-%d") if invoice.due_date else "" # Basic HTML template for the invoice return f""" Invoice #{invoice.invoice_number}
INVOICE
#{invoice.invoice_number}
{invoice.status.upper()}

From:

{user.full_name}
{user.email}

To:

{client.name}
{client.company_name or ""}
{client.email}
{client.address or ""}
Invoice Date: {issued_date}
Due Date: {due_date}
{"".join(f"" for item in items)}
Description Quantity Unit Price Amount
{item.description}{item.quantity}${item.unit_price:.2f}${item.quantity * item.unit_price:.2f}
Subtotal: ${subtotal:.2f}
Total: ${total:.2f}
Thank you for your business!
"""