""" Commands for service-related operations. """ from typing import Any, Dict, List, Optional from datetime import datetime from backend.core.models.services.services import Service from backend.core.repositories.services.services import ServiceRepository from backend.core.repositories.accounts.accounts import AccountRepository from backend.core.repositories.profiles.profiles import ProfileRepository from backend.core.utils.validators import ( is_valid_uuid, is_valid_date, is_valid_datetime, validate_required_fields, validate_model_exists ) from backend.core.utils.helpers import generate_uuid, parse_date from backend.core.commands.base import Command, CommandResult class CreateServiceCommand(Command): """ Command to create a new service. """ def __init__( self, service_repo: ServiceRepository, account_repo: AccountRepository, profile_repo: ProfileRepository, account_id: str, date: str, status: str = 'scheduled', team_member_ids: Optional[List[str]] = None, notes: Optional[str] = None, deadline_start: Optional[str] = None, deadline_end: Optional[str] = None ): """ Initialize the create service command. Args: service_repo: Repository for service operations. account_repo: Repository for account operations. profile_repo: Repository for profile operations. account_id: ID of the account the service is for. date: Date of the service (YYYY-MM-DD). status: Status of the service ('scheduled', 'in_progress', 'completed', 'cancelled'). team_member_ids: List of profile IDs for team members assigned to the service. notes: Additional notes about the service. deadline_start: Start time of the service deadline (ISO format). deadline_end: End time of the service deadline (ISO format). """ self.service_repo = service_repo self.account_repo = account_repo self.profile_repo = profile_repo self.account_id = account_id self.date = date self.status = status self.team_member_ids = team_member_ids or [] self.notes = notes self.deadline_start = deadline_start self.deadline_end = deadline_end def validate(self) -> Dict[str, Any]: """ Validate the service creation data. Returns: Dict[str, Any]: Validation result with 'is_valid' and optional 'errors'. """ errors = [] # Check required fields missing_fields = validate_required_fields( { 'account_id': self.account_id, 'date': self.date }, ['account_id', 'date'] ) if missing_fields: errors.append(f"Required fields missing: {', '.join(missing_fields)}") # Validate account exists if not errors and self.account_id: account_validation = validate_model_exists( self.account_id, 'account', self.account_repo.get_by_id ) if not account_validation['valid']: errors.append(account_validation['error']) # Validate date format if not errors and self.date and not is_valid_date(self.date): errors.append("Invalid date format. Use YYYY-MM-DD.") # Validate deadline formats if not errors and self.deadline_start and not is_valid_datetime(self.deadline_start): errors.append("Invalid deadline_start format. Use ISO format.") if not errors and self.deadline_end and not is_valid_datetime(self.deadline_end): errors.append("Invalid deadline_end format. Use ISO format.") # Validate status valid_statuses = ['scheduled', 'in_progress', 'completed', 'cancelled'] if not errors and self.status not in valid_statuses: errors.append(f"Invalid status. Must be one of: {', '.join(valid_statuses)}") # Validate team member IDs if not errors and self.team_member_ids: for member_id in self.team_member_ids: if not is_valid_uuid(member_id): errors.append(f"Invalid team member ID format: {member_id}") elif not self.profile_repo.get_by_id(member_id): errors.append(f"Team member with ID {member_id} not found") return { 'is_valid': len(errors) == 0, 'errors': errors } def execute(self) -> CommandResult[Service]: """ Execute the service creation command. Returns: CommandResult[Service]: Result of the command execution. """ # Validate command data validation = self.validate() if not validation['is_valid']: return CommandResult.failure_result(validation['errors']) try: # Create service data service_id = generate_uuid() # Get date objects service_date = parse_date(self.date) # Create default deadline times if not provided deadline_start = self.deadline_start deadline_end = self.deadline_end if not deadline_start and service_date: # Default to 9:00 AM on the service date deadline_start = datetime.combine( service_date, datetime.min.time().replace(hour=9) ).isoformat() if not deadline_end and service_date: # Default to 5:00 PM on the service date deadline_end = datetime.combine( service_date, datetime.min.time().replace(hour=17) ).isoformat() # Create service data dictionary instead of Service object service_data = { 'id': service_id, 'account_id': self.account_id, 'date': self.date, 'status': self.status, 'notes': self.notes, 'deadline_start': deadline_start, 'deadline_end': deadline_end } # Use create_with_team_members to handle both service creation and team member assignment created_service = self.service_repo.create_with_team_members( service_data, self.team_member_ids or [] ) return CommandResult.success_result( created_service, f"Service for account {self.account_id} created successfully" ) except Exception as e: return CommandResult.failure_result( str(e), "Failed to create service" ) class UpdateServiceCommand(Command): """ Command to update an existing service. """ def __init__( self, service_repo: ServiceRepository, account_repo: AccountRepository, profile_repo: ProfileRepository, service_id: str, status: Optional[str] = None, date: Optional[str] = None, team_member_ids: Optional[List[str]] = None, notes: Optional[str] = None, deadline_start: Optional[str] = None, deadline_end: Optional[str] = None ): """ Initialize the update service command. Args: service_repo: Repository for service operations. account_repo: Repository for account operations. profile_repo: Repository for profile operations. service_id: ID of the service to update. status: New status for the service. date: New date for the service. team_member_ids: New list of team member IDs. notes: New notes for the service. deadline_start: New start deadline for the service. deadline_end: New end deadline for the service. """ self.service_repo = service_repo self.account_repo = account_repo self.profile_repo = profile_repo self.service_id = service_id self.status = status self.date = date self.team_member_ids = team_member_ids self.notes = notes self.deadline_start = deadline_start self.deadline_end = deadline_end def validate(self) -> Dict[str, Any]: """ Validate the service update data. Returns: Dict[str, Any]: Validation result with 'is_valid' and optional 'errors'. """ errors = [] # Validate service exists if not is_valid_uuid(self.service_id): errors.append("Invalid service ID format") else: service = self.service_repo.get_by_id(self.service_id) if not service: errors.append(f"Service with ID {self.service_id} not found") # Validate date format if provided if not errors and self.date and not is_valid_date(self.date): errors.append("Invalid date format. Use YYYY-MM-DD.") # Validate deadline formats if provided if not errors and self.deadline_start and not is_valid_datetime(self.deadline_start): errors.append("Invalid deadline_start format. Use ISO format.") if not errors and self.deadline_end and not is_valid_datetime(self.deadline_end): errors.append("Invalid deadline_end format. Use ISO format.") # Validate status if provided if not errors and self.status: valid_statuses = ['scheduled', 'in_progress', 'completed', 'cancelled'] if self.status not in valid_statuses: errors.append(f"Invalid status. Must be one of: {', '.join(valid_statuses)}") # Validate team member IDs if provided if not errors and self.team_member_ids is not None: for member_id in self.team_member_ids: if not is_valid_uuid(member_id): errors.append(f"Invalid team member ID format: {member_id}") elif not self.profile_repo.get_by_id(member_id): errors.append(f"Team member with ID {member_id} not found") return { 'is_valid': len(errors) == 0, 'errors': errors } def execute(self) -> CommandResult[Service]: """ Execute the service update command. Returns: CommandResult[Service]: Result of the command execution. """ # Validate command data validation = self.validate() if not validation['is_valid']: return CommandResult.failure_result(validation['errors']) try: # Create a dictionary of fields to update update_data = {} # Add fields to update_data if they were provided if self.status is not None: update_data['status'] = self.status if self.date is not None: update_data['date'] = self.date if self.notes is not None: update_data['notes'] = self.notes if self.deadline_start is not None: update_data['deadline_start'] = self.deadline_start if self.deadline_end is not None: update_data['deadline_end'] = self.deadline_end # Update the service with the data dictionary updated_service = self.service_repo.update(self.service_id, update_data) # Update team members if provided if self.team_member_ids is not None: updated_service = self.service_repo.assign_team_members( self.service_id, self.team_member_ids ) return CommandResult.success_result( updated_service, f"Service {self.service_id} updated successfully" ) except Exception as e: return CommandResult.failure_result( str(e), "Failed to update service" ) class DeleteServiceCommand(Command): """ Command to delete a service. """ def __init__(self, service_repo: ServiceRepository, service_id: str): """ Initialize the delete service command. Args: service_repo: Repository for service operations. service_id: ID of the service to delete. """ self.service_repo = service_repo self.service_id = service_id def validate(self) -> Dict[str, Any]: """ Validate the service deletion request. Returns: Dict[str, Any]: Validation result with 'is_valid' and optional 'errors'. """ errors = [] # Validate service exists if not is_valid_uuid(self.service_id): errors.append("Invalid service ID format") else: service = self.service_repo.get_by_id(self.service_id) if not service: errors.append(f"Service with ID {self.service_id} not found") return { 'is_valid': len(errors) == 0, 'errors': errors } def execute(self) -> CommandResult[bool]: """ Execute the service deletion command. Returns: CommandResult[bool]: Result of the command execution. """ # Validate command data validation = self.validate() if not validation['is_valid']: return CommandResult.failure_result(validation['errors']) try: # Delete the service success = self.service_repo.delete(self.service_id) if success: return CommandResult.success_result( True, f"Service {self.service_id} deleted successfully" ) else: return CommandResult.failure_result( "Failed to delete service", f"Service {self.service_id} could not be deleted" ) except Exception as e: return CommandResult.failure_result( str(e), "Failed to delete service" ) class CompleteServiceCommand(Command): """Command to mark a service as complete.""" def __init__( self, service_repo: ServiceRepository, service_id: str, completion_notes: Optional[str] = None ): self.service_repo = service_repo self.service_id = service_id self.completion_notes = completion_notes def validate(self) -> Dict[str, Any]: errors = [] if not is_valid_uuid(self.service_id): errors.append("Invalid service ID format") else: service = self.service_repo.get_by_id(self.service_id) if not service: errors.append(f"Service with ID {self.service_id} not found") elif service.status == 'completed': errors.append("Service is already completed") elif service.status == 'cancelled': errors.append("Cancelled service cannot be completed") return { 'is_valid': len(errors) == 0, 'errors': errors } def execute(self) -> CommandResult[Service]: validation = self.validate() if not validation['is_valid']: return CommandResult.failure_result(validation['errors']) try: update_data = { 'status': 'completed', 'notes': self.completion_notes } updated_service = self.service_repo.update(self.service_id, update_data) return CommandResult.success_result( updated_service, f"Service {self.service_id} marked as completed" ) except Exception as e: return CommandResult.failure_result(str(e)) class CancelServiceCommand(Command): """Command to cancel a service.""" def __init__( self, service_repo: ServiceRepository, service_id: str, cancellation_reason: Optional[str] = None ): self.service_repo = service_repo self.service_id = service_id self.cancellation_reason = cancellation_reason def validate(self) -> Dict[str, Any]: errors = [] if not is_valid_uuid(self.service_id): errors.append("Invalid service ID format") else: service = self.service_repo.get_by_id(self.service_id) if not service: errors.append(f"Service with ID {self.service_id} not found") elif service.status == 'cancelled': errors.append("Service is already cancelled") elif service.status == 'completed': errors.append("Completed service cannot be cancelled") return { 'is_valid': len(errors) == 0, 'errors': errors } def execute(self) -> CommandResult[Service]: validation = self.validate() if not validation['is_valid']: return CommandResult.failure_result(validation['errors']) try: update_data = { 'status': 'cancelled', 'notes': self.cancellation_reason } updated_service = self.service_repo.update(self.service_id, update_data) return CommandResult.success_result( updated_service, f"Service {self.service_id} cancelled successfully" ) except Exception as e: return CommandResult.failure_result(str(e)) class AssignTeamMembersCommand(Command): """Command to assign team members to a service.""" def __init__( self, service_repo: ServiceRepository, profile_repo: ProfileRepository, service_id: str, team_member_ids: List[str] ): self.service_repo = service_repo self.profile_repo = profile_repo self.service_id = service_id self.team_member_ids = team_member_ids def validate(self) -> Dict[str, Any]: errors = [] if not is_valid_uuid(self.service_id): errors.append("Invalid service ID format") else: service = self.service_repo.get_by_id(self.service_id) if not service: errors.append(f"Service with ID {self.service_id} not found") for member_id in self.team_member_ids: if not is_valid_uuid(member_id): errors.append(f"Invalid team member ID format: {member_id}") elif not self.profile_repo.get_by_id(member_id): errors.append(f"Team member with ID {member_id} not found") return { 'is_valid': len(errors) == 0, 'errors': errors } def execute(self) -> CommandResult[Service]: validation = self.validate() if not validation['is_valid']: return CommandResult.failure_result(validation['errors']) try: updated_service = self.service_repo.assign_team_members( self.service_id, self.team_member_ids ) return CommandResult.success_result( updated_service, f"Team members assigned to service {self.service_id} successfully" ) except Exception as e: return CommandResult.failure_result(str(e)) class GetServicesByDateRangeCommand(Command): """Command to get services within a date range.""" def __init__( self, service_repo: ServiceRepository, start_date: str, end_date: str, account_id: Optional[str] = None, team_member_id: Optional[str] = None ): self.service_repo = service_repo self.start_date = start_date self.end_date = end_date self.account_id = account_id self.team_member_id = team_member_id def validate(self) -> Dict[str, Any]: errors = [] if not is_valid_date(self.start_date): errors.append("Invalid start date format") if not is_valid_date(self.end_date): errors.append("Invalid end date format") if self.account_id and not is_valid_uuid(self.account_id): errors.append("Invalid account ID format") if self.team_member_id and not is_valid_uuid(self.team_member_id): errors.append("Invalid team member ID format") return { 'is_valid': len(errors) == 0, 'errors': errors } def execute(self) -> CommandResult[List[Service]]: validation = self.validate() if not validation['is_valid']: return CommandResult.failure_result(validation['errors']) try: start = parse_date(self.start_date) end = parse_date(self.end_date) services = self.service_repo.filter_services( date_from=start, date_to=end, account_id=self.account_id, team_member_id=self.team_member_id ) return CommandResult.success_result( list(services), f"Found {services.count()} services in date range" ) except Exception as e: return CommandResult.failure_result(str(e))