Compare commits

7 Commits

Author SHA1 Message Date
mattspeer 6c1b55158d Delete track_time.py 2026-05-09 09:06:36 -05:00
mattspeer ab046271cf Delete reports.py 2026-05-09 09:06:31 -05:00
mattspeer 975b54b749 Delete manage_clients.py 2026-05-09 09:06:23 -05:00
mattspeer 52239d00af Delete main.py 2026-05-09 09:06:18 -05:00
mattspeer ffef627a4a Delete edit_time.py 2026-05-09 09:06:13 -05:00
mattspeer 910a3bc600 Delete db_build.py 2026-05-09 09:06:06 -05:00
mattspeer 3cd30645fd Delete create_invoice.py 2026-05-09 09:05:58 -05:00
7 changed files with 0 additions and 1030 deletions
-274
View File
@@ -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 <br/> for ReportLab to create new lines
descriptions_str = "<br/>".join(summary['descriptions'])
projects_str = "<br/>".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("<b>From:</b><br/>Matt Speer<br/>2313 Hunters Cove<br/>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"<b>Invoice For:</b><br/>{client_data[1]}<br/>{client_data[2]}"
if client_data[3]: # Check if street_address_2 is not empty
client_address_str += f"<br/>{client_data[3]}"
client_address_str += f"<br/>{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}<br/><br/>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()
-65
View File
@@ -1,65 +0,0 @@
import sqlite3
def create_database():
"""
Connects to the SQLite database and creates the 'clients' and 'time_tracking' tables
if they do not already exist.
"""
db_file = 'time_tracker.db' # Define the database file name
conn = None # Initialize the connection variable to None
try:
# Connect to the database. This will create the file if it doesn't exist.
conn = sqlite3.connect(db_file)
cursor = conn.cursor()
# Create the 'clients' table. The 'address' field has been split into
# separate fields for better data organization.
cursor.execute('''
CREATE TABLE IF NOT EXISTS clients (
client_id INTEGER PRIMARY KEY,
client_name TEXT NOT NULL UNIQUE,
street_address_1 TEXT,
street_address_2 TEXT,
city TEXT,
state TEXT,
zip_code TEXT,
billing_rate REAL,
balance REAL,
active INTEGER NOT NULL DEFAULT 1
);
''')
# Create the 'time_tracking' table with new fields for date and hours.
# The 'invoiced' field is an INTEGER where 0 is false and 1 is true.
# The 'client_id' is a foreign key that references the 'clients' table.
cursor.execute('''
CREATE TABLE IF NOT EXISTS time_tracking (
entry_id INTEGER PRIMARY KEY,
project TEXT NOT NULL,
description TEXT,
invoiced INTEGER NOT NULL DEFAULT 0 CHECK(invoiced IN (0, 1)),
date TEXT NOT NULL,
hours REAL NOT NULL,
client_id INTEGER,
FOREIGN KEY (client_id) REFERENCES clients (client_id)
);
''')
# Commit the changes to the database.
conn.commit()
print(f"Successfully created tables 'clients' and 'time_tracking' in '{db_file}'")
except sqlite3.Error as e:
# Print an error message if something goes wrong.
print(f"An error occurred: {e}")
finally:
# Ensure the database connection is always closed, even if an error occurs.
if conn:
conn.close()
print("Database connection closed.")
if __name__ == "__main__":
# Call the function to create the database when the script is run directly.
create_database()
-90
View File
@@ -1,90 +0,0 @@
import sqlite3
import os
def connect_db():
db_file = 'time_tracker.db'
if not os.path.exists(db_file):
print(f"Error: Database file '{db_file}' not found.")
return None
return sqlite3.connect(db_file)
def display_entry_details(entry):
print("\n--- Current Entry Details ---")
print(f"ID: {entry[0]} | Project: {entry[1]} | Date: {entry[4]}")
print(f"Description: {entry[2]}")
print(f"Hours: {entry[5]}")
print("-" * 30)
def edit_time_entry():
conn = connect_db()
if not conn: return
cursor = conn.cursor()
try:
# 1. Select Client
cursor.execute('SELECT client_id, client_name FROM clients ORDER BY client_name')
clients = cursor.fetchall()
print("\n--- Select Client to Edit Time ---")
for cid, name in clients:
print(f"{cid}: {name}")
client_id = input("\nEnter Client ID (or 0 to go back): ")
if client_id == '0' or not client_id: return
while True:
# 2. List Uninvoiced Entries for Client
cursor.execute('''SELECT entry_id, project, description, invoiced, date, hours
FROM time_tracking WHERE client_id = ? AND invoiced = 0
ORDER BY date DESC''', (client_id,))
entries = cursor.fetchall()
if not entries:
print("No uninvoiced entries found for this client.")
break
print(f"\n--- Time Entries for Client {client_id} ---")
for e in entries:
print(f"ID {e[0]}: [{e[4]}] {e[1]} - {e[5]} hrs")
print("0: Back to Main Menu")
entry_choice = input("\nSelect Entry ID to edit: ")
if entry_choice == '0': break
# Find the specific entry
selected_entry = next((e for e in entries if str(e[0]) == entry_choice), None)
if not selected_entry:
print("Invalid Entry ID.")
continue
# 3. Edit Fields one by one
display_entry_details(selected_entry)
# Project
new_project = input(f"Project [{selected_entry[1]}]: ") or selected_entry[1]
# Description
new_desc = input(f"Description [{selected_entry[2]}]: ") or selected_entry[2]
# Date
new_date = input(f"Date [{selected_entry[4]}]: ") or selected_entry[4]
# Hours
new_hours_raw = input(f"Hours [{selected_entry[5]}]: ")
new_hours = float(new_hours_raw) if new_hours_raw else selected_entry[5]
# 4. Update Database
cursor.execute('''UPDATE time_tracking
SET project = ?, description = ?, date = ?, hours = ?
WHERE entry_id = ?''',
(new_project, new_desc, new_date, new_hours, entry_choice))
conn.commit()
print("\nUpdate Successful! New Values:")
print(f"Project: {new_project}\nDescription: {new_desc}\nDate: {new_date}\nHours: {new_hours}")
print("="*30)
except Exception as e:
print(f"An error occurred: {e}")
finally:
conn.close()
if __name__ == "__main__":
edit_time_entry()
-73
View File
@@ -1,73 +0,0 @@
#!/usr/bin/env python3
import os
import sys
import subprocess
# It is assumed that the following scripts exist in the same directory:
# - manage_clients.py
# - track_time.py
# - create_invoice.py
# These modules will be run as separate processes.
def run_script_from_file(script_name):
"""
Runs a Python script as a subprocess.
Args:
script_name (str): The name of the Python file to run.
"""
# Check if the file exists
if not os.path.exists(script_name):
print(f"Error: The script '{script_name}' was not found.")
print("Please ensure all required script files are in the same directory.")
return
try:
# Run the script using subprocess.run
# This will execute the script's top-level code (including any code in
# its 'if __name__ == "__main__":' block).
print(f"\n--- Running {script_name} ---")
subprocess.run(['python3', script_name], check=True)
print(f"--- {script_name} completed ---")
except subprocess.CalledProcessError as e:
print(f"An error occurred while trying to run '{script_name}': {e}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
def main_menu():
"""
Displays the main menu and handles user input to run different scripts.
"""
while True:
print("\n" + "="*40)
print(" TIME TRACKER MAIN MENU")
print("="*40)
print("1. Manage Clients")
print("2. Track Time")
print("3. Edit Time") # New Option
print("4. Create Invoice")
print("5. Reports")
print("0. Exit")
print("="*40)
choice = input("Enter your choice (1-5, or 0): ")
if choice == '1':
run_script_from_file('manage_clients.py')
elif choice == '2':
run_script_from_file('track_time.py')
elif choice == '3':
run_script_from_file('edit_time.py') # Call new script
elif choice == '4':
run_script_from_file('create_invoice.py')
elif choice == '5':
run_script_from_file('reports.py')
elif choice == '0':
print("Exiting the Time Tracker. Goodbye!")
break
else:
print("Invalid choice. Please enter a number between 1 and 5, or 0 to exit.")
if __name__ == "__main__":
main_menu()
-289
View File
@@ -1,289 +0,0 @@
import sqlite3
import os
def connect_db():
"""
Connects to the SQLite database file and returns the connection object.
If the file does not exist, it prints 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 display_client_details(client_data):
"""
Displays the details of a single client in a readable format.
"""
print("\n--- Client Details ---")
print(f"ID: {client_data[0]}")
print(f"Name: {client_data[1]}")
print(f"Street Address 1: {client_data[2]}")
print(f"Street Address 2: {client_data[3]}")
print(f"City: {client_data[4]}")
print(f"State: {client_data[5]}")
print(f"Zip Code: {client_data[6]}")
print(f"Billing Rate: {client_data[7]:.2f}")
print(f"Balance: {client_data[8]:.2f}")
print(f"Active: {'Yes' if client_data[9] else 'No'}")
print("----------------------\n")
def add_new_client():
"""
Prompts the user for information to create a new client and adds it to the database.
"""
conn = connect_db()
if not conn:
return
try:
cursor = conn.cursor()
print("\n--- Add New Client ---")
# Prompt for each field
client_name = input("Enter client name: ")
street_address_1 = input("Enter street address 1: ")
street_address_2 = input("Enter street address 2 (optional): ")
city = input("Enter city: ")
state = input("Enter state: ")
zip_code = input("Enter zip code: ")
# Validate billing rate and balance
while True:
try:
billing_rate = float(input("Enter billing rate: "))
break
except ValueError:
print("Invalid input. Please enter a number for billing rate.")
while True:
try:
balance = float(input("Enter starting balance: "))
break
except ValueError:
print("Invalid input. Please enter a number for balance.")
# Active status is boolean, use 1 or 0
active_input = input("Is the client active? (yes/no): ").lower()
active = 1 if active_input in ['yes', 'y'] else 0
# Insert the new client into the database
cursor.execute('''
INSERT INTO clients (client_name, street_address_1, street_address_2, city, state, zip_code, billing_rate, balance, active)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (client_name, street_address_1, street_address_2, city, state, zip_code, billing_rate, balance, active))
conn.commit()
print("\nClient added successfully!")
# Fetch the newly created client to display it
last_id = cursor.lastrowid
cursor.execute('SELECT * FROM clients WHERE client_id = ?', (last_id,))
new_client = cursor.fetchone()
display_client_details(new_client)
except sqlite3.IntegrityError:
print("\nError: A client with that name already exists. Please choose a unique name.")
except sqlite3.Error as e:
print(f"\nAn error occurred: {e}")
finally:
if conn:
conn.close()
def edit_existing_client():
"""
Displays a list of clients and allows the user to select and edit one.
"""
conn = connect_db()
if not conn:
return
try:
cursor = conn.cursor()
cursor.execute('SELECT client_id, client_name FROM clients')
clients = cursor.fetchall()
if not clients:
print("No clients found to edit.")
return
print("\n--- Existing Clients ---")
for client_id, client_name in clients:
print(f"{client_id}: {client_name}")
print("----------------------\n")
while True:
try:
choice = input("Enter the ID of the client you want to edit (or 'back' to return to menu): ").strip().lower()
if choice == 'back':
return
client_id = int(choice)
cursor.execute('SELECT * FROM clients WHERE client_id = ?', (client_id,))
client_data = cursor.fetchone()
if client_data:
break
else:
print("Invalid client ID. Please try again.")
except ValueError:
print("Invalid input. Please enter a number or 'back'.")
display_client_details(client_data)
# The fields that can be edited, mapped to their database column names
field_map = {
'1': 'client_name',
'2': 'street_address_1',
'3': 'street_address_2',
'4': 'city',
'5': 'state',
'6': 'zip_code',
'7': 'billing_rate',
'8': 'balance',
'9': 'active'
}
print("\n--- Select a Field to Edit ---")
print("1: Client Name")
print("2: Street Address 1")
print("3: Street Address 2")
print("4: City")
print("5: State")
print("6: Zip Code")
print("7: Billing Rate")
print("8: Balance")
print("9: Active Status")
print("0: Cancel and Return to Main Menu")
while True:
edit_choice = input("Enter the number of the field you want to edit: ")
if edit_choice == '0':
print("Edit cancelled.")
return
column_name = field_map.get(edit_choice)
if column_name:
break
else:
print("Invalid choice. Please enter a valid number.")
# Get the new value from the user, prepopulating with old value
current_value_index = list(field_map.keys()).index(edit_choice) + 1
# Handle special cases for data types
if column_name in ['billing_rate', 'balance']:
new_value_str = input(f"Enter new value for {column_name.replace('_', ' ')} (current: {client_data[current_value_index]}): ")
if not new_value_str:
new_value = client_data[current_value_index]
else:
try:
new_value = float(new_value_str)
except ValueError:
print("Invalid input. Please enter a number.")
return
elif column_name == 'active':
new_value_input = input(f"Is the client active? (yes/no, current: {'Yes' if client_data[current_value_index] else 'No'}): ").lower()
if not new_value_input:
new_value = client_data[current_value_index]
else:
new_value = 1 if new_value_input in ['yes', 'y'] else 0
else:
new_value_input = input(f"Enter new value for {column_name.replace('_', ' ')} (current: {client_data[current_value_index]}): ")
if not new_value_input:
new_value = client_data[current_value_index]
else:
new_value = new_value_input
# Update the database
cursor.execute(f"UPDATE clients SET {column_name} = ? WHERE client_id = ?", (new_value, client_id))
conn.commit()
print("\nClient information updated successfully!")
# Fetch and display the updated client details
cursor.execute('SELECT * FROM clients WHERE client_id = ?', (client_id,))
updated_client = cursor.fetchone()
display_client_details(updated_client)
except sqlite3.Error as e:
print(f"\nAn error occurred: {e}")
finally:
if conn:
conn.close()
def display_client_info():
"""
Displays a list of clients and allows the user to select one to view its details.
"""
conn = connect_db()
if not conn:
return
try:
cursor = conn.cursor()
cursor.execute('SELECT client_id, client_name FROM clients ORDER BY client_name')
clients = cursor.fetchall()
if not clients:
print("No clients found.")
return
print("\n--- Existing Clients ---")
for client_id, client_name in clients:
print(f"{client_id}: {client_name}")
print("----------------------\n")
while True:
try:
choice = input("Enter the ID of the client you want to view (or 'back' to return to menu): ").strip().lower()
if choice == 'back':
return
client_id = int(choice)
cursor.execute('SELECT * FROM clients WHERE client_id = ?', (client_id,))
client_data = cursor.fetchone()
if client_data:
display_client_details(client_data)
break
else:
print("Invalid client ID. Please try again.")
except ValueError:
print("Invalid input. Please enter a number or 'back'.")
except sqlite3.Error as e:
print(f"An error occurred: {e}")
finally:
if conn:
conn.close()
def main_menu():
"""
Displays the main menu and handles user choices.
"""
while True:
print("\n--- Time Tracker Client Manager ---")
print("1. Add a new client")
print("2. Display client information")
print("3. Edit an existing client")
print("0. Exit")
choice = input("Enter your choice: ").strip()
if choice == '1':
add_new_client()
elif choice == '2':
display_client_info()
elif choice == '3':
edit_existing_client()
elif choice == '0':
print("Exiting. Goodbye!")
break
else:
print("Invalid choice. Please try again.")
if __name__ == "__main__":
main_menu()
-125
View File
@@ -1,125 +0,0 @@
import sqlite3
import os
from datetime import datetime
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 unbilled_time_report():
"""
Generates a report of unbilled time entries for a selected client.
"""
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 a report for.")
return
print("\n--- Select a Client for Unbilled Time Report ---")
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 report generation.")
return
client_id = int(choice)
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.")
# Fetch client details and billing rate
cursor.execute('SELECT client_name, billing_rate FROM clients WHERE client_id = ?', (client_id,))
client_name, billing_rate = cursor.fetchone()
# Fetch unbilled time entries for the selected client, including description
cursor.execute('''
SELECT date, hours, description, project
FROM time_tracking
WHERE client_id = ? AND invoiced = 0
ORDER BY date
''', (client_id,))
time_entries = cursor.fetchall()
if not time_entries:
print(f"\nNo unbilled time entries found for {client_name}.")
return
# Calculate daily summary, descriptions, and total cost
daily_summary = {}
grand_total_cost = 0.0
for entry_date, hours, description, project in time_entries:
if entry_date not in daily_summary:
daily_summary[entry_date] = {'hours': 0.0, 'cost': 0.0, 'entries': []}
daily_summary[entry_date]['hours'] += hours
daily_summary[entry_date]['cost'] += hours * billing_rate
daily_summary[entry_date]['entries'].append({'description': description, 'project': project})
grand_total_cost += hours * billing_rate
# Print the formatted report
print(f"\n--- Unbilled Time Report for {client_name} ---")
print(f"Total Unbilled Cost: ${grand_total_cost:.2f}\n")
for daily_date, summary in daily_summary.items():
print(f"Date: {daily_date} | Hours: {summary['hours']:.2f} | Cost: ${summary['cost']:.2f}")
for entry in summary['entries']:
print(f" - Project: {entry['project']}, Description: {entry['description']}")
print("-" * 32)
print(f"\nReport generated on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
except sqlite3.Error as e:
print(f"An error occurred: {e}")
finally:
if conn:
conn.close()
def main_menu():
"""
Displays the main menu and handles user choices.
"""
while True:
print("\n--- Reports Menu ---")
print("1: Unbilled Time")
print("0: Exit")
print("--------------------\n")
choice = input("Enter your choice: ")
if choice == '1':
unbilled_time_report()
elif choice == '0':
print("Exiting reports script. Goodbye!")
break
else:
print("Invalid choice. Please try again.")
if __name__ == "__main__":
main_menu()
-114
View File
@@ -1,114 +0,0 @@
import sqlite3
import os
from datetime import date
def connect_db():
"""
Connects to the SQLite database file and returns the connection object.
If the file does not exist, it prints 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 add_time_entry():
"""
Prompts the user for details and adds a new time entry to the database.
"""
conn = connect_db()
if not conn:
return
try:
cursor = conn.cursor()
# Display all 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. Please add an active client first.")
return
print("\n--- Select a Client ---")
for client_id, client_name in active_clients:
print(f"{client_id}: {client_name}")
print("0: Exit") # Added exit option
print("----------------------")
while True:
try:
choice = int(input("Enter the ID of the client (or 0 to exit): "))
except ValueError:
print("Invalid input. Please enter a number.")
continue
if choice == 0:
print("Exiting time entry.")
return # Exit the function if the user chooses 0
# Check if the entered ID is in the list of active clients
if any(c[0] == choice for c in active_clients):
client_id = choice
break
else:
print("Invalid client ID. Please enter a valid ID from the list.")
print(f"\n--- Add New Time Entry for Client ID {client_id} ---")
project = input("Enter project name: ")
description = input("Enter a brief description: ")
# Get the hours and validate it's a number
while True:
try:
hours = float(input("Enter number of hours (e.g., 1.5): "))
if hours <= 0:
print("Hours must be a positive number.")
else:
break
except ValueError:
print("Invalid input. Please enter a number for hours.")
# Get the date, defaulting to today's date
entry_date = input(f"Enter the date (YYYY-MM-DD, default is today: {date.today()}): ")
if not entry_date:
entry_date = str(date.today())
# The 'invoiced' field defaults to 0 (false)
invoiced = 0
# Insert the new time entry into the database
cursor.execute('''
INSERT INTO time_tracking (project, description, invoiced, date, hours, client_id)
VALUES (?, ?, ?, ?, ?, ?)
''', (project, description, invoiced, entry_date, hours, client_id))
conn.commit()
print("\nTime entry added successfully!")
# Fetch and display the newly created entry
last_id = cursor.lastrowid
cursor.execute('SELECT * FROM time_tracking WHERE entry_id = ?', (last_id,))
new_entry = cursor.fetchone()
print("\n--- New Time Entry Details ---")
print(f"ID: {new_entry[0]}")
print(f"Project: {new_entry[1]}")
print(f"Description: {new_entry[2]}")
print(f"Invoiced: {'Yes' if new_entry[3] else 'No'}")
print(f"Date: {new_entry[4]}")
print(f"Hours: {new_entry[5]:.2f}")
print(f"Client ID: {new_entry[6]}")
print("------------------------------\n")
except sqlite3.Error as e:
print(f"An error occurred: {e}")
finally:
if conn:
conn.close()
if __name__ == "__main__":
add_time_entry()