78 lines
2.8 KiB
Python
78 lines
2.8 KiB
Python
import os
|
|
from utils import load_config, load_json
|
|
|
|
def main():
|
|
config = load_config()
|
|
data_dir = config["DataDirectory"]
|
|
|
|
# Load supporting data
|
|
clients_data = load_json(os.path.join(data_dir, "Clients.json"), {"Clients": []})
|
|
projects_data = load_json(os.path.join(data_dir, "Projects.json"), {"Projects": []})
|
|
|
|
# Map IDs for quick lookup
|
|
client_map = {c["ClientID"]: c["Name"] for c in clients_data["Clients"]}
|
|
project_map = {p["ProjectID"]: p["Name"] for p in projects_data["Projects"]}
|
|
project_rates = {p["ProjectID"]: p.get("BillingRate", 0) for p in projects_data["Projects"]}
|
|
client_rates = {c["ClientID"]: c["DefaultRate"] for c in clients_data["Clients"]}
|
|
|
|
log_files = [f for f in os.listdir(data_dir) if f.endswith("_Time_Log.json")]
|
|
|
|
# Structure: { ClientName: { ProjectName: { hours: 0, amount: 0 } } }
|
|
report = {}
|
|
|
|
for file_name in log_files:
|
|
logs = load_json(os.path.join(data_dir, file_name), {"Entries": []})
|
|
for entry in logs.get("Entries", []):
|
|
if not entry.get("Invoiced", False):
|
|
c_id = entry["ClientID"]
|
|
p_id = entry["ProjectID"]
|
|
|
|
c_name = client_map.get(c_id, f"Unknown ({c_id})")
|
|
p_name = project_map.get(p_id, "No Project")
|
|
|
|
# Determine rate
|
|
rate = project_rates.get(p_id, 0)
|
|
if rate == 0:
|
|
rate = client_rates.get(c_id, 0)
|
|
|
|
hours = float(entry.get("Duration", 0))
|
|
amount = hours * rate
|
|
|
|
if c_name not in report:
|
|
report[c_name] = {}
|
|
if p_name not in report[c_name]:
|
|
report[c_name][p_name] = {"hours": 0.0, "amount": 0.0}
|
|
|
|
report[c_name][p_name]["hours"] += hours
|
|
report[c_name][p_name]["amount"] += amount
|
|
|
|
# Print Report
|
|
print("\n" + "="*50)
|
|
print("UNBILLED TIME REPORT")
|
|
print("="*50)
|
|
|
|
if not report:
|
|
print("No unbilled entries found.")
|
|
return
|
|
|
|
grand_total_amount = 0
|
|
for client, projects in sorted(report.items()):
|
|
print(f"\nCLIENT: {client}")
|
|
print("-" * 30)
|
|
client_total_h = 0
|
|
client_total_a = 0
|
|
|
|
for project, stats in projects.items():
|
|
print(f" {project:<20} {stats['hours']:>6.2f} hrs ${stats['amount']:>8.2f}")
|
|
client_total_h += stats['hours']
|
|
client_total_a += stats['amount']
|
|
|
|
print(f" {'Total:':<20} {client_total_h:>6.2f} hrs ${client_total_a:>8.2f}")
|
|
grand_total_amount += client_total_a
|
|
|
|
print("\n" + "="*50)
|
|
print(f"GRAND TOTAL UNBILLED: ${grand_total_amount:,.2f}")
|
|
print("="*50 + "\n")
|
|
|
|
if __name__ == "__main__":
|
|
main() |