# app/modules/tenancy/schemas/team.py """ Pydantic schemas for store team management. This module defines request/response schemas for: - Team member listing - Team member invitation - Team member updates - Role management """ from datetime import datetime from pydantic import BaseModel, EmailStr, Field, field_validator # ============================================================================ # Role Schemas # ============================================================================ class RoleBase(BaseModel): """Base role schema.""" name: str = Field(..., min_length=1, max_length=100, description="Role name") permissions: list[str] = Field( default_factory=list, description="List of permission strings" ) class RoleCreate(RoleBase): """Schema for creating a role.""" class RoleUpdate(BaseModel): """Schema for updating a role.""" name: str | None = Field(None, min_length=1, max_length=100) permissions: list[str] | None = None class RoleResponse(RoleBase): """Schema for role response.""" id: int store_id: int created_at: datetime updated_at: datetime class Config: from_attributes = True # Pydantic v2 (use orm_mode = True for v1) class RoleListResponse(BaseModel): """Schema for role list response.""" roles: list[RoleResponse] total: int # ============================================================================ # Team Member Schemas # ============================================================================ class TeamMemberBase(BaseModel): """Base team member schema.""" email: EmailStr = Field(..., description="Team member email address") first_name: str | None = Field(None, max_length=100) last_name: str | None = Field(None, max_length=100) class TeamMemberInvite(TeamMemberBase): """Schema for inviting a team member.""" role_id: int | None = Field( None, description="Role ID to assign (for preset roles)" ) role_name: str | None = Field( None, description="Role name (manager, staff, support, etc.)" ) custom_permissions: list[str] | None = Field( None, description="Custom permissions (overrides role preset)" ) @field_validator("role_name") @classmethod def validate_role_name(cls, v): """Validate role name is in allowed presets.""" if v is not None: allowed_roles = ["manager", "staff", "support", "viewer", "marketing"] if v.lower() not in allowed_roles: raise ValueError( f"Role name must be one of: {', '.join(allowed_roles)}" ) return v.lower() if v else v @field_validator("custom_permissions") @classmethod def validate_custom_permissions(cls, v): """Validate custom permissions list.""" return v class TeamMemberUpdate(BaseModel): """Schema for updating a team member.""" role_id: int | None = Field(None, description="New role ID") is_active: bool | None = Field(None, description="Active status") class TeamMemberResponse(BaseModel): """Schema for team member response.""" id: int = Field(..., description="User ID") email: EmailStr username: str first_name: str | None last_name: str | None full_name: str user_type: str = Field(..., description="'owner' or 'member'") role_name: str = Field(..., description="Role name") role_id: int | None permissions: list[str] = Field( default_factory=list, description="User's permissions" ) is_active: bool is_owner: bool invitation_pending: bool = Field( default=False, description="True if invitation not yet accepted" ) invited_at: datetime | None = Field(None, description="When invitation was sent") accepted_at: datetime | None = Field( None, description="When invitation was accepted" ) joined_at: datetime = Field(..., description="When user joined store") class Config: from_attributes = True class TeamMemberListResponse(BaseModel): """Schema for team member list response.""" members: list[TeamMemberResponse] total: int active_count: int pending_invitations: int # ============================================================================ # Invitation Schemas # ============================================================================ class InvitationAccept(BaseModel): """Schema for accepting a team invitation.""" invitation_token: str = Field( ..., min_length=32, description="Invitation token from email" ) password: str = Field( ..., min_length=8, max_length=128, description="Password for new account" ) first_name: str = Field(..., min_length=1, max_length=100) last_name: str = Field(..., min_length=1, max_length=100) @field_validator("password") @classmethod def validate_password_strength(cls, v): """Validate password meets minimum requirements.""" if len(v) < 8: raise ValueError("Password must be at least 8 characters long") has_upper = any(c.isupper() for c in v) has_lower = any(c.islower() for c in v) has_digit = any(c.isdigit() for c in v) if not (has_upper and has_lower and has_digit): raise ValueError( "Password must contain at least one uppercase letter, " "one lowercase letter, and one digit" ) return v class InvitationResponse(BaseModel): """Schema for invitation response.""" message: str email: EmailStr role: str invitation_token: str | None = Field( None, description="Token (only returned in dev/test environments)" ) invitation_sent: bool = Field(default=True) class InvitationAcceptResponse(BaseModel): """Schema for invitation acceptance response.""" message: str store: dict = Field(..., description="Store information") user: dict = Field(..., description="User information") role: str # ============================================================================ # Team Statistics Schema # ============================================================================ class TeamStatistics(BaseModel): """Schema for team statistics.""" total_members: int active_members: int inactive_members: int pending_invitations: int owners: int team_members: int roles_breakdown: dict = Field( default_factory=dict, description="Count of members per role" ) # ============================================================================ # Bulk Operations Schemas # ============================================================================ class BulkRemoveRequest(BaseModel): """Schema for bulk removing team members.""" user_ids: list[int] = Field( ..., min_items=1, description="List of user IDs to remove" ) class BulkRemoveResponse(BaseModel): """Schema for bulk remove response.""" success_count: int failed_count: int errors: list[dict] = Field(default_factory=list) # ============================================================================ # Permission Check Schemas # ============================================================================ class PermissionCheckRequest(BaseModel): """Schema for checking permissions.""" permissions: list[str] = Field(..., min_items=1, description="Permissions to check") class PermissionCheckResponse(BaseModel): """Schema for permission check response.""" has_all: bool = Field(..., description="True if user has all permissions") has_any: bool = Field(..., description="True if user has any permission") granted: list[str] = Field(default_factory=list, description="Permissions user has") denied: list[str] = Field( default_factory=list, description="Permissions user lacks" ) class UserPermissionsResponse(BaseModel): """Schema for user's permissions response.""" permissions: list[str] = Field(default_factory=list) permission_count: int is_owner: bool role_name: str | None = None # ============================================================================ # Error Response Schema # ============================================================================ class TeamErrorResponse(BaseModel): """Schema for team operation errors.""" error_code: str message: str details: dict | None = None