126 lines
4.8 KiB
Python

from io import BytesIO
from reportlab.lib.pagesizes import letter
from reportlab.lib.units import inch
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
from reportlab.lib import colors
from app.models.invoice import Invoice
class PDFGenerator:
def generate_invoice_pdf(self, invoice: Invoice) -> BytesIO:
buffer = BytesIO()
doc = SimpleDocTemplate(buffer, pagesize=letter)
styles = getSampleStyleSheet()
story = []
title_style = ParagraphStyle(
'CustomTitle',
parent=styles['Heading1'],
fontSize=24,
spaceAfter=30,
alignment=TA_CENTER,
)
header_style = ParagraphStyle(
'Header',
parent=styles['Normal'],
fontSize=12,
spaceAfter=6,
)
story.append(Paragraph("INVOICE", title_style))
story.append(Spacer(1, 20))
invoice_info = [
["Invoice Number:", invoice.invoice_number],
["Issue Date:", invoice.issue_date.strftime("%Y-%m-%d")],
["Due Date:", invoice.due_date.strftime("%Y-%m-%d")],
["Status:", invoice.status.title()],
]
invoice_table = Table(invoice_info, colWidths=[2*inch, 3*inch])
invoice_table.setStyle(TableStyle([
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, -1), 10),
('BOTTOMPADDING', (0, 0), (-1, -1), 6),
]))
story.append(invoice_table)
story.append(Spacer(1, 20))
if invoice.user.company_name:
story.append(Paragraph(f"<b>From:</b> {invoice.user.company_name}", header_style))
story.append(Paragraph(f"<b>Billed by:</b> {invoice.user.full_name}", header_style))
story.append(Paragraph(f"<b>Email:</b> {invoice.user.email}", header_style))
story.append(Spacer(1, 10))
story.append(Paragraph("<b>Bill To:</b>", header_style))
story.append(Paragraph(f"<b>Customer:</b> {invoice.customer.name}", header_style))
story.append(Paragraph(f"<b>Email:</b> {invoice.customer.email}", header_style))
if invoice.customer.address:
story.append(Paragraph(f"<b>Address:</b> {invoice.customer.address}", header_style))
if invoice.customer.city:
story.append(Paragraph(f"<b>City:</b> {invoice.customer.city}", header_style))
if invoice.customer.country:
story.append(Paragraph(f"<b>Country:</b> {invoice.customer.country}", header_style))
story.append(Spacer(1, 20))
if invoice.items:
items_data = [["Description", "Quantity", "Unit Price", "Total"]]
for item in invoice.items:
items_data.append([
item.description,
str(item.quantity),
f"${item.unit_price:.2f}",
f"${item.total_price:.2f}"
])
items_table = Table(items_data, colWidths=[3*inch, 1*inch, 1.5*inch, 1.5*inch])
items_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 10),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
('GRID', (0, 0), (-1, -1), 1, colors.black),
('ALIGN', (1, 1), (-1, -1), 'RIGHT'),
('ALIGN', (0, 1), (0, -1), 'LEFT'),
]))
story.append(items_table)
story.append(Spacer(1, 20))
totals_data = [
["Subtotal:", f"${invoice.subtotal:.2f}"],
[f"Tax ({invoice.tax_rate}%):", f"${invoice.tax_amount:.2f}"],
["Total:", f"${invoice.total_amount:.2f}"],
]
totals_table = Table(totals_data, colWidths=[4*inch, 2*inch])
totals_table.setStyle(TableStyle([
('ALIGN', (0, 0), (-1, -1), 'RIGHT'),
('FONTNAME', (0, -1), (-1, -1), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, -1), 10),
('BOTTOMPADDING', (0, 0), (-1, -1), 6),
('LINEBELOW', (0, -1), (-1, -1), 2, colors.black),
]))
story.append(totals_table)
if invoice.notes:
story.append(Spacer(1, 20))
story.append(Paragraph("<b>Notes:</b>", header_style))
story.append(Paragraph(invoice.notes, styles['Normal']))
doc.build(story)
buffer.seek(0)
return buffer
pdf_generator = PDFGenerator()