Automated Action 77865dae90 Setup complete FastAPI backend with user authentication, client management, and invoice generation
Features:
- User authentication with JWT
- Client management with CRUD operations
- Invoice generation and management
- SQLite database with Alembic migrations
- Detailed project documentation
2025-05-26 17:41:47 +00:00

184 lines
5.6 KiB
Python

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"""
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Invoice #{invoice.invoice_number}</title>
<style>
body {{
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
color: #333;
}}
.invoice-header {{
display: flex;
justify-content: space-between;
margin-bottom: 30px;
}}
.invoice-title {{
font-size: 24px;
font-weight: bold;
color: #2c3e50;
}}
.company-details, .client-details {{
margin-bottom: 20px;
}}
.invoice-meta {{
margin-bottom: 30px;
border: 1px solid #eee;
padding: 10px;
background-color: #f9f9f9;
}}
table {{
width: 100%;
border-collapse: collapse;
}}
th, td {{
padding: 10px;
text-align: left;
border-bottom: 1px solid #ddd;
}}
th {{
background-color: #f2f2f2;
}}
.totals {{
margin-top: 20px;
text-align: right;
}}
.total {{
font-size: 18px;
font-weight: bold;
}}
.status {{
padding: 5px 10px;
border-radius: 3px;
display: inline-block;
color: white;
font-weight: bold;
}}
.draft {{ background-color: #f39c12; }}
.sent {{ background-color: #3498db; }}
.paid {{ background-color: #2ecc71; }}
</style>
</head>
<body>
<div class="invoice-header">
<div>
<div class="invoice-title">INVOICE</div>
<div>#{invoice.invoice_number}</div>
</div>
<div>
<div class="status {invoice.status.lower()}">{invoice.status.upper()}</div>
</div>
</div>
<div class="company-details">
<h3>From:</h3>
<div>{user.full_name}</div>
<div>{user.email}</div>
<!-- Add more user/company details as needed -->
</div>
<div class="client-details">
<h3>To:</h3>
<div>{client.name}</div>
<div>{client.company_name or ""}</div>
<div>{client.email}</div>
<div>{client.address or ""}</div>
</div>
<div class="invoice-meta">
<div><strong>Invoice Date:</strong> {issued_date}</div>
<div><strong>Due Date:</strong> {due_date}</div>
</div>
<table>
<thead>
<tr>
<th>Description</th>
<th>Quantity</th>
<th>Unit Price</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
{"".join(f"<tr><td>{item.description}</td><td>{item.quantity}</td><td>${item.unit_price:.2f}</td><td>${item.quantity * item.unit_price:.2f}</td></tr>" for item in items)}
</tbody>
</table>
<div class="totals">
<div><strong>Subtotal:</strong> ${subtotal:.2f}</div>
<!-- Add tax calculations if needed -->
<div class="total"><strong>Total:</strong> ${total:.2f}</div>
</div>
<div style="margin-top: 40px; font-size: 12px; color: #777; text-align: center;">
Thank you for your business!
</div>
</body>
</html>
"""