Skip to content

Commit e8ac683

Browse files
committed
feat(time-entries): Add endpoint to get summary of worked time
1 parent 1b14d54 commit e8ac683

File tree

4 files changed

+164
-7
lines changed

4 files changed

+164
-7
lines changed

commons/data_access_layer/cosmos_db.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,11 @@ def __init__(self, status_code: int, description: str = None):
379379
self.description = description
380380

381381

382+
def init_app(app: Flask) -> None:
383+
global cosmos_helper
384+
cosmos_helper = CosmosDBFacade.from_flask_config(app)
385+
386+
382387
def current_datetime() -> datetime:
383388
return datetime.utcnow()
384389

@@ -398,11 +403,6 @@ def generate_uuid4() -> str:
398403
return str(uuid.uuid4())
399404

400405

401-
def init_app(app: Flask) -> None:
402-
global cosmos_helper
403-
cosmos_helper = CosmosDBFacade.from_flask_config(app)
404-
405-
406406
def get_last_day_of_month(year: int, month: int) -> int:
407407
from calendar import monthrange
408408
return monthrange(year=year, month=month)[1]
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
from datetime import datetime, timedelta
2+
from commons.data_access_layer.cosmos_db import (
3+
current_datetime,
4+
current_datetime_str,
5+
datetime_str,
6+
get_current_month,
7+
get_current_year
8+
)
9+
10+
11+
def start_datetime_of_current_month() -> datetime:
12+
return datetime(year=get_current_year(), month=get_current_month(), day=1,)
13+
14+
15+
def start_datetime_of_current_week() -> datetime:
16+
today = current_datetime()
17+
# WARNING : this gives monday datetime with hours at current hour
18+
monday = today - timedelta(days=today.weekday())
19+
return monday
20+
21+
22+
def start_datetime_of_current_day() -> datetime:
23+
today = current_datetime()
24+
today = today.replace(hour=0, minute=0, second=0, microsecond=000000)
25+
return today
26+
27+
28+
def start_datetime_of_current_month_str() -> str:
29+
return datetime_str(start_datetime_of_current_month())
30+
31+
32+
def str_to_datetime(
33+
value: str, conversion_format: str = '%Y-%m-%dT%H:%M:%S.%f'
34+
) -> datetime:
35+
return datetime.strptime(value, conversion_format)
36+
37+
38+
def date_range():
39+
return {
40+
"start_date": start_datetime_of_current_month_str(),
41+
"end_date": current_datetime_str(),
42+
}
43+
44+
45+
def filter_time_entries(
46+
time_entries, start_date: datetime, end_date: datetime = current_datetime()
47+
):
48+
return [
49+
t
50+
for t in time_entries
51+
if start_date <= str_to_datetime(t.start_date) <= end_date
52+
or start_date <= str_to_datetime(t.end_date) <= end_date
53+
]
54+
55+
56+
def stop_running_time_entry(time_entries):
57+
for t in time_entries:
58+
if t.end_date is None:
59+
t.end_date = current_datetime_str()
60+
61+
62+
class WorkedTime:
63+
def __init__(self, time_entries):
64+
self.time_entries = time_entries
65+
66+
def total_time_in_seconds(self):
67+
times = []
68+
69+
for t in self.time_entries:
70+
start_datetime = str_to_datetime(t.start_date)
71+
end_datetime = str_to_datetime(t.end_date)
72+
73+
elapsed_time = end_datetime - start_datetime
74+
times.append(elapsed_time)
75+
76+
total_time = timedelta()
77+
for time in times:
78+
total_time += time
79+
80+
return total_time.total_seconds()
81+
82+
def hours(self):
83+
return self.total_time_in_seconds() // 3600
84+
85+
def minutes(self):
86+
return (self.total_time_in_seconds() % 3600) // 60
87+
88+
def seconds(self):
89+
return (self.total_time_in_seconds() % 3600) % 60
90+
91+
def summary(self):
92+
return {
93+
"hours": self.hours(),
94+
"minutes": self.minutes(),
95+
"seconds": round(self.seconds(), 2),
96+
}
97+
98+
99+
def worked_time_in_day(time_entries):
100+
day_time_entries = filter_time_entries(
101+
time_entries, start_date=start_datetime_of_current_day()
102+
)
103+
return WorkedTime(day_time_entries).summary()
104+
105+
106+
def worked_time_in_week(time_entries):
107+
week_time_entries = filter_time_entries(
108+
time_entries, start_date=start_datetime_of_current_week()
109+
)
110+
return WorkedTime(week_time_entries).summary()
111+
112+
113+
def worked_time_in_month(time_entries):
114+
month_time_entries = filter_time_entries(
115+
time_entries, start_date=start_datetime_of_current_month()
116+
)
117+
return WorkedTime(month_time_entries).summary()
118+
119+
120+
def summary(time_entries):
121+
stop_running_time_entry(time_entries)
122+
return {
123+
'day': worked_time_in_day(time_entries),
124+
'week': worked_time_in_week(time_entries),
125+
'month': worked_time_in_month(time_entries),
126+
}

time_tracker_api/time_entries/time_entries_model.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
from azure.cosmos import PartitionKey
66
from flask_restplus._http import HTTPStatus
77

8+
from .custom_modules import worked_time
9+
810
from commons.data_access_layer.cosmos_db import (
911
CosmosDBDao,
1012
CosmosDBRepository,
1113
CustomError,
12-
current_datetime_str,
1314
CosmosDBModel,
15+
current_datetime_str,
1416
get_date_range_of_month,
1517
get_current_year,
1618
get_current_month,
@@ -105,7 +107,10 @@ def create_sql_date_range_filter(date_range: dict) -> str:
105107
return ''
106108

107109
def find_all(
108-
self, event_context: EventContext, conditions: dict, date_range: dict
110+
self,
111+
event_context: EventContext,
112+
conditions: dict = {},
113+
date_range: dict = {},
109114
):
110115
custom_sql_conditions = []
111116
custom_sql_conditions.append(
@@ -332,6 +337,19 @@ def find_running(self):
332337
event_ctx.tenant_id, event_ctx.user_id
333338
)
334339

340+
def get_worked_time(self, conditions: dict = {}):
341+
event_ctx = self.create_event_context(
342+
"read", "Summary of worked time in the current month"
343+
)
344+
conditions.update({"owner_id": event_ctx.user_id})
345+
346+
time_entries = self.repository.find_all(
347+
event_ctx,
348+
conditions=conditions,
349+
date_range=worked_time.date_range(),
350+
)
351+
return worked_time.summary(time_entries)
352+
335353
@staticmethod
336354
def handle_date_filter_args(args: dict) -> dict:
337355
if 'month' and 'year' in args:

time_tracker_api/time_entries/time_entries_namespace.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,3 +247,16 @@ class ActiveTimeEntry(Resource):
247247
def get(self):
248248
"""Find the time entry that is running"""
249249
return time_entries_dao.find_running()
250+
251+
252+
@ns.route('/summary')
253+
@ns.response(HTTPStatus.OK, 'Summary of worked time in the current month')
254+
@ns.response(
255+
HTTPStatus.NOT_FOUND, 'There is no time entry in the current month'
256+
)
257+
class WorkedTimeSummary(Resource):
258+
@ns.doc('summary_of_worked_time')
259+
# @ns.marshal_with(time_entry)
260+
def get(self):
261+
"""Find the summary of worked time"""
262+
return time_entries_dao.get_worked_time()

0 commit comments

Comments
 (0)