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()