From 15340311911044ddce9d8902a0cdf1d6152dd8fa Mon Sep 17 00:00:00 2001 From: Tim Wolff-Piggott Date: Sun, 25 Jun 2023 10:08:32 +0200 Subject: [PATCH] Bugfix: first billable date (#4) - add first_billable_date property to correctly resolve edge case - add rich traceback handling fixes #3 --- toggl_tally/api.py | 7 +++---- toggl_tally/cli.py | 5 ++++- toggl_tally/tally.py | 10 ++++++++++ toggl_tally/tests/test_tally.py | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/toggl_tally/api.py b/toggl_tally/api.py index f80a7f2..26ad32a 100644 --- a/toggl_tally/api.py +++ b/toggl_tally/api.py @@ -19,12 +19,11 @@ def __init__( self.session = requests.Session() def auth(self): - try: - api_token = os.environ["TOGGL_API_TOKEN"] - except KeyError as exception: + api_token = os.getenv("TOGGL_API_TOKEN") + if api_token is None: raise KeyError( "Please ensure that the 'TOGGL_API_TOKEN' environment variable is set" - ) from exception + ) self.session.auth = (api_token, "api_token") def get_time_entries_between(self, start_date: datetime, end_date: datetime): diff --git a/toggl_tally/cli.py b/toggl_tally/cli.py index d01a82f..8baa4a4 100644 --- a/toggl_tally/cli.py +++ b/toggl_tally/cli.py @@ -5,6 +5,7 @@ import click import yaml from rich.console import Console +from rich.traceback import install from toggl_tally import RichReport, TogglAPI, TogglFilter, TogglTally @@ -59,6 +60,8 @@ def _comma_separated_arg_split(ctx, param, value): ) @click.pass_context def toggl_tally(ctx: click.Context, config: Optional[Path]): + # rich traceback handling + install(max_frames=1) if config is not None: with config.open("r") as f: config_dict = yaml.safe_load(f) @@ -171,7 +174,7 @@ def hours( ) with console.status("[bold dark_cyan]Getting time entries"): unfiltered_time_entries = api.get_time_entries_between( - start_date=tally.last_invoice_date, + start_date=tally.first_billable_date, end_date=tally.now, ) filtered_time_entries = filter.filter_time_entries(response=unfiltered_time_entries) diff --git a/toggl_tally/tally.py b/toggl_tally/tally.py index a4062e6..fa12185 100644 --- a/toggl_tally/tally.py +++ b/toggl_tally/tally.py @@ -70,6 +70,16 @@ def last_invoice_date(self) -> datetime: else: return self.current_month_invoice_date + @property + def first_billable_date(self) -> datetime: + last_invoice_date = self.last_invoice_date + if last_invoice_date.day < self.invoice_day_of_month: + # Return the next day + return last_invoice_date + timedelta(days=1) + else: + # Return the last invoice date + return last_invoice_date + @property def next_invoice_date(self) -> datetime: if self.now < self.current_month_invoice_date: diff --git a/toggl_tally/tests/test_tally.py b/toggl_tally/tests/test_tally.py index d8f4762..173eb48 100644 --- a/toggl_tally/tests/test_tally.py +++ b/toggl_tally/tests/test_tally.py @@ -78,6 +78,12 @@ def test_toggl_tally_next_working_day(toggl_tally_object, expected_next_working_ datetime(2023, 3, 20), id="invoice_date_current_month_public_holiday", ), + pytest.param( + dict(now=datetime(2023, 6, 17), invoice_day_of_month=20), + datetime(2023, 5, 19), + datetime(2023, 6, 20), + id="invoice_date_weekend", + ), ], indirect=["toggl_tally_object"], ) @@ -101,6 +107,11 @@ def test_toggl_tally_invoice_dates( datetime(2023, 3, 24), id="last_billable_date_inclusive", ), + pytest.param( + dict(now=datetime(2023, 5, 17), invoice_day_of_month=20), + datetime(2023, 5, 19), + id="last_billable_date_inclusive_2", + ), pytest.param( dict(now=datetime(2023, 3, 1), invoice_day_of_month=21), datetime(2023, 3, 20), @@ -222,3 +233,25 @@ def test_toggle_tally_rrule_day_strs_fails_for_empty(): exception_match_str = re.escape("Working days should be non-empty") with pytest.raises(ValueError, match=exception_match_str): sut._get_rrule_days([]) + + +@pytest.mark.parametrize( + "toggl_tally_object,expected_first_billable_date", + [ + pytest.param( + dict(now=datetime(2023, 6, 18), invoice_day_of_month=19), + datetime(2023, 5, 19), + id="first_billable_date_inclusive", + ), + pytest.param( + dict(now=datetime(2023, 6, 19), invoice_day_of_month=20), + datetime(2023, 5, 20), + id="first_billable_date_exclusive", + ), + ], + indirect=["toggl_tally_object"], +) +def test_toggl_tally_first_billable_date( + toggl_tally_object, expected_first_billable_date +): + assert toggl_tally_object.first_billable_date == expected_first_billable_date