refactor: Complete overhaul of the pdf_exporter
This commit is contained in:
parent
a849b79a98
commit
4b025b8517
1 changed files with 82 additions and 101 deletions
183
pdf_exporter.py
183
pdf_exporter.py
|
|
@ -1,115 +1,96 @@
|
||||||
# timelogix/pdf_exporter.py
|
|
||||||
import datetime
|
|
||||||
from reportlab.lib.pagesizes import letter
|
from reportlab.lib.pagesizes import letter
|
||||||
from reportlab.pdfgen import canvas
|
from reportlab.pdfgen import canvas
|
||||||
from reportlab.lib import colors
|
|
||||||
from reportlab.platypus import Table, TableStyle
|
|
||||||
from reportlab.lib.styles import getSampleStyleSheet
|
|
||||||
from reportlab.lib.units import inch
|
from reportlab.lib.units import inch
|
||||||
|
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, Preformatted
|
||||||
|
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||||
|
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT
|
||||||
|
from reportlab.lib import colors
|
||||||
|
import datetime
|
||||||
|
|
||||||
class PDFExporter:
|
class PDFExporter:
|
||||||
def __init__(self):
|
def __init__(self, company_name, company_address, client_name, client_address, hourly_rate, invoice_number):
|
||||||
self.company_name = "Your Company Name"
|
self.company_name = company_name
|
||||||
self.company_address = "123 Main St, Anytown, USA"
|
self.company_address = company_address
|
||||||
self.client_name = "Client Name"
|
self.client_name = client_name
|
||||||
self.client_address = "Client Address"
|
self.client_address = client_address
|
||||||
self.hourly_rate = 60.00
|
self.hourly_rate = hourly_rate
|
||||||
self.invoice_number = 1
|
self.invoice_number = invoice_number
|
||||||
|
|
||||||
def export_to_pdf(self, log_entries):
|
def export_to_pdf(self, log_entries, filename):
|
||||||
try:
|
doc = SimpleDocTemplate(filename, pagesize=letter)
|
||||||
filename = f"invoice_{self.invoice_number}.pdf"
|
styles = getSampleStyleSheet()
|
||||||
c = canvas.Canvas(filename, pagesize=letter)
|
|
||||||
styles = getSampleStyleSheet()
|
header_table_data = [
|
||||||
|
[
|
||||||
|
Preformatted(f"BILL FROM:\n{self.company_name}\n{self.company_address}\n", styles['Normal']),
|
||||||
|
Paragraph(f"INVOICE # {self.invoice_number}", styles['Heading1']),
|
||||||
|
|
||||||
# --- Header ---
|
],
|
||||||
c.setFont("Helvetica-Bold", 16)
|
[
|
||||||
c.drawString(inch, 7.5 * inch, self.company_name)
|
Preformatted(f"BILL TO:\n{self.client_name}\n{self.client_address}\n", styles['Normal']),
|
||||||
c.setFont("Helvetica", 10)
|
Paragraph(f"Invoice Date: {datetime.date.today().strftime('%m/%d/%Y')}", styles['Normal']),
|
||||||
c.drawString(inch, 7.3 * inch, self.company_address)
|
],
|
||||||
|
]
|
||||||
|
|
||||||
c.setFont("Helvetica-Bold", 12)
|
header_table_style = TableStyle([
|
||||||
c.drawString(4.5 * inch, 7.5 * inch, "Invoice")
|
('ALIGN', (0, 0), (0, 0), 'LEFT'),
|
||||||
c.setFont("Helvetica", 10)
|
('ALIGN', (1, 0), (1, 0), 'RIGHT'),
|
||||||
c.drawString(
|
('ALIGN', (0, 1), (0, 1), 'LEFT'),
|
||||||
4.5 * inch, 7.3 * inch, f"Invoice Number: {self.invoice_number}"
|
('ALIGN', (1, 1), (1, 1), 'LEFT'),
|
||||||
)
|
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
||||||
current_date = datetime.datetime.now().strftime("%Y-%m-%d")
|
('TOPPADDING', (0, 0), (-1, -1), 0),
|
||||||
c.drawString(4.5 * inch, 7.1 * inch, f"Date: {current_date}")
|
('BOTTOMPADDING', (0, 0), (-1, -1), 0),
|
||||||
|
('LEFTPADDING', (0, 0), (-1, -1), 0),
|
||||||
|
('RIGHTPADDING', (0, 0), (-1, -1), 0),
|
||||||
|
])
|
||||||
|
|
||||||
# --- Client Info ---
|
header_table = Table(header_table_data, colWidths=[3*inch, 3*inch])
|
||||||
bill_to_y = 6.5 * inch # Starting y position for "Bill To:"
|
header_table.setStyle(header_table_style)
|
||||||
line_height = 0.2 * inch # Height for each line of text
|
|
||||||
|
|
||||||
c.setFont("Helvetica-Bold", 12)
|
data = [["Date", "Project", "Hours", "Rate", "Total"]]
|
||||||
c.drawString(inch, bill_to_y, "Bill To:") # "Bill To:" label
|
total_amount = 0
|
||||||
|
for entry in log_entries:
|
||||||
|
hours = entry["duration"] / 3600
|
||||||
|
line_total = hours * self.hourly_rate
|
||||||
|
total_amount += line_total
|
||||||
|
data.append([
|
||||||
|
entry["start_time"].split(' ')[0],
|
||||||
|
entry["project"],
|
||||||
|
f"{hours:.2f}",
|
||||||
|
f"${self.hourly_rate:.2f}",
|
||||||
|
f"${line_total:.2f}",
|
||||||
|
])
|
||||||
|
|
||||||
|
table_style = 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'),
|
||||||
|
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
||||||
|
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
|
||||||
|
('GRID', (0, 0), (-1, -1), 1, colors.black),
|
||||||
|
])
|
||||||
|
table = Table(data)
|
||||||
|
table.setStyle(table_style)
|
||||||
|
|
||||||
c.setFont("Helvetica", 10)
|
totals_style = ParagraphStyle(name='Totals', fontSize=10, alignment=TA_RIGHT)
|
||||||
c.drawString(
|
subtotal = Paragraph(f"Subtotal: ${total_amount:.2f}", totals_style)
|
||||||
inch, bill_to_y - line_height, self.client_name
|
tax = Paragraph(f"Tax (0%): $0.00", totals_style)
|
||||||
) # Client Name
|
total = Paragraph(f"Total: ${total_amount:.2f}", totals_style)
|
||||||
c.drawString(
|
|
||||||
inch, bill_to_y - 2 * line_height, self.client_address
|
|
||||||
) # Client Address
|
|
||||||
|
|
||||||
# --- Table ---
|
notes_style = ParagraphStyle(name='Notes', fontSize=10, alignment=TA_LEFT)
|
||||||
data = [["Task", "Project", "Hours", "Rate", "Total"]]
|
notes = Paragraph("Notes:\nThank you for your business!", notes_style)
|
||||||
total_amount = 0
|
|
||||||
|
|
||||||
for entry in log_entries:
|
elements = [
|
||||||
hours = entry["duration"] / 3600
|
header_table,
|
||||||
line_total = hours * self.hourly_rate
|
Spacer(1, 12),
|
||||||
total_amount += line_total
|
table,
|
||||||
data.append(
|
Spacer(1, 12),
|
||||||
[
|
subtotal,
|
||||||
entry["task"],
|
tax,
|
||||||
entry["project"],
|
total,
|
||||||
f"{hours:.2f}",
|
Spacer(1, 12),
|
||||||
f"${self.hourly_rate:.2f}",
|
notes,
|
||||||
f"${line_total:.2f}",
|
]
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
table = Table(data, colWidths=[1.5 * inch, 1.5 * inch, inch, inch, inch])
|
doc.build(elements)
|
||||||
style = 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"),
|
|
||||||
("BOTTOMPADDING", (0, 0), (-1, 0), 12),
|
|
||||||
("BACKGROUND", (0, 1), (-1, -1), colors.beige),
|
|
||||||
("GRID", (0, 0), (-1, -1), 1, colors.black),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
table.setStyle(style)
|
|
||||||
table.wrapOn(c, letter[0] - 2 * inch, letter[1] - 2 * inch)
|
|
||||||
table.drawOn(c, inch, 4 * inch)
|
|
||||||
|
|
||||||
# --- Totals ---
|
|
||||||
c.setFont("Helvetica-Bold", 12)
|
|
||||||
c.drawString(4 * inch, 3.5 * inch, "Subtotal:")
|
|
||||||
c.setFont("Helvetica", 12)
|
|
||||||
c.drawRightString(5.5 * inch, 3.5 * inch, f"${total_amount:.2f}")
|
|
||||||
|
|
||||||
c.setFont("Helvetica-Bold", 12)
|
|
||||||
c.drawString(4 * inch, 3.3 * inch, "Tax (0%):")
|
|
||||||
c.setFont("Helvetica", 12)
|
|
||||||
c.drawRightString(5.5 * inch, 3.3 * inch, "$0.00")
|
|
||||||
|
|
||||||
c.setFont("Helvetica-Bold", 12)
|
|
||||||
c.drawString(4 * inch, 3.1 * inch, "Total:")
|
|
||||||
c.setFont("Helvetica", 12)
|
|
||||||
c.drawRightString(5.5 * inch, 3.1 * inch, f"${total_amount:.2f}")
|
|
||||||
|
|
||||||
# --- Notes ---
|
|
||||||
c.setFont("Helvetica", 10)
|
|
||||||
c.drawString(inch, 2 * inch, "Notes:")
|
|
||||||
c.drawString(inch, 1.8 * inch, "Thank you for your business!")
|
|
||||||
|
|
||||||
c.save()
|
|
||||||
print(f"Exported to PDF successfully as {filename}!")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error exporting to PDF: {e}")
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue