Compare commits
4 Commits
1a09921fcc
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5025bf7ec | ||
|
|
d3d861bd2d | ||
|
|
b100533e03 | ||
|
|
d0196fefd0 |
48
tamigo.py
48
tamigo.py
@@ -20,10 +20,13 @@ def load_config():
|
|||||||
if os.path.exists(CONFIG_FILE):
|
if os.path.exists(CONFIG_FILE):
|
||||||
try:
|
try:
|
||||||
with open(CONFIG_FILE, "r") as f:
|
with open(CONFIG_FILE, "r") as f:
|
||||||
return json.load(f)
|
config = json.load(f)
|
||||||
|
if "show_zero_hours" not in config:
|
||||||
|
config["show_zero_hours"] = False
|
||||||
|
return config
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return {"theme": "fallout"} # Default to fallout because it's cooler
|
return {"theme": "fallout", "show_zero_hours": False} # Default to fallout and hide zero hours
|
||||||
|
|
||||||
def save_config(config):
|
def save_config(config):
|
||||||
with open(CONFIG_FILE, "w") as f:
|
with open(CONFIG_FILE, "w") as f:
|
||||||
@@ -190,6 +193,8 @@ def get_worked_days(client, start_date_dt, end_date_dt):
|
|||||||
with console.status("[highlight]INITIALIZING DATA RETRIEVAL...[/highlight]" if current_theme_name == "fallout" else "Fetching data..."):
|
with console.status("[highlight]INITIALIZING DATA RETRIEVAL...[/highlight]" if current_theme_name == "fallout" else "Fetching data..."):
|
||||||
data = client.get_employee_actual_shifts(start_date_dt, end_date_dt)
|
data = client.get_employee_actual_shifts(start_date_dt, end_date_dt)
|
||||||
|
|
||||||
|
show_zero = current_config.get("show_zero_hours", False)
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
work_days = {}
|
work_days = {}
|
||||||
for item in data:
|
for item in data:
|
||||||
@@ -200,6 +205,11 @@ def get_worked_days(client, start_date_dt, end_date_dt):
|
|||||||
if hours == 0 and item.get("StartTime") and item.get("EndTime"):
|
if hours == 0 and item.get("StartTime") and item.get("EndTime"):
|
||||||
st, et = parse_tamigo_date(item.get("StartTime")), parse_tamigo_date(item.get("EndTime"))
|
st, et = parse_tamigo_date(item.get("StartTime")), parse_tamigo_date(item.get("EndTime"))
|
||||||
if st and et: hours = (et - st).total_seconds() / 3600.0
|
if st and et: hours = (et - st).total_seconds() / 3600.0
|
||||||
|
|
||||||
|
# Filter zero hours if requested
|
||||||
|
if hours == 0 and not show_zero:
|
||||||
|
continue
|
||||||
|
|
||||||
if (hours > 0 or item.get("CheckInTime") or item.get("ActualStartTime") or item.get("StartTime")) and not item.get("IsAbsent", False):
|
if (hours > 0 or item.get("CheckInTime") or item.get("ActualStartTime") or item.get("StartTime")) and not item.get("IsAbsent", False):
|
||||||
if date_str not in work_days: work_days[date_str] = {"hours": hours, "text": item.get("ActualShiftText") or item.get("ActivityName") or "WORKED"}
|
if date_str not in work_days: work_days[date_str] = {"hours": hours, "text": item.get("ActualShiftText") or item.get("ActivityName") or "WORKED"}
|
||||||
else: work_days[date_str]["hours"] += hours
|
else: work_days[date_str]["hours"] += hours
|
||||||
@@ -305,9 +315,10 @@ def main():
|
|||||||
|
|
||||||
def menu(client):
|
def menu(client):
|
||||||
while True:
|
while True:
|
||||||
|
settings_text = "Settings"
|
||||||
choice = questionary.select(
|
choice = questionary.select(
|
||||||
"SELECT ACTION:" if current_theme_name == "fallout" else "What would you like to do?",
|
"SELECT ACTION:" if current_theme_name == "fallout" else "What would you like to do?",
|
||||||
choices=["Calculate actual work days", "Export hours worked", "Show profile info", "Switch UI Theme", "Logout and Exit"],
|
choices=["Calculate actual work days", "Export hours worked", "Show profile info", settings_text, "Logout and Exit"],
|
||||||
style=theme_data["questionary"]
|
style=theme_data["questionary"]
|
||||||
).ask()
|
).ask()
|
||||||
|
|
||||||
@@ -316,13 +327,36 @@ def menu(client):
|
|||||||
elif choice == "Show profile info":
|
elif choice == "Show profile info":
|
||||||
if client.user_info: console.print_json(data=client.user_info)
|
if client.user_info: console.print_json(data=client.user_info)
|
||||||
else: console.print("[error]ERROR: NO PROFILE INFO.[/error]")
|
else: console.print("[error]ERROR: NO PROFILE INFO.[/error]")
|
||||||
elif choice == "Switch UI Theme":
|
elif choice == settings_text:
|
||||||
new_theme = "normal" if current_theme_name == "fallout" else "fallout"
|
settings_menu()
|
||||||
apply_theme(new_theme)
|
|
||||||
console.print(f"[success]UI THEME SET TO: {new_theme.upper()}[/success]")
|
|
||||||
else:
|
else:
|
||||||
console.print("[highlight]SYSTEM SHUTDOWN...[/highlight]" if current_theme_name == "fallout" else "Exiting...")
|
console.print("[highlight]SYSTEM SHUTDOWN...[/highlight]" if current_theme_name == "fallout" else "Exiting...")
|
||||||
break
|
break
|
||||||
|
|
||||||
|
def settings_menu():
|
||||||
|
while True:
|
||||||
|
show_zero = current_config.get("show_zero_hours", False)
|
||||||
|
toggle_text = "Hide zero-hour days" if show_zero else "Show zero-hour days"
|
||||||
|
|
||||||
|
back_text = "Back"
|
||||||
|
|
||||||
|
choice = questionary.select(
|
||||||
|
"SELECT SETTING:" if current_theme_name == "fallout" else "Settings:",
|
||||||
|
choices=["Switch UI Theme", toggle_text, back_text],
|
||||||
|
style=theme_data["questionary"]
|
||||||
|
).ask()
|
||||||
|
|
||||||
|
if choice == "Switch UI Theme":
|
||||||
|
new_theme = "normal" if current_theme_name == "fallout" else "fallout"
|
||||||
|
apply_theme(new_theme)
|
||||||
|
console.print(f"[success]UI THEME SET TO: {new_theme.upper()}[/success]")
|
||||||
|
elif choice == toggle_text:
|
||||||
|
current_config["show_zero_hours"] = not show_zero
|
||||||
|
save_config(current_config)
|
||||||
|
state = "VISIBLE" if not show_zero else "HIDDEN"
|
||||||
|
console.print(f"[success]ZERO-HOUR DAYS ARE NOW {state}.[/success]" if current_theme_name == "fallout" else f"[success]Zero-hour days are now {state.lower()}.[/success]")
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -41,6 +41,76 @@ class TestTamigoExport(unittest.TestCase):
|
|||||||
self.assertIn("2025-03-10", work_days)
|
self.assertIn("2025-03-10", work_days)
|
||||||
self.assertEqual(work_days["2025-03-10"]["hours"], 8.0)
|
self.assertEqual(work_days["2025-03-10"]["hours"], 8.0)
|
||||||
|
|
||||||
|
def test_get_worked_days_zero_hour_filtering(self):
|
||||||
|
# Mock API response with a 0-hour shift (missing EndTime so it stays 0)
|
||||||
|
mock_shifts = [
|
||||||
|
{
|
||||||
|
"Date": "/Date(1741564800000)/", # 2025-03-10
|
||||||
|
"StartTime": "2025-03-10T09:00:00Z",
|
||||||
|
"ActualShiftHours": 0,
|
||||||
|
"ActualShiftText": "Sick Day",
|
||||||
|
"IsAbsent": False
|
||||||
|
}
|
||||||
|
]
|
||||||
|
self.client.get_employee_actual_shifts.return_value = mock_shifts
|
||||||
|
start_date = datetime(2025, 3, 1)
|
||||||
|
end_date = datetime(2025, 3, 15)
|
||||||
|
|
||||||
|
# 1. Test with show_zero_hours = False (Default)
|
||||||
|
with patch('tamigo.console'), patch('tamigo.current_config', {'show_zero_hours': False}):
|
||||||
|
work_days = get_worked_days(self.client, start_date, end_date)
|
||||||
|
self.assertEqual(len(work_days), 0)
|
||||||
|
|
||||||
|
# 2. Test with show_zero_hours = True
|
||||||
|
with patch('tamigo.console'), patch('tamigo.current_config', {'show_zero_hours': True}):
|
||||||
|
work_days = get_worked_days(self.client, start_date, end_date)
|
||||||
|
self.assertEqual(len(work_days), 1)
|
||||||
|
self.assertIn("2025-03-10", work_days)
|
||||||
|
self.assertEqual(work_days["2025-03-10"]["hours"], 0)
|
||||||
|
|
||||||
|
@patch('tamigo.select_date_range')
|
||||||
|
@patch('questionary.select')
|
||||||
|
@patch('questionary.text')
|
||||||
|
@patch('tamigo.console')
|
||||||
|
def test_export_worked_hours_respects_zero_hour_filtering(self, mock_console, mock_text, mock_select, mock_date_range):
|
||||||
|
from tamigo import export_worked_hours
|
||||||
|
|
||||||
|
# Mock API response with a 0-hour shift
|
||||||
|
mock_shifts = [
|
||||||
|
{
|
||||||
|
"Date": "/Date(1741564800000)/", # 2025-03-10
|
||||||
|
"StartTime": "2025-03-10T09:00:00Z",
|
||||||
|
"ActualShiftHours": 0,
|
||||||
|
"ActualShiftText": "Sick Day",
|
||||||
|
"IsAbsent": False
|
||||||
|
}
|
||||||
|
]
|
||||||
|
self.client.get_employee_actual_shifts.return_value = mock_shifts
|
||||||
|
mock_date_range.return_value = (datetime(2025, 3, 1), datetime(2025, 3, 15))
|
||||||
|
|
||||||
|
# 1. Test with show_zero_hours = False -> Should NOT export anything
|
||||||
|
with patch('tamigo.current_config', {'show_zero_hours': False}):
|
||||||
|
export_worked_hours(self.client)
|
||||||
|
# Verify the warning was printed
|
||||||
|
mock_console.print.assert_any_call("[warning]WARNING: NO LOG ENTRIES DETECTED.[/warning]")
|
||||||
|
self.assertFalse(os.path.exists("tamigo_export_20250301_20250315.csv"))
|
||||||
|
|
||||||
|
# 2. Test with show_zero_hours = True -> Should export the 0-hour day
|
||||||
|
mock_select.return_value.ask.return_value = "CSV"
|
||||||
|
mock_text.return_value.ask.return_value = "test_zero_export.csv"
|
||||||
|
|
||||||
|
with patch('tamigo.current_config', {'show_zero_hours': True}):
|
||||||
|
export_worked_hours(self.client)
|
||||||
|
|
||||||
|
self.assertTrue(os.path.exists("test_zero_export.csv"))
|
||||||
|
with open("test_zero_export.csv", "r") as f:
|
||||||
|
rows = list(csv.reader(f))
|
||||||
|
self.assertEqual(len(rows), 2) # Header + 1 data row
|
||||||
|
self.assertEqual(rows[1][0], "2025-03-10")
|
||||||
|
self.assertEqual(rows[1][1], "0.00")
|
||||||
|
|
||||||
|
os.remove("test_zero_export.csv")
|
||||||
|
|
||||||
@patch('tamigo.select_date_range')
|
@patch('tamigo.select_date_range')
|
||||||
@patch('tamigo.get_worked_days')
|
@patch('tamigo.get_worked_days')
|
||||||
@patch('questionary.select')
|
@patch('questionary.select')
|
||||||
|
|||||||
Reference in New Issue
Block a user