From 3cd30645fdef975a92a39c93c8c425c4d151128c Mon Sep 17 00:00:00 2001 From: mattspeer Date: Sat, 9 May 2026 09:05:58 -0500 Subject: [PATCH] Delete create_invoice.py --- create_invoice.py | 274 ---------------------------------------------- 1 file changed, 274 deletions(-) delete mode 100644 create_invoice.py diff --git a/create_invoice.py b/create_invoice.py deleted file mode 100644 index 6fb560e..0000000 --- a/create_invoice.py +++ /dev/null @@ -1,274 +0,0 @@ -import sqlite3 -import os -from datetime import date, datetime -from reportlab.lib.pagesizes import letter -from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer -from reportlab.lib import colors -from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle - -def connect_db(): - """ - Connects to the SQLite database file and returns the connection object. - If the file does not exist, it prints an an error message. - """ - db_file = 'time_tracker.db' - if not os.path.exists(db_file): - print(f"Error: Database file '{db_file}' not found. Please run the database creation script first.") - return None - return sqlite3.connect(db_file) - -def generate_invoice(): - """ - Guides the user through selecting a client and month to generate a PDF invoice. - Calculates costs, creates the PDF, and updates the database. - """ - conn = connect_db() - if not conn: - return - - try: - cursor = conn.cursor() - - # Display active clients for selection - cursor.execute('SELECT client_id, client_name FROM clients WHERE active = 1 ORDER BY client_name') - active_clients = cursor.fetchall() - - if not active_clients: - print("No active clients found to generate an invoice for.") - return - - print("\n--- Select a Client to Invoice ---") - for client_id, client_name in active_clients: - print(f"{client_id}: {client_name}") - print("0: Exit") - print("----------------------------------\n") - - while True: - try: - choice = input("Enter the ID of the client (or 0 to exit): ") - if choice == '0': - print("Exiting invoice generation.") - return - - client_id = int(choice) - # Check if the entered ID is in the list of active clients - if any(c[0] == client_id for c in active_clients): - break - else: - print("Invalid client ID. Please enter a valid ID from the list.") - except ValueError: - print("Invalid input. Please enter a number.") - - # Get the month from the user - month_year_str = input("Enter the month to invoice (YYYY-MM): ") - if len(month_year_str) != 7 or month_year_str[4] != '-': - print("Invalid date format. Please use YYYY-MM.") - return - - # Fetch client details and billing rate - cursor.execute('SELECT * FROM clients WHERE client_id = ?', (client_id,)) - client_data = cursor.fetchone() - client_name, billing_rate = client_data[1], client_data[7] - - # Fetch time entries for the selected client and month - cursor.execute(''' - SELECT date, hours, description, project, entry_id - FROM time_tracking - WHERE client_id = ? AND date LIKE ? AND invoiced = 0 - ORDER BY date - ''', (client_id, f"{month_year_str}%")) - - time_entries = cursor.fetchall() - - if not time_entries: - print("No new time entries found for this client and month.") - return - - # Group entries by date and calculate daily totals - daily_summary = {} - for entry in time_entries: - entry_date = entry[0] - if entry_date not in daily_summary: - daily_summary[entry_date] = {'hours': 0.0, 'descriptions': [], 'projects': [], 'entry_ids': []} - - daily_summary[entry_date]['hours'] += entry[1] - daily_summary[entry_date]['descriptions'].append(f"{entry[2]}") - daily_summary[entry_date]['projects'].append(f"{entry[3]}") - daily_summary[entry_date]['entry_ids'].append(entry[4]) - - # Prepare data for PDF and calculate invoice total - invoice_total = 0.0 - # Reorder table headers as requested - data_for_pdf = [['Date', 'Description', 'Project', 'Hours', 'Rate', 'Amount']] - - for daily_date, summary in daily_summary.items(): - daily_hours = summary['hours'] - daily_total = daily_hours * billing_rate - invoice_total += daily_total - # Join descriptions and projects with
for ReportLab to create new lines - descriptions_str = "
".join(summary['descriptions']) - projects_str = "
".join(summary['projects']) - - # Use Paragraph to handle multi-line descriptions and projects in the PDF - data_for_pdf.append([ - daily_date, - Paragraph(descriptions_str, getSampleStyleSheet()['Normal']), - Paragraph(projects_str, getSampleStyleSheet()['Normal']), - f"{daily_hours:.2f}", - f"${billing_rate:.2f}", - f"${daily_total:.2f}" - ]) - - # Display the invoice summary before creating the PDF - print("\n--- Invoice Summary ---") - for row in data_for_pdf[1:]: # Skip header row for print - print(f"Date: {row[0]}, Hours: {row[3]}, Total: {row[5]}") - print(f"\nTotal Invoice Amount: ${invoice_total:.2f}") - - # Confirmation to create the PDF - confirm = input("Generate PDF and update database? (yes/no): ").lower() - if confirm not in ['yes', 'y']: - print("Invoice generation cancelled.") - return - - # Get the desired save directory from the user with a default value - default_dir = os.path.expanduser("~/Documents/0 - Inbox") - save_dir = input(f"Enter directory to save PDF (default: {default_dir}): ") - if not save_dir: - save_dir = default_dir - - # Ensure the directory exists - if not os.path.exists(save_dir): - os.makedirs(save_dir) - print(f"Created directory: {save_dir}") - - # Generate unique invoice ID and full file path - invoice_id = datetime.now().strftime('%Y%m%d%H%M%S') - file_name = f"Invoice_{invoice_id}_{client_name.replace(' ', '_')}.pdf" - full_file_path = os.path.join(save_dir, file_name) - - # Create PDF and update database - create_pdf(full_file_path, client_data, invoice_total, data_for_pdf, invoice_id) - - # Update database - update_database_after_invoice(conn, cursor, time_entries, client_id, invoice_total) - - print(f"\nInvoice successfully created at '{full_file_path}' and database has been updated.") - - except sqlite3.Error as e: - print(f"An error occurred: {e}") - finally: - if conn: - conn.close() - -def create_pdf(file_name, client_data, invoice_total, data_for_pdf, invoice_id): - """ - Creates the PDF document with the invoice details. - """ - doc = SimpleDocTemplate(file_name, pagesize=letter) - elements = [] - - # Define styles for the document - styles = getSampleStyleSheet() - styles.add(ParagraphStyle(name='InvoiceTitle', fontSize=24, fontName='Helvetica-Bold')) - styles.add(ParagraphStyle(name='ClientInfo', fontSize=12)) - styles.add(ParagraphStyle(name='AmountDue', fontSize=12, alignment=2, fontName='Helvetica-Bold')) - - today_date_str = date.today().strftime('%m/%d/%Y') - - # Top header table (INVOICE and From) - header_top_table = Table([ - [ - Paragraph("INVOICE", styles['InvoiceTitle']), - Paragraph("From:
Matt Speer
2313 Hunters Cove
Vestavia Hills, AL 35216", styles['ClientInfo']) - ] - ], colWidths=[250, 250]) - - header_top_table.setStyle(TableStyle([ - ('VALIGN', (0, 0), (-1, -1), 'TOP'), - ('LEFTPADDING', (0, 0), (-1, -1), 0), - ('RIGHTPADDING', (0, 0), (-1, -1), 0), - ('ALIGN', (1, 0), (1, 0), 'RIGHT') - ])) - - elements.append(header_top_table) - elements.append(Spacer(1, 20)) - - # Construct the client address string conditionally - client_address_str = f"Invoice For:
{client_data[1]}
{client_data[2]}" - if client_data[3]: # Check if street_address_2 is not empty - client_address_str += f"
{client_data[3]}" - client_address_str += f"
{client_data[4]}, {client_data[5]} {client_data[6]}" - - # Bottom header table (Invoice ID/Date and Invoice For) - header_bottom_table = Table([ - [ - Paragraph(f"Invoice ID: {invoice_id}

Invoice Date: {today_date_str}", styles['ClientInfo']), - Paragraph(client_address_str, styles['ClientInfo']) - ] - ], colWidths=[250, 250]) - - header_bottom_table.setStyle(TableStyle([ - ('VALIGN', (0, 0), (-1, -1), 'TOP'), - ('LEFTPADDING', (0, 0), (-1, -1), 0), - ('RIGHTPADDING', (0, 0), (-1, -1), 0), - ('ALIGN', (1, 0), (1, 0), 'RIGHT') - ])) - - elements.append(header_bottom_table) - elements.append(Spacer(1, 20)) - - # Create the table for time entries - table = Table(data_for_pdf, colWidths=[80, 200, 80, 40, 50, 50]) - table.setStyle(TableStyle([ - ('TEXTCOLOR', (0, 0), (-1, 0), colors.black), - ('ALIGN', (0, 0), (-1, -1), 'LEFT'), - ('ALIGN', (0, 0), (-1, -0), 'LEFT'), - ('VALIGN', (0, 0), (-1, 0), 'MIDDLE'), # Vertical align the header to the middle - ('VALIGN', (0, 1), (-1, -1), 'TOP'), - ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), - ('FONTSIZE', (0, 0), (-1, 0), 10), - ('BOTTOMPADDING', (0, 0), (-1, 0), 12), - ('LINEABOVE', (0, 0), (-1, 0), 1, colors.black), - ('LINEBELOW', (0, 0), (-1, 0), 1, colors.black), - ])) - - elements.append(table) - elements.append(Spacer(1, 12)) - - # Line before the total amount due - line_table = Table([['']], colWidths=[500]) # Set width to match the time entries table - line_table.setStyle(TableStyle([ - ('LINEBELOW', (0, 0), (-1, -1), 1, colors.black), - ('BOTTOMPADDING', (0, 0), (-1, -1), 0) - ])) - elements.append(line_table) - - elements.append(Spacer(1, 12)) - - # Add the total amount due section after the table - elements.append(Paragraph(f"AMOUNT DUE: ${invoice_total:.2f}", styles['AmountDue'])) - - # Build the document - doc.build(elements) - -def update_database_after_invoice(conn, cursor, time_entries, client_id, invoice_total): - """ - Updates the 'invoiced' field for time entries and the client's balance. - """ - entry_ids = [entry[4] for entry in time_entries] - - # Update time entries to be invoiced - cursor.executemany('UPDATE time_tracking SET invoiced = 1 WHERE entry_id = ?', [(entry_id,) for entry_id in entry_ids]) - - # Update the client's balance - cursor.execute('SELECT balance FROM clients WHERE client_id = ?', (client_id,)) - current_balance = cursor.fetchone()[0] - - new_balance = current_balance - invoice_total - cursor.execute('UPDATE clients SET balance = ? WHERE client_id = ?', (new_balance, client_id)) - - conn.commit() - -if __name__ == "__main__": - generate_invoice()