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