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