Skip to content

Commit f0cfd2c

Browse files
Refactor: get task query to handle deferred task logic (#237)
* feat: enhance task status filtering and validation logic - Updated the TaskRepository to improve status filtering logic, allowing for more precise queries based on task status and deferred details. - Refactored the TaskService to adjust validation for deferring tasks, ensuring that deferred dates are correctly compared to due dates. - Modified integration and unit tests to reflect changes in task deferral logic and removed unused constants for cleaner code. These enhancements improve the accuracy of task management operations and ensure better validation during task deferral. * fix: failing teams unit test * nit: remove comment * fix: update task status handling in TaskService - Adjusted task status assignment in TaskService to account for deferred tasks, ensuring that tasks with deferred details are correctly marked as DEFERRED. - Updated the return statement to reflect the new task status logic, improving the accuracy of task status representation. - Initialized task status to TODO in the update payload for task modifications, enhancing consistency in task state management. * fix: handle deferred details in task status updates - Added logic to clear deferred details when a task's status is updated, ensuring that tasks with deferred information are correctly managed during status changes. - This change improves the accuracy of task updates and maintains consistency in task state management. * fix: refine task status update logic in TaskService * fix: correct comparison operator for deferred task details in TaskRepository --------- Co-authored-by: anuj.k <[email protected]>
1 parent 660c5d7 commit f0cfd2c

File tree

5 files changed

+59
-22
lines changed

5 files changed

+59
-22
lines changed

todo/repositories/task_repository.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,36 @@ class TaskRepository(MongoRepository):
1717

1818
@classmethod
1919
def _build_status_filter(cls, status_filter: str = None) -> dict:
20-
"""
21-
Build status filter for task queries.
20+
now = datetime.now(timezone.utc)
21+
22+
if status_filter == TaskStatus.DEFERRED.value:
23+
return {
24+
"$and": [
25+
{"deferredDetails": {"$ne": None}},
26+
{"deferredDetails.deferredTill": {"$gt": now}},
27+
]
28+
}
29+
30+
elif status_filter == TaskStatus.DONE.value:
31+
return {
32+
"$or": [
33+
{"deferredDetails": None},
34+
{"deferredDetails.deferredTill": {"$lt": now}},
35+
]
36+
}
2237

23-
"""
24-
if status_filter:
25-
if status_filter == TaskStatus.DONE.value:
26-
return {} # No status filtering, include all tasks
27-
return {"status": status_filter}
2838
else:
29-
return {"status": {"$ne": TaskStatus.DONE.value}}
39+
return {
40+
"$and": [
41+
{"status": {"$ne": TaskStatus.DONE.value}},
42+
{
43+
"$or": [
44+
{"deferredDetails": None},
45+
{"deferredDetails.deferredTill": {"$lt": now}},
46+
]
47+
},
48+
]
49+
}
3050

3151
@classmethod
3252
def list(

todo/services/task_service.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from django.core.exceptions import ValidationError
44
from django.urls import reverse_lazy
55
from urllib.parse import urlencode
6-
from datetime import datetime, timezone, timedelta
6+
from datetime import datetime, timezone
77
from todo.dto.deferred_details_dto import DeferredDetailsDTO
88
from todo.dto.label_dto import LabelDTO
99
from todo.dto.task_dto import TaskDTO, CreateTaskDTO
@@ -30,7 +30,6 @@
3030
from todo.constants.task import (
3131
TaskStatus,
3232
TaskPriority,
33-
MINIMUM_DEFERRAL_NOTICE_DAYS,
3433
)
3534
from todo.constants.messages import ApiErrors, ValidationErrors
3635
from django.conf import settings
@@ -176,6 +175,11 @@ def prepare_task_dto(cls, task_model: TaskModel, user_id: str = None) -> TaskDTO
176175
if watchlist_entry:
177176
in_watchlist = watchlist_entry.isActive
178177

178+
task_status = task_model.status
179+
180+
if task_model.deferredDetails and task_model.deferredDetails.deferredTill > datetime.now(timezone.utc):
181+
task_status = TaskStatus.DEFERRED.value
182+
179183
return TaskDTO(
180184
id=str(task_model.id),
181185
displayId=task_model.displayId,
@@ -186,7 +190,7 @@ def prepare_task_dto(cls, task_model: TaskModel, user_id: str = None) -> TaskDTO
186190
labels=label_dtos,
187191
startedAt=task_model.startedAt,
188192
dueAt=task_model.dueAt,
189-
status=task_model.status,
193+
status=task_status,
190194
priority=task_model.priority,
191195
deferredDetails=deferred_details,
192196
in_watchlist=in_watchlist,
@@ -423,6 +427,16 @@ def update_task_with_assignee_from_dict(cls, task_id: str, validated_data: dict,
423427
if validated_data.get("status") == TaskStatus.IN_PROGRESS and not current_task.startedAt:
424428
update_payload["startedAt"] = datetime.now(timezone.utc)
425429

430+
if (
431+
validated_data.get("status") is not None
432+
and validated_data.get("status") != TaskStatus.DEFERRED.value
433+
and current_task.deferredDetails
434+
):
435+
update_payload["deferredDetails"] = None
436+
437+
if validated_data.get("status") == TaskStatus.DEFERRED.value:
438+
update_payload["status"] = current_task.status
439+
426440
# Update task if there are changes
427441
if update_payload:
428442
update_payload["updatedBy"] = user_id
@@ -550,9 +564,7 @@ def defer_task(cls, task_id: str, deferred_till: datetime, user_id: str) -> Task
550564
else current_task.dueAt.astimezone(timezone.utc)
551565
)
552566

553-
defer_limit = due_at - timedelta(days=MINIMUM_DEFERRAL_NOTICE_DAYS)
554-
555-
if deferred_till > defer_limit:
567+
if deferred_till >= due_at:
556568
raise UnprocessableEntityException(
557569
ValidationErrors.CANNOT_DEFER_TOO_CLOSE_TO_DUE_DATE,
558570
source={ApiErrorSource.PARAMETER: "deferredTill"},
@@ -565,6 +577,7 @@ def defer_task(cls, task_id: str, deferred_till: datetime, user_id: str) -> Task
565577
)
566578

567579
update_payload = {
580+
"status": TaskStatus.TODO.value,
568581
"deferredDetails": deferred_details.model_dump(),
569582
"updatedBy": user_id,
570583
}

todo/tests/integration/test_task_defer_api.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from bson import ObjectId
44
from django.urls import reverse
55
from todo.constants.messages import ApiErrors, ValidationErrors
6-
from todo.constants.task import MINIMUM_DEFERRAL_NOTICE_DAYS, TaskPriority, TaskStatus
6+
from todo.constants.task import TaskPriority, TaskStatus
77
from todo.tests.integration.base_mongo_test import AuthenticatedMongoTestCase
88
from todo.tests.fixtures.task import tasks_db_data
99

@@ -52,7 +52,7 @@ def _insert_task(self, *, status: str = TaskStatus.TODO.value, due_at: datetime
5252

5353
def test_defer_task_success(self):
5454
now = datetime.now(timezone.utc)
55-
due_at = now + timedelta(days=MINIMUM_DEFERRAL_NOTICE_DAYS + 30)
55+
due_at = now + timedelta(days=30)
5656
task_id = self._insert_task(due_at=due_at)
5757
deferred_till = now + timedelta(days=10)
5858

@@ -76,11 +76,10 @@ def test_defer_task_success(self):
7676

7777
def test_defer_task_too_close_to_due_date_returns_422(self):
7878
now = datetime.now(timezone.utc)
79-
due_at = now + timedelta(days=MINIMUM_DEFERRAL_NOTICE_DAYS + 5)
79+
due_at = now + timedelta(days=5)
8080
task_id = self._insert_task(due_at=due_at)
8181

82-
defer_limit = due_at - timedelta(days=MINIMUM_DEFERRAL_NOTICE_DAYS)
83-
deferred_till = defer_limit + timedelta(days=1)
82+
deferred_till = due_at + timedelta(days=1)
8483

8584
url = reverse("task_detail", args=[task_id]) + "?action=defer"
8685
response = self.client.patch(url, data={"deferredTill": deferred_till.isoformat()}, format="json")
@@ -129,7 +128,7 @@ def test_defer_task_with_missing_date_returns_400(self):
129128

130129
def test_defer_task_unauthorized(self):
131130
now = datetime.now(timezone.utc)
132-
due_at = now + timedelta(days=MINIMUM_DEFERRAL_NOTICE_DAYS + 30)
131+
due_at = now + timedelta(days=30)
133132
task_id = self._insert_task(due_at=due_at)
134133
deferred_till = now + timedelta(days=10)
135134
url = reverse("task_detail", args=[task_id]) + "?action=defer"

todo/tests/unit/repositories/test_task_repository.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,12 @@ def test_count_returns_total_task_count(self):
9797
result = TaskRepository.count()
9898

9999
self.assertEqual(result, 42)
100-
self.mock_collection.count_documents.assert_called_once_with({"status": {"$ne": "DONE"}})
100+
101+
self.mock_collection.count_documents.assert_called_once()
102+
actual_filter = self.mock_collection.count_documents.call_args[0][0]
103+
self.assertIn("$and", actual_filter)
104+
self.assertIn("status", actual_filter["$and"][0])
105+
self.assertIn("$or", actual_filter["$and"][1])
101106

102107
def test_get_all_returns_all_tasks(self):
103108
self.mock_collection.find.return_value = self.task_data

todo/tests/unit/services/test_task_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1024,7 +1024,7 @@ def test_defer_task_success(self, mock_prepare_dto, mock_repo_update, mock_repo_
10241024
@patch("todo.services.task_service.TaskRepository.get_by_id")
10251025
def test_defer_task_too_close_to_due_date_raises_exception(self, mock_repo_get_by_id):
10261026
mock_repo_get_by_id.return_value = self.task_model
1027-
deferred_till = self.due_at - timedelta(days=1)
1027+
deferred_till = self.due_at + timedelta(days=1)
10281028

10291029
with self.assertRaises(UnprocessableEntityException):
10301030
TaskService.defer_task(self.task_id, deferred_till, self.user_id)

0 commit comments

Comments
 (0)