# app/modules/tenancy/services/team_service.py """ Team service for store team management. This module provides: - Team member invitation - Role management - Team member CRUD operations """ import logging from datetime import UTC, datetime from typing import Any from sqlalchemy import func from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session from app.modules.tenancy.exceptions import ( TeamMemberNotFoundException, TeamValidationException, ) from app.modules.tenancy.models import Role, StoreUser, User logger = logging.getLogger(__name__) class TeamService: """Service for team management operations.""" def get_team_members( self, db: Session, store_id: int, current_user: User ) -> list[dict[str, Any]]: """ Get all team members for store. Args: db: Database session store_id: Store ID current_user: Current user Returns: List of team members """ try: store_users = ( db.query(StoreUser) .filter(StoreUser.store_id == store_id, StoreUser.is_active == True) .all() ) members = [] for vu in store_users: members.append( { "id": vu.user_id, "email": vu.user.email, "first_name": vu.user.first_name, "last_name": vu.user.last_name, "role": vu.role.name, "role_id": vu.role_id, "is_active": vu.is_active, "joined_at": vu.created_at, } ) return members except SQLAlchemyError as e: logger.error(f"Error getting team members: {str(e)}") raise TeamValidationException("Failed to retrieve team members") def invite_team_member( self, db: Session, store_id: int, invitation_data: dict, current_user: User ) -> dict[str, Any]: """ Invite a new team member. Args: db: Database session store_id: Store ID invitation_data: Invitation details current_user: Current user Returns: Invitation result """ try: # TODO: Implement full invitation flow with email # For now, return placeholder return { "message": "Team invitation feature coming soon", "email": invitation_data.get("email"), "role": invitation_data.get("role"), } except SQLAlchemyError as e: logger.error(f"Error inviting team member: {str(e)}") raise TeamValidationException("Failed to invite team member") def update_team_member( self, db: Session, store_id: int, user_id: int, update_data: dict, current_user: User, ) -> dict[str, Any]: """ Update team member role or status. Args: db: Database session store_id: Store ID user_id: User ID to update update_data: Update data current_user: Current user Returns: Updated member info """ try: store_user = ( db.query(StoreUser) .filter( StoreUser.store_id == store_id, StoreUser.user_id == user_id ) .first() ) if not store_user: raise TeamMemberNotFoundException(user_id=user_id, store_id=store_id) # Update fields if "role_id" in update_data: store_user.role_id = update_data["role_id"] if "is_active" in update_data: store_user.is_active = update_data["is_active"] store_user.updated_at = datetime.now(UTC) db.flush() db.refresh(store_user) return { "message": "Team member updated successfully", "user_id": user_id, } except SQLAlchemyError as e: logger.error(f"Error updating team member: {str(e)}") raise TeamValidationException("Failed to update team member") def remove_team_member( self, db: Session, store_id: int, user_id: int, current_user: User ) -> bool: """ Remove team member from store. Args: db: Database session store_id: Store ID user_id: User ID to remove current_user: Current user Returns: True if removed """ try: store_user = ( db.query(StoreUser) .filter( StoreUser.store_id == store_id, StoreUser.user_id == user_id ) .first() ) if not store_user: raise TeamMemberNotFoundException(user_id=user_id, store_id=store_id) # Soft delete store_user.is_active = False store_user.updated_at = datetime.now(UTC) logger.info(f"Removed user {user_id} from store {store_id}") return True except SQLAlchemyError as e: logger.error(f"Error removing team member: {str(e)}") raise TeamValidationException("Failed to remove team member") # ======================================================================== # Cross-module public API methods # ======================================================================== def get_store_owner(self, db: Session, store_id: int) -> StoreUser | None: """ Get the owner StoreUser for a store. Args: db: Database session store_id: Store ID Returns: StoreUser with is_owner=True, or None """ return ( db.query(StoreUser) .filter( StoreUser.store_id == store_id, StoreUser.is_owner == True, # noqa: E712 ) .first() ) def get_active_team_member_count(self, db: Session, store_id: int) -> int: """ Count active team members for a store. Args: db: Database session store_id: Store ID Returns: Number of active team members """ return ( db.query(func.count(StoreUser.id)) .filter( StoreUser.store_id == store_id, StoreUser.is_active == True, # noqa: E712 ) .scalar() or 0 ) def get_store_users_with_user( self, db: Session, store_id: int, active_only: bool = True ) -> list[tuple[User, StoreUser]]: """ Get User and StoreUser pairs for a store. Args: db: Database session store_id: Store ID active_only: Only active users Returns: List of (User, StoreUser) tuples """ query = ( db.query(User, StoreUser) .join(StoreUser, User.id == StoreUser.user_id) .filter(StoreUser.store_id == store_id) ) if active_only: query = query.filter(User.is_active == True) # noqa: E712 return query.all() def get_store_roles(self, db: Session, store_id: int) -> list[dict[str, Any]]: """ Get available roles for store. Args: db: Database session store_id: Store ID Returns: List of roles """ try: roles = db.query(Role).filter(Role.store_id == store_id).all() return [ { "id": role.id, "name": role.name, "permissions": role.permissions, } for role in roles ] except SQLAlchemyError as e: logger.error(f"Error getting store roles: {str(e)}") raise TeamValidationException("Failed to retrieve roles") def get_total_active_team_member_count(self, db: Session) -> int: """ Count active team members across all stores. Returns: Total number of active team members platform-wide """ return ( db.query(func.count(StoreUser.id)) .filter(StoreUser.is_active == True) # noqa: E712 .scalar() or 0 ) # Create service instance team_service = TeamService()