""" WorkItem service layer for business logic and database operations. This module handles all database operations for work items, providing a clean separation between the API routes and data access layer. """ from typing import Optional from uuid import UUID from fastapi import HTTPException, status from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import Session from api.models.work_item import WorkItem from api.models.session import Session as SessionModel from api.schemas.work_item import WorkItemCreate, WorkItemUpdate def get_work_items(db: Session, skip: int = 0, limit: int = 100) -> tuple[list[WorkItem], int]: """ Retrieve a paginated list of work items. Args: db: Database session skip: Number of records to skip (for pagination) limit: Maximum number of records to return Returns: tuple: (list of work items, total count) Example: ```python work_items, total = get_work_items(db, skip=0, limit=50) print(f"Retrieved {len(work_items)} of {total} work items") ``` """ # Get total count total = db.query(WorkItem).count() # Get paginated results, ordered by created_at descending (newest first) work_items = ( db.query(WorkItem) .order_by(WorkItem.created_at.desc()) .offset(skip) .limit(limit) .all() ) return work_items, total def get_work_item_by_id(db: Session, work_item_id: UUID) -> WorkItem: """ Retrieve a single work item by its ID. Args: db: Database session work_item_id: UUID of the work item to retrieve Returns: WorkItem: The work item object Raises: HTTPException: 404 if work item not found Example: ```python work_item = get_work_item_by_id(db, work_item_id) print(f"Found work item: {work_item.title}") ``` """ work_item = db.query(WorkItem).filter(WorkItem.id == str(work_item_id)).first() if not work_item: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Work item with ID {work_item_id} not found" ) return work_item def get_work_items_by_session(db: Session, session_id: str, skip: int = 0, limit: int = 100) -> tuple[list[WorkItem], int]: """ Retrieve work items for a specific session. Args: db: Database session session_id: Session UUID skip: Number of records to skip limit: Maximum number of records to return Returns: tuple: (list of work items, total count) Example: ```python work_items, total = get_work_items_by_session(db, session_id) print(f"Session has {total} work items") ``` """ total = db.query(WorkItem).filter(WorkItem.session_id == str(session_id)).count() work_items = ( db.query(WorkItem) .filter(WorkItem.session_id == str(session_id)) .order_by(WorkItem.item_order, WorkItem.created_at) .offset(skip) .limit(limit) .all() ) return work_items, total def get_work_items_by_project(db: Session, project_id: str, skip: int = 0, limit: int = 100) -> tuple[list[WorkItem], int]: """ Retrieve work items for a specific project (through sessions). Args: db: Database session project_id: Project UUID skip: Number of records to skip limit: Maximum number of records to return Returns: tuple: (list of work items, total count) Example: ```python work_items, total = get_work_items_by_project(db, project_id) print(f"Project has {total} work items") ``` """ total = ( db.query(WorkItem) .join(SessionModel, WorkItem.session_id == SessionModel.id) .filter(SessionModel.project_id == str(project_id)) .count() ) work_items = ( db.query(WorkItem) .join(SessionModel, WorkItem.session_id == SessionModel.id) .filter(SessionModel.project_id == str(project_id)) .order_by(WorkItem.created_at.desc()) .offset(skip) .limit(limit) .all() ) return work_items, total def get_work_items_by_client(db: Session, client_id: str, skip: int = 0, limit: int = 100) -> tuple[list[WorkItem], int]: """ Retrieve work items for a specific client (through sessions). Args: db: Database session client_id: Client UUID skip: Number of records to skip limit: Maximum number of records to return Returns: tuple: (list of work items, total count) Example: ```python work_items, total = get_work_items_by_client(db, client_id) print(f"Client has {total} work items") ``` """ total = ( db.query(WorkItem) .join(SessionModel, WorkItem.session_id == SessionModel.id) .filter(SessionModel.client_id == str(client_id)) .count() ) work_items = ( db.query(WorkItem) .join(SessionModel, WorkItem.session_id == SessionModel.id) .filter(SessionModel.client_id == str(client_id)) .order_by(WorkItem.created_at.desc()) .offset(skip) .limit(limit) .all() ) return work_items, total def get_work_items_by_status(db: Session, status_filter: str, skip: int = 0, limit: int = 100) -> tuple[list[WorkItem], int]: """ Retrieve work items by status. Args: db: Database session status_filter: Status to filter by (completed, in_progress, blocked, pending, deferred) skip: Number of records to skip limit: Maximum number of records to return Returns: tuple: (list of work items, total count) Example: ```python work_items, total = get_work_items_by_status(db, "in_progress") print(f"Found {total} in progress work items") ``` """ total = db.query(WorkItem).filter(WorkItem.status == status_filter).count() work_items = ( db.query(WorkItem) .filter(WorkItem.status == status_filter) .order_by(WorkItem.created_at.desc()) .offset(skip) .limit(limit) .all() ) return work_items, total def validate_session_exists(db: Session, session_id: str) -> None: """ Validate that a session exists. Args: db: Database session session_id: Session UUID to validate Raises: HTTPException: 404 if session not found Example: ```python validate_session_exists(db, session_id) # Continues if session exists, raises HTTPException if not ``` """ session = db.query(SessionModel).filter(SessionModel.id == str(session_id)).first() if not session: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Session with ID {session_id} not found" ) def create_work_item(db: Session, work_item_data: WorkItemCreate) -> WorkItem: """ Create a new work item. Args: db: Database session work_item_data: Work item creation data Returns: WorkItem: The created work item object Raises: HTTPException: 404 if session not found HTTPException: 422 if validation fails HTTPException: 500 if database error occurs Example: ```python work_item_data = WorkItemCreate( session_id="123e4567-e89b-12d3-a456-426614174000", category="infrastructure", title="Configure firewall rules", description="Updated firewall rules for new server", status="completed" ) work_item = create_work_item(db, work_item_data) print(f"Created work item: {work_item.id}") ``` """ # Validate session exists validate_session_exists(db, work_item_data.session_id) try: # Create new work item instance db_work_item = WorkItem(**work_item_data.model_dump()) # Add to database db.add(db_work_item) db.commit() db.refresh(db_work_item) return db_work_item except IntegrityError as e: db.rollback() # Handle foreign key constraint violations if "session_id" in str(e.orig): raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Session with ID {work_item_data.session_id} not found" ) elif "category" in str(e.orig): raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Invalid category. Must be one of: infrastructure, troubleshooting, configuration, development, maintenance, security, documentation" ) elif "status" in str(e.orig): raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Invalid status. Must be one of: completed, in_progress, blocked, pending, deferred" ) elif "priority" in str(e.orig): raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Invalid priority. Must be one of: critical, high, medium, low" ) else: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Database error: {str(e)}" ) except Exception as e: db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to create work item: {str(e)}" ) def update_work_item(db: Session, work_item_id: UUID, work_item_data: WorkItemUpdate) -> WorkItem: """ Update an existing work item. Args: db: Database session work_item_id: UUID of the work item to update work_item_data: Work item update data (only provided fields will be updated) Returns: WorkItem: The updated work item object Raises: HTTPException: 404 if work item or session not found HTTPException: 422 if validation fails HTTPException: 500 if database error occurs Example: ```python update_data = WorkItemUpdate( status="completed", actual_minutes=45 ) work_item = update_work_item(db, work_item_id, update_data) print(f"Updated work item: {work_item.title}") ``` """ # Get existing work item work_item = get_work_item_by_id(db, work_item_id) try: # Update only provided fields update_data = work_item_data.model_dump(exclude_unset=True) # If updating session_id, validate session exists if "session_id" in update_data and update_data["session_id"] != work_item.session_id: validate_session_exists(db, update_data["session_id"]) # Apply updates for field, value in update_data.items(): setattr(work_item, field, value) db.commit() db.refresh(work_item) return work_item except HTTPException: db.rollback() raise except IntegrityError as e: db.rollback() if "session_id" in str(e.orig): raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Session not found" ) elif "category" in str(e.orig): raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Invalid category. Must be one of: infrastructure, troubleshooting, configuration, development, maintenance, security, documentation" ) elif "status" in str(e.orig): raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Invalid status. Must be one of: completed, in_progress, blocked, pending, deferred" ) elif "priority" in str(e.orig): raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Invalid priority. Must be one of: critical, high, medium, low" ) else: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Database error: {str(e)}" ) except Exception as e: db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to update work item: {str(e)}" ) def delete_work_item(db: Session, work_item_id: UUID) -> dict: """ Delete a work item by its ID. Args: db: Database session work_item_id: UUID of the work item to delete Returns: dict: Success message Raises: HTTPException: 404 if work item not found HTTPException: 500 if database error occurs Example: ```python result = delete_work_item(db, work_item_id) print(result["message"]) # "Work item deleted successfully" ``` """ # Get existing work item (raises 404 if not found) work_item = get_work_item_by_id(db, work_item_id) try: db.delete(work_item) db.commit() return { "message": "Work item deleted successfully", "work_item_id": str(work_item_id) } except Exception as e: db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to delete work item: {str(e)}" )