From f09e4228e416c165fa70eb7201495c7812aa44e4 Mon Sep 17 00:00:00 2001 From: Achintya-Chatterjee Date: Sun, 3 Aug 2025 00:58:16 +0530 Subject: [PATCH 1/6] refactor: implement team isolation for task assignments - Added `original_team_id` field to `TaskAssignmentModel` to track original team context - Modified `TaskAssignmentRepository.update_assignment()` to set `original_team_id` when reassigning from team to user - Updated `TaskRepository.list()` and `count()` to include team member tasks with proper team isolation - Team task lists (`/v1/tasks?teamId=...`) now include tasks assigned to team members - Tasks are filtered by `original_team_id` to prevent cross-team contamination - Users in multiple teams only see tasks relevant to each specific team context - Modified `TaskAssignmentDetailView.patch()` to use `update_assignment()` instead of `update_executor()` - This ensures `original_team_id` is properly set when UI calls `PATCH /v1/task-assignments/{task_id}` - Maintains backward compatibility with existing UI workflow - Added support for both ObjectId and string formats in MongoDB queries - Prevents data type mismatches in `original_team_id` filtering --- todo/models/task_assignment.py | 3 +- .../task_assignment_repository.py | 7 ++ todo/repositories/task_repository.py | 86 +++++++++++++++++-- todo/views/task_assignment.py | 20 ++--- 4 files changed, 91 insertions(+), 25 deletions(-) diff --git a/todo/models/task_assignment.py b/todo/models/task_assignment.py index fb70730b..2fb9dbcd 100644 --- a/todo/models/task_assignment.py +++ b/todo/models/task_assignment.py @@ -24,8 +24,9 @@ class TaskAssignmentModel(Document): created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) updated_at: datetime | None = None executor_id: PyObjectId | None = None # User within the team who is executing the task + original_team_id: PyObjectId | None = None # Track the original team when reassigned from team to user - @validator("task_id", "assignee_id", "created_by", "updated_by") + @validator("task_id", "assignee_id", "created_by", "updated_by", "original_team_id") def validate_object_ids(cls, v): if v is None: return v diff --git a/todo/repositories/task_assignment_repository.py b/todo/repositories/task_assignment_repository.py index 092e73be..93f49afd 100644 --- a/todo/repositories/task_assignment_repository.py +++ b/todo/repositories/task_assignment_repository.py @@ -72,6 +72,12 @@ def update_assignment( """ collection = cls.get_collection() try: + current_assignment = cls.get_by_task_id(task_id) + original_team_id = None + + if current_assignment and current_assignment.user_type == "team" and user_type == "user": + original_team_id = current_assignment.assignee_id + # Deactivate current assignment if exists (try both ObjectId and string) collection.update_many( {"task_id": ObjectId(task_id), "is_active": True}, @@ -102,6 +108,7 @@ def update_assignment( user_type=user_type, created_by=PyObjectId(user_id), updated_by=None, + original_team_id=original_team_id, ) return cls.create(new_assignment) diff --git a/todo/repositories/task_repository.py b/todo/repositories/task_repository.py index eb27c0b2..d70c8cb5 100644 --- a/todo/repositories/task_repository.py +++ b/todo/repositories/task_repository.py @@ -2,7 +2,6 @@ from typing import List from bson import ObjectId from pymongo import ReturnDocument -import logging from todo.exceptions.task_exceptions import TaskNotFoundException from todo.models.task import TaskModel @@ -40,17 +39,52 @@ def list( status_filter: str = None, ) -> List[TaskModel]: tasks_collection = cls.get_collection() - logger = logging.getLogger(__name__) base_filter = cls._build_status_filter(status_filter) if team_id: - logger.debug(f"TaskRepository.list: team_id={team_id}") + # Get tasks directly assigned to the team team_assignments = TaskAssignmentRepository.get_by_assignee_id(team_id, "team") - team_task_ids = [assignment.task_id for assignment in team_assignments] - logger.debug(f"TaskRepository.list: team_task_ids={team_task_ids}") - query_filter = {"$and": [base_filter, {"_id": {"$in": team_task_ids}}]} - logger.debug(f"TaskRepository.list: query_filter={query_filter}") + direct_team_task_ids = [assignment.task_id for assignment in team_assignments] + + # Get tasks assigned to team members (with original_team_id filtering for team isolation) + from todo.repositories.team_repository import UserTeamDetailsRepository + + team_member_ids = UserTeamDetailsRepository.get_users_by_team_id(team_id) + member_task_ids = [] + + if team_member_ids: + # Only get tasks where original_team_id matches current team (for team isolation) + member_assignments = list( + TaskAssignmentRepository.get_collection().find( + { + "$and": [ + { + "$or": [ + { + "assignee_id": { + "$in": [ObjectId(member_id) for member_id in team_member_ids] + } + }, + {"assignee_id": {"$in": team_member_ids}}, + ] + }, + {"user_type": "user"}, + {"is_active": True}, + { + "$or": [ + {"original_team_id": ObjectId(team_id)}, # ObjectId format + {"original_team_id": team_id}, # String format + ] + }, + ] + } + ) + ) + member_task_ids = [ObjectId(assignment["task_id"]) for assignment in member_assignments] + + all_team_task_ids = list(set(direct_team_task_ids + member_task_ids)) + query_filter = {"$and": [base_filter, {"_id": {"$in": all_team_task_ids}}]} elif user_id: assigned_task_ids = cls._get_assigned_task_ids_for_user(user_id) query_filter = {"$and": [base_filter, {"_id": {"$in": assigned_task_ids}}]} @@ -108,9 +142,43 @@ def count(cls, user_id: str = None, team_id: str = None, status_filter: str = No base_filter = cls._build_status_filter(status_filter) if team_id: + # Get tasks directly assigned to the team team_assignments = TaskAssignmentRepository.get_by_assignee_id(team_id, "team") - team_task_ids = [assignment.task_id for assignment in team_assignments] - query_filter = {"$and": [base_filter, {"_id": {"$in": team_task_ids}}]} + direct_team_task_ids = [assignment.task_id for assignment in team_assignments] + + # Get tasks assigned to team members (with original_team_id filtering for team isolation) + from todo.repositories.team_repository import UserTeamDetailsRepository + + team_member_ids = UserTeamDetailsRepository.get_users_by_team_id(team_id) + member_task_ids = [] + + if team_member_ids: + # Only get tasks where original_team_id matches current team (for team isolation) + + member_assignments = TaskAssignmentRepository.get_collection().find( + { + "$and": [ + { + "$or": [ + {"assignee_id": {"$in": [ObjectId(member_id) for member_id in team_member_ids]}}, + {"assignee_id": {"$in": team_member_ids}}, + ] + }, + {"user_type": "user"}, + {"is_active": True}, + { + "$or": [ + {"original_team_id": ObjectId(team_id)}, + {"original_team_id": team_id}, + ] + }, + ] + } + ) + member_task_ids = [ObjectId(assignment["task_id"]) for assignment in member_assignments] + + all_team_task_ids = list(set(direct_team_task_ids + member_task_ids)) + query_filter = {"$and": [base_filter, {"_id": {"$in": all_team_task_ids}}]} elif user_id: assigned_task_ids = cls._get_assigned_task_ids_for_user(user_id) query_filter = { diff --git a/todo/views/task_assignment.py b/todo/views/task_assignment.py index 7ce06926..9040d720 100644 --- a/todo/views/task_assignment.py +++ b/todo/views/task_assignment.py @@ -222,26 +222,16 @@ def patch(self, request: Request, task_id: str): {"error": f"User {executor_id} is not a member of the team."}, status=status.HTTP_400_BAD_REQUEST ) - # Update executor_id try: - updated = TaskAssignmentRepository.update_executor(task_id, executor_id, user["user_id"]) - if not updated: - # Get more details about why it failed - import traceback - - print( - f"DEBUG: update_executor failed for task_id={task_id}, executor_id={executor_id}, user_id={user['user_id']}" - ) - print(f"DEBUG: assignment details: {assignment}") + updated_assignment = TaskAssignmentRepository.update_assignment( + task_id, executor_id, "user", user["user_id"] + ) + if not updated_assignment: return Response( - {"error": "Failed to update executor. Check server logs for details."}, + {"error": "Failed to update assignment."}, status=status.HTTP_500_INTERNAL_SERVER_ERROR, ) except Exception as e: - print(f"DEBUG: Exception in update_executor: {str(e)}") - import traceback - - traceback.print_exc() return Response( {"error": f"Exception during update: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) From 77e831b56dc34c998089c75112e16813a0beff3c Mon Sep 17 00:00:00 2001 From: Achintya-Chatterjee Date: Sun, 3 Aug 2025 01:04:03 +0530 Subject: [PATCH 2/6] chore: remove unnecessary comments from code --- todo/repositories/task_repository.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/todo/repositories/task_repository.py b/todo/repositories/task_repository.py index d70c8cb5..2cbf013a 100644 --- a/todo/repositories/task_repository.py +++ b/todo/repositories/task_repository.py @@ -73,8 +73,8 @@ def list( {"is_active": True}, { "$or": [ - {"original_team_id": ObjectId(team_id)}, # ObjectId format - {"original_team_id": team_id}, # String format + {"original_team_id": ObjectId(team_id)}, + {"original_team_id": team_id}, ] }, ] From 0821a981f2ae489306c3213d4a969da17a46b9d3 Mon Sep 17 00:00:00 2001 From: Achintya-Chatterjee Date: Tue, 5 Aug 2025 00:17:12 +0530 Subject: [PATCH 3/6] chore: remove redudant query and added a helper method and calling that helper method in list and count method --- todo/repositories/task_repository.py | 118 ++++++++++----------------- 1 file changed, 41 insertions(+), 77 deletions(-) diff --git a/todo/repositories/task_repository.py b/todo/repositories/task_repository.py index 428c1136..54b7fe1a 100644 --- a/todo/repositories/task_repository.py +++ b/todo/repositories/task_repository.py @@ -9,11 +9,50 @@ from todo.repositories.task_assignment_repository import TaskAssignmentRepository from todo.constants.messages import ApiErrors, RepositoryErrors from todo.constants.task import SORT_FIELD_PRIORITY, SORT_FIELD_ASSIGNEE, SORT_ORDER_DESC, TaskStatus +from todo.repositories.team_repository import UserTeamDetailsRepository class TaskRepository(MongoRepository): collection_name = TaskModel.collection_name + @classmethod + def _get_team_task_ids(cls, team_id: str) -> List[ObjectId]: + """ + Get all task IDs for a team (direct assignments + member assignments with team isolation). + """ + team_assignments = TaskAssignmentRepository.get_by_assignee_id(team_id, "team") + direct_team_task_ids = [assignment.task_id for assignment in team_assignments] + + team_member_ids = UserTeamDetailsRepository.get_users_by_team_id(team_id) + member_task_ids = [] + + if team_member_ids: + member_assignments = list( + TaskAssignmentRepository.get_collection().find( + { + "$and": [ + { + "$or": [ + {"assignee_id": {"$in": [ObjectId(member_id) for member_id in team_member_ids]}}, + {"assignee_id": {"$in": team_member_ids}}, + ] + }, + {"user_type": "user"}, + {"is_active": True}, + { + "$or": [ + {"original_team_id": ObjectId(team_id)}, + {"original_team_id": team_id}, + ] + }, + ] + } + ) + ) + member_task_ids = [ObjectId(assignment["task_id"]) for assignment in member_assignments] + + return list(set(direct_team_task_ids + member_task_ids)) + @classmethod def _build_status_filter(cls, status_filter: str = None) -> dict: now = datetime.now(timezone.utc) @@ -63,47 +102,7 @@ def list( base_filter = cls._build_status_filter(status_filter) if team_id: - # Get tasks directly assigned to the team - team_assignments = TaskAssignmentRepository.get_by_assignee_id(team_id, "team") - direct_team_task_ids = [assignment.task_id for assignment in team_assignments] - - # Get tasks assigned to team members (with original_team_id filtering for team isolation) - from todo.repositories.team_repository import UserTeamDetailsRepository - - team_member_ids = UserTeamDetailsRepository.get_users_by_team_id(team_id) - member_task_ids = [] - - if team_member_ids: - # Only get tasks where original_team_id matches current team (for team isolation) - member_assignments = list( - TaskAssignmentRepository.get_collection().find( - { - "$and": [ - { - "$or": [ - { - "assignee_id": { - "$in": [ObjectId(member_id) for member_id in team_member_ids] - } - }, - {"assignee_id": {"$in": team_member_ids}}, - ] - }, - {"user_type": "user"}, - {"is_active": True}, - { - "$or": [ - {"original_team_id": ObjectId(team_id)}, - {"original_team_id": team_id}, - ] - }, - ] - } - ) - ) - member_task_ids = [ObjectId(assignment["task_id"]) for assignment in member_assignments] - - all_team_task_ids = list(set(direct_team_task_ids + member_task_ids)) + all_team_task_ids = cls._get_team_task_ids(team_id) query_filter = {"$and": [base_filter, {"_id": {"$in": all_team_task_ids}}]} elif user_id: assigned_task_ids = cls._get_assigned_task_ids_for_user(user_id) @@ -162,42 +161,7 @@ def count(cls, user_id: str = None, team_id: str = None, status_filter: str = No base_filter = cls._build_status_filter(status_filter) if team_id: - # Get tasks directly assigned to the team - team_assignments = TaskAssignmentRepository.get_by_assignee_id(team_id, "team") - direct_team_task_ids = [assignment.task_id for assignment in team_assignments] - - # Get tasks assigned to team members (with original_team_id filtering for team isolation) - from todo.repositories.team_repository import UserTeamDetailsRepository - - team_member_ids = UserTeamDetailsRepository.get_users_by_team_id(team_id) - member_task_ids = [] - - if team_member_ids: - # Only get tasks where original_team_id matches current team (for team isolation) - - member_assignments = TaskAssignmentRepository.get_collection().find( - { - "$and": [ - { - "$or": [ - {"assignee_id": {"$in": [ObjectId(member_id) for member_id in team_member_ids]}}, - {"assignee_id": {"$in": team_member_ids}}, - ] - }, - {"user_type": "user"}, - {"is_active": True}, - { - "$or": [ - {"original_team_id": ObjectId(team_id)}, - {"original_team_id": team_id}, - ] - }, - ] - } - ) - member_task_ids = [ObjectId(assignment["task_id"]) for assignment in member_assignments] - - all_team_task_ids = list(set(direct_team_task_ids + member_task_ids)) + all_team_task_ids = cls._get_team_task_ids(team_id) query_filter = {"$and": [base_filter, {"_id": {"$in": all_team_task_ids}}]} elif user_id: assigned_task_ids = cls._get_assigned_task_ids_for_user(user_id) From 23284b1a0487e39d68845f00680e1ebbb59f057a Mon Sep 17 00:00:00 2001 From: Achintya-Chatterjee Date: Tue, 5 Aug 2025 02:04:53 +0530 Subject: [PATCH 4/6] fix: added the originial_team_id in the create task assignment method --- todo/dto/task_assignment_dto.py | 9 +++++++++ todo/services/task_assignment_service.py | 1 + todo/services/task_service.py | 2 ++ todo/views/task_assignment.py | 15 +++++++++++++-- 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/todo/dto/task_assignment_dto.py b/todo/dto/task_assignment_dto.py index 02ee861a..1d325c59 100644 --- a/todo/dto/task_assignment_dto.py +++ b/todo/dto/task_assignment_dto.py @@ -8,6 +8,7 @@ class CreateTaskAssignmentDTO(BaseModel): task_id: str assignee_id: str user_type: Literal["user", "team"] + original_team_id: Optional[str] = None @validator("task_id") def validate_task_id(cls, value): @@ -30,6 +31,13 @@ def validate_user_type(cls, value): raise ValueError("user_type must be either 'user' or 'team'") return value + @validator("original_team_id") + def validate_original_team_id(cls, value): + """Validate that the original team ID is a valid ObjectId if provided.""" + if value is not None and not ObjectId.is_valid(value): + raise ValueError(f"Invalid original team ID: {value}") + return value + class TaskAssignmentDTO(BaseModel): id: str @@ -38,6 +46,7 @@ class TaskAssignmentDTO(BaseModel): assignee_name: Optional[str] = None user_type: Literal["user", "team"] executor_id: Optional[str] = None # User ID executing the task (for team assignments) + original_team_id: Optional[str] = None is_active: bool created_by: str updated_by: Optional[str] = None diff --git a/todo/services/task_assignment_service.py b/todo/services/task_assignment_service.py index b01110c0..0684aa66 100644 --- a/todo/services/task_assignment_service.py +++ b/todo/services/task_assignment_service.py @@ -66,6 +66,7 @@ def create_task_assignment(cls, dto: CreateTaskAssignmentDTO, user_id: str) -> C user_type=dto.user_type, created_by=PyObjectId(user_id), updated_by=None, + original_team_id=PyObjectId(dto.original_team_id) if dto.original_team_id else None, ) assignment = TaskAssignmentRepository.create(task_assignment) diff --git a/todo/services/task_service.py b/todo/services/task_service.py index 81c030d5..26cbc512 100644 --- a/todo/services/task_service.py +++ b/todo/services/task_service.py @@ -235,6 +235,8 @@ def _prepare_assignee_dto(cls, assignee_details: TaskAssignmentModel) -> TaskAss assignee_id=assignee_id, assignee_name=assignee.name, user_type=assignee_details.user_type, + executor_id=str(assignee_details.executor_id) if assignee_details.executor_id else None, + original_team_id=str(assignee_details.original_team_id) if assignee_details.original_team_id else None, is_active=assignee_details.is_active, created_by=str(assignee_details.created_by), updated_by=str(assignee_details.updated_by) if assignee_details.updated_by else None, diff --git a/todo/views/task_assignment.py b/todo/views/task_assignment.py index 9040d720..982f1699 100644 --- a/todo/views/task_assignment.py +++ b/todo/views/task_assignment.py @@ -221,17 +221,28 @@ def patch(self, request: Request, task_id: str): return Response( {"error": f"User {executor_id} is not a member of the team."}, status=status.HTTP_400_BAD_REQUEST ) - + # Update executor_id try: updated_assignment = TaskAssignmentRepository.update_assignment( task_id, executor_id, "user", user["user_id"] ) if not updated_assignment: + # Get more details about why it failed + import traceback + + print( + f"DEBUG: update_executor failed for task_id={task_id}, executor_id={executor_id}, user_id={user['user_id']}" + ) + print(f"DEBUG: assignment details: {assignment}") return Response( - {"error": "Failed to update assignment."}, + {"error": "Failed to update assignment. Check server logs for details."}, status=status.HTTP_500_INTERNAL_SERVER_ERROR, ) except Exception as e: + print(f"DEBUG: Exception in update_executor: {str(e)}") + import traceback + + traceback.print_exc() return Response( {"error": f"Exception during update: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) From 15cacc39c8431198c5891b49534536fa05c9de16 Mon Sep 17 00:00:00 2001 From: "anuj.k" Date: Wed, 6 Aug 2025 01:47:27 +0530 Subject: [PATCH 5/6] refactor: rename original_team_id to team_id in task assignment models and services - Updated CreateTaskAssignmentDTO and TaskAssignmentDTO to replace original_team_id with team_id for clarity. - Adjusted validation methods and repository logic to reflect the new field name. - Ensured consistency across task assignment service and repository for handling team assignments. --- todo/dto/task_assignment_dto.py | 8 ++++---- todo/models/task_assignment.py | 4 ++-- todo/repositories/task_assignment_repository.py | 15 +++++++++++---- todo/repositories/task_repository.py | 4 ++-- todo/services/task_assignment_service.py | 2 +- todo/services/task_service.py | 7 ++++++- 6 files changed, 26 insertions(+), 14 deletions(-) diff --git a/todo/dto/task_assignment_dto.py b/todo/dto/task_assignment_dto.py index 1d325c59..63b00a5a 100644 --- a/todo/dto/task_assignment_dto.py +++ b/todo/dto/task_assignment_dto.py @@ -8,7 +8,7 @@ class CreateTaskAssignmentDTO(BaseModel): task_id: str assignee_id: str user_type: Literal["user", "team"] - original_team_id: Optional[str] = None + team_id: Optional[str] = None @validator("task_id") def validate_task_id(cls, value): @@ -31,8 +31,8 @@ def validate_user_type(cls, value): raise ValueError("user_type must be either 'user' or 'team'") return value - @validator("original_team_id") - def validate_original_team_id(cls, value): + @validator("team_id") + def validate_team_id(cls, value): """Validate that the original team ID is a valid ObjectId if provided.""" if value is not None and not ObjectId.is_valid(value): raise ValueError(f"Invalid original team ID: {value}") @@ -46,7 +46,7 @@ class TaskAssignmentDTO(BaseModel): assignee_name: Optional[str] = None user_type: Literal["user", "team"] executor_id: Optional[str] = None # User ID executing the task (for team assignments) - original_team_id: Optional[str] = None + team_id: Optional[str] = None is_active: bool created_by: str updated_by: Optional[str] = None diff --git a/todo/models/task_assignment.py b/todo/models/task_assignment.py index 2fb9dbcd..8425df4b 100644 --- a/todo/models/task_assignment.py +++ b/todo/models/task_assignment.py @@ -24,9 +24,9 @@ class TaskAssignmentModel(Document): created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) updated_at: datetime | None = None executor_id: PyObjectId | None = None # User within the team who is executing the task - original_team_id: PyObjectId | None = None # Track the original team when reassigned from team to user + team_id: PyObjectId | None = None # Track the original team when reassigned from team to user - @validator("task_id", "assignee_id", "created_by", "updated_by", "original_team_id") + @validator("task_id", "assignee_id", "created_by", "updated_by", "team_id") def validate_object_ids(cls, v): if v is None: return v diff --git a/todo/repositories/task_assignment_repository.py b/todo/repositories/task_assignment_repository.py index 93f49afd..bd1722a3 100644 --- a/todo/repositories/task_assignment_repository.py +++ b/todo/repositories/task_assignment_repository.py @@ -2,6 +2,7 @@ from typing import Optional, List from bson import ObjectId +from todo.exceptions.task_exceptions import TaskNotFoundException from todo.models.task_assignment import TaskAssignmentModel from todo.repositories.common.mongo_repository import MongoRepository from todo.models.common.pyobjectid import PyObjectId @@ -73,10 +74,16 @@ def update_assignment( collection = cls.get_collection() try: current_assignment = cls.get_by_task_id(task_id) - original_team_id = None - if current_assignment and current_assignment.user_type == "team" and user_type == "user": - original_team_id = current_assignment.assignee_id + if not current_assignment: + raise TaskNotFoundException(task_id) + + team_id = None + + if user_type == "team": + team_id = assignee_id + elif user_type == "user" and current_assignment.team_id is not None: + team_id = current_assignment.team_id # Deactivate current assignment if exists (try both ObjectId and string) collection.update_many( @@ -108,7 +115,7 @@ def update_assignment( user_type=user_type, created_by=PyObjectId(user_id), updated_by=None, - original_team_id=original_team_id, + team_id=team_id, ) return cls.create(new_assignment) diff --git a/todo/repositories/task_repository.py b/todo/repositories/task_repository.py index 54b7fe1a..66e259f6 100644 --- a/todo/repositories/task_repository.py +++ b/todo/repositories/task_repository.py @@ -41,8 +41,8 @@ def _get_team_task_ids(cls, team_id: str) -> List[ObjectId]: {"is_active": True}, { "$or": [ - {"original_team_id": ObjectId(team_id)}, - {"original_team_id": team_id}, + {"team_id": ObjectId(team_id)}, + {"team_id": team_id}, ] }, ] diff --git a/todo/services/task_assignment_service.py b/todo/services/task_assignment_service.py index 0684aa66..592c5579 100644 --- a/todo/services/task_assignment_service.py +++ b/todo/services/task_assignment_service.py @@ -66,7 +66,7 @@ def create_task_assignment(cls, dto: CreateTaskAssignmentDTO, user_id: str) -> C user_type=dto.user_type, created_by=PyObjectId(user_id), updated_by=None, - original_team_id=PyObjectId(dto.original_team_id) if dto.original_team_id else None, + team_id=PyObjectId(dto.team_id) if dto.team_id else None, ) assignment = TaskAssignmentRepository.create(task_assignment) diff --git a/todo/services/task_service.py b/todo/services/task_service.py index 26cbc512..18444b20 100644 --- a/todo/services/task_service.py +++ b/todo/services/task_service.py @@ -236,7 +236,7 @@ def _prepare_assignee_dto(cls, assignee_details: TaskAssignmentModel) -> TaskAss assignee_name=assignee.name, user_type=assignee_details.user_type, executor_id=str(assignee_details.executor_id) if assignee_details.executor_id else None, - original_team_id=str(assignee_details.original_team_id) if assignee_details.original_team_id else None, + team_id=str(assignee_details.team_id) if assignee_details.team_id else None, is_active=assignee_details.is_active, created_by=str(assignee_details.created_by), updated_by=str(assignee_details.updated_by) if assignee_details.updated_by else None, @@ -630,11 +630,16 @@ def create_task(cls, dto: CreateTaskDTO) -> CreateTaskResponse: created_task = TaskRepository.create(task) # Create assignee relationship if assignee is provided + team_id = None + if dto.assignee and dto.assignee.get("user_type") == "team": + team_id = dto.assignee.get("assignee_id") + if dto.assignee: assignee_dto = CreateTaskAssignmentDTO( task_id=str(created_task.id), assignee_id=dto.assignee.get("assignee_id"), user_type=dto.assignee.get("user_type"), + team_id=team_id, ) TaskAssignmentService.create_task_assignment(assignee_dto, created_task.createdBy) From 2e47a2a7d68cec2b976dcf40fc864095790f3c9f Mon Sep 17 00:00:00 2001 From: "anuj.k" Date: Wed, 6 Aug 2025 02:56:46 +0530 Subject: [PATCH 6/6] fix: ensure team_id is stored as ObjectId in task assignments --- .../task_assignment_repository.py | 2 +- todo/repositories/task_repository.py | 41 +++---------------- 2 files changed, 6 insertions(+), 37 deletions(-) diff --git a/todo/repositories/task_assignment_repository.py b/todo/repositories/task_assignment_repository.py index bd1722a3..fdbf0b1c 100644 --- a/todo/repositories/task_assignment_repository.py +++ b/todo/repositories/task_assignment_repository.py @@ -115,7 +115,7 @@ def update_assignment( user_type=user_type, created_by=PyObjectId(user_id), updated_by=None, - team_id=team_id, + team_id=PyObjectId(team_id), ) return cls.create(new_assignment) diff --git a/todo/repositories/task_repository.py b/todo/repositories/task_repository.py index 66e259f6..41baccbf 100644 --- a/todo/repositories/task_repository.py +++ b/todo/repositories/task_repository.py @@ -17,41 +17,9 @@ class TaskRepository(MongoRepository): @classmethod def _get_team_task_ids(cls, team_id: str) -> List[ObjectId]: - """ - Get all task IDs for a team (direct assignments + member assignments with team isolation). - """ - team_assignments = TaskAssignmentRepository.get_by_assignee_id(team_id, "team") - direct_team_task_ids = [assignment.task_id for assignment in team_assignments] - - team_member_ids = UserTeamDetailsRepository.get_users_by_team_id(team_id) - member_task_ids = [] - - if team_member_ids: - member_assignments = list( - TaskAssignmentRepository.get_collection().find( - { - "$and": [ - { - "$or": [ - {"assignee_id": {"$in": [ObjectId(member_id) for member_id in team_member_ids]}}, - {"assignee_id": {"$in": team_member_ids}}, - ] - }, - {"user_type": "user"}, - {"is_active": True}, - { - "$or": [ - {"team_id": ObjectId(team_id)}, - {"team_id": team_id}, - ] - }, - ] - } - ) - ) - member_task_ids = [ObjectId(assignment["task_id"]) for assignment in member_assignments] - - return list(set(direct_team_task_ids + member_task_ids)) + team_tasks = TaskAssignmentRepository.get_collection().find({"team_id": team_id, "is_active": True}) + team_task_ids = [ObjectId(task["task_id"]) for task in team_tasks] + return list(set(team_task_ids)) @classmethod def _build_status_filter(cls, status_filter: str = None) -> dict: @@ -131,7 +99,7 @@ def _get_assigned_task_ids_for_user(cls, user_id: str) -> List[ObjectId]: direct_task_ids = [assignment.task_id for assignment in direct_assignments] # Get teams where user is a member - from todo.repositories.team_repository import UserTeamDetailsRepository, TeamRepository + from todo.repositories.team_repository import TeamRepository user_teams = UserTeamDetailsRepository.get_by_user_id(user_id) team_ids = [str(team.team_id) for team in user_teams] @@ -163,6 +131,7 @@ def count(cls, user_id: str = None, team_id: str = None, status_filter: str = No if team_id: all_team_task_ids = cls._get_team_task_ids(team_id) query_filter = {"$and": [base_filter, {"_id": {"$in": all_team_task_ids}}]} + elif user_id: assigned_task_ids = cls._get_assigned_task_ids_for_user(user_id) query_filter = {