126 lines
4.8 KiB
Python
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() |