Compare commits
7 Commits
v1.0
..
6c1b55158d
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c1b55158d | |||
| ab046271cf | |||
| 975b54b749 | |||
| 52239d00af | |||
| ffef627a4a | |||
| 910a3bc600 | |||
| 3cd30645fd |
@@ -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
@@ -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()
|
|
||||||
@@ -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()
|
|
||||||
@@ -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()
|
|
||||||
@@ -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
@@ -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
@@ -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()
|
|
||||||
Reference in New Issue
Block a user