""" Commands for schedule-related operations. """ from typing import Any, Dict, List, Optional from datetime import datetime from backend.core.models.schedules.schedules import Schedule from backend.core.models.services.services import Service from backend.core.repositories.schedules.schedules import ScheduleRepository from backend.core.repositories.accounts.accounts import AccountRepository from backend.core.utils.validators import ( is_valid_uuid, is_valid_date, 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 CreateScheduleCommand(Command): """ Command to create a new schedule. """ def __init__( self, schedule_repo: ScheduleRepository, account_repo: AccountRepository, account_id: str, start_date: str, monday_service: bool = False, tuesday_service: bool = False, wednesday_service: bool = False, thursday_service: bool = False, friday_service: bool = False, saturday_service: bool = False, sunday_service: bool = False, weekend_service: bool = False, schedule_exception: Optional[str] = None, end_date: Optional[str] = None ): """ Initialize the create schedule command. Args: schedule_repo: Repository for schedule operations. account_repo: Repository for account operations. account_id: ID of the account this schedule belongs to. start_date: Start date of the schedule (YYYY-MM-DD). monday_service: Whether service is scheduled for Monday. tuesday_service: Whether service is scheduled for Tuesday. wednesday_service: Whether service is scheduled for Wednesday. thursday_service: Whether service is scheduled for Thursday. friday_service: Whether service is scheduled for Friday. saturday_service: Whether service is scheduled for Saturday. sunday_service: Whether service is scheduled for Sunday. weekend_service: Whether weekend service is enabled. schedule_exception: Any exceptions to the schedule. end_date: End date of the schedule (YYYY-MM-DD, optional). """ self.schedule_repo = schedule_repo self.account_repo = account_repo self.account_id = account_id self.start_date = start_date self.monday_service = monday_service self.tuesday_service = tuesday_service self.wednesday_service = wednesday_service self.thursday_service = thursday_service self.friday_service = friday_service self.saturday_service = saturday_service self.sunday_service = sunday_service self.weekend_service = weekend_service self.schedule_exception = schedule_exception self.end_date = end_date def validate(self) -> Dict[str, Any]: """ Validate the schedule creation data. Returns: Dict[str, Any]: Validation result with 'is_valid' and optional 'errors'. """ errors = [] # Check required fields required_fields = ['account_id', 'start_date'] field_values = { 'account_id': self.account_id, 'start_date': self.start_date } missing_fields = validate_required_fields(field_values, required_fields) 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 formats if not errors and self.start_date and not is_valid_date(self.start_date): errors.append("Invalid start date format. Use YYYY-MM-DD.") if not errors and self.end_date and not is_valid_date(self.end_date): errors.append("Invalid end date format. Use YYYY-MM-DD.") # Validate start date is before end date if both provided if not errors and self.start_date and self.end_date: start = parse_date(self.start_date) end = parse_date(self.end_date) if start and end and start > end: errors.append("Start date must be before end date.") # Check if there's an active schedule for this account if not errors: active_schedule = self.schedule_repo.get_active_by_account(self.account_id) if active_schedule: errors.append( f"Account already has an active schedule. End the current schedule before creating a new one.") # Validate at least one service day is selected if not errors and not any([ self.monday_service, self.tuesday_service, self.wednesday_service, self.thursday_service, self.friday_service, self.saturday_service, self.sunday_service, self.weekend_service ]): errors.append("At least one service day must be selected.") return { 'is_valid': len(errors) == 0, 'errors': errors } def execute(self) -> CommandResult[Schedule]: """ Execute the schedule creation command. Returns: CommandResult[Schedule]: Result of the command execution. """ # Validate command data validation = self.validate() if not validation['is_valid']: return CommandResult.failure_result(validation['errors']) try: # Create schedule data schedule_id = generate_uuid() # Create schedule data dictionary schedule_data = { 'id': schedule_id, 'account_id': self.account_id, 'monday_service': self.monday_service, 'tuesday_service': self.tuesday_service, 'wednesday_service': self.wednesday_service, 'thursday_service': self.thursday_service, 'friday_service': self.friday_service, 'saturday_service': self.saturday_service, 'sunday_service': self.sunday_service, 'weekend_service': self.weekend_service, 'schedule_exception': self.schedule_exception, 'start_date': self.start_date, 'end_date': self.end_date } # Save to repository created_schedule = self.schedule_repo.create(schedule_data) return CommandResult.success_result( created_schedule, f"Schedule created successfully for account {self.account_id}" ) except Exception as e: return CommandResult.failure_result( str(e), "Failed to create schedule" ) class UpdateScheduleCommand(Command): """ Command to update an existing schedule. """ def __init__( self, schedule_repo: ScheduleRepository, account_repo: AccountRepository, schedule_id: str, monday_service: Optional[bool] = None, tuesday_service: Optional[bool] = None, wednesday_service: Optional[bool] = None, thursday_service: Optional[bool] = None, friday_service: Optional[bool] = None, saturday_service: Optional[bool] = None, sunday_service: Optional[bool] = None, weekend_service: Optional[bool] = None, schedule_exception: Optional[str] = None, start_date: Optional[str] = None, end_date: Optional[str] = None ): """ Initialize the update schedule command. Args: schedule_repo: Repository for schedule operations. account_repo: Repository for account operations. schedule_id: ID of the schedule to update. monday_service: Whether service is scheduled for Monday. tuesday_service: Whether service is scheduled for Tuesday. wednesday_service: Whether service is scheduled for Wednesday. thursday_service: Whether service is scheduled for Thursday. friday_service: Whether service is scheduled for Friday. saturday_service: Whether service is scheduled for Saturday. sunday_service: Whether service is scheduled for Sunday. weekend_service: Whether weekend service is enabled. schedule_exception: Any exceptions to the schedule. start_date: Start date of the schedule. end_date: End date of the schedule. """ self.schedule_repo = schedule_repo self.account_repo = account_repo self.schedule_id = schedule_id self.monday_service = monday_service self.tuesday_service = tuesday_service self.wednesday_service = wednesday_service self.thursday_service = thursday_service self.friday_service = friday_service self.saturday_service = saturday_service self.sunday_service = sunday_service self.weekend_service = weekend_service self.schedule_exception = schedule_exception self.start_date = start_date self.end_date = end_date def validate(self) -> Dict[str, Any]: """ Validate the schedule update data. Returns: Dict[str, Any]: Validation result with 'is_valid' and optional 'errors'. """ errors = [] # Validate schedule exists if not is_valid_uuid(self.schedule_id): errors.append("Invalid schedule ID format") else: schedule = self.schedule_repo.get_by_id(self.schedule_id) if not schedule: errors.append(f"Schedule with ID {self.schedule_id} not found") # Validate date formats if provided if not errors and self.start_date is not None and not is_valid_date(self.start_date): errors.append("Invalid start date format. Use YYYY-MM-DD.") if not errors and self.end_date is not None and not is_valid_date(self.end_date): errors.append("Invalid end date format. Use YYYY-MM-DD.") # Validate start date is before end date if both provided if not errors and self.start_date and self.end_date: start = parse_date(self.start_date) end = parse_date(self.end_date) if start and end and start > end: errors.append("Start date must be before end date.") # If only updating end_date, validate it's after the existing start_date if not errors and self.end_date and not self.start_date: schedule = self.schedule_repo.get_by_id(self.schedule_id) if schedule: end = parse_date(self.end_date) start = parse_date(schedule.start_date) if end and start and start > end: errors.append("End date must be after the existing start date.") # Check if at least one service day will be selected after update if not errors: schedule = self.schedule_repo.get_by_id(self.schedule_id) if schedule: monday = self.monday_service if self.monday_service is not None else schedule.monday_service tuesday = self.tuesday_service if self.tuesday_service is not None else schedule.tuesday_service wednesday = self.wednesday_service if self.wednesday_service is not None else schedule.wednesday_service thursday = self.thursday_service if self.thursday_service is not None else schedule.thursday_service friday = self.friday_service if self.friday_service is not None else schedule.friday_service saturday = self.saturday_service if self.saturday_service is not None else schedule.saturday_service sunday = self.sunday_service if self.sunday_service is not None else schedule.sunday_service weekend = self.weekend_service if self.weekend_service is not None else schedule.weekend_service if not any([monday, tuesday, wednesday, thursday, friday, saturday, sunday, weekend]): errors.append("At least one service day must be selected.") return { 'is_valid': len(errors) == 0, 'errors': errors } def execute(self) -> CommandResult[Schedule]: """ Execute the schedule update command. Returns: CommandResult[Schedule]: 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.monday_service is not None: update_data['monday_service'] = self.monday_service if self.tuesday_service is not None: update_data['tuesday_service'] = self.tuesday_service if self.wednesday_service is not None: update_data['wednesday_service'] = self.wednesday_service if self.thursday_service is not None: update_data['thursday_service'] = self.thursday_service if self.friday_service is not None: update_data['friday_service'] = self.friday_service if self.saturday_service is not None: update_data['saturday_service'] = self.saturday_service if self.sunday_service is not None: update_data['sunday_service'] = self.sunday_service if self.weekend_service is not None: update_data['weekend_service'] = self.weekend_service if self.schedule_exception is not None: update_data['schedule_exception'] = self.schedule_exception if self.start_date is not None: update_data['start_date'] = self.start_date if self.end_date is not None: update_data['end_date'] = self.end_date # Update the schedule with the data dictionary updated_schedule = self.schedule_repo.update(self.schedule_id, update_data) return CommandResult.success_result( updated_schedule, f"Schedule {self.schedule_id} updated successfully" ) except Exception as e: return CommandResult.failure_result( str(e), "Failed to update schedule" ) class DeleteScheduleCommand(Command): """ Command to delete a schedule. """ def __init__( self, schedule_repo: ScheduleRepository, schedule_id: str ): """ Initialize the delete schedule command. Args: schedule_repo: Repository for schedule operations. schedule_id: ID of the schedule to delete. """ self.schedule_repo = schedule_repo self.schedule_id = schedule_id def validate(self) -> Dict[str, Any]: """ Validate the schedule deletion request. Returns: Dict[str, Any]: Validation result with 'is_valid' and optional 'errors'. """ errors = [] # Validate schedule exists if not is_valid_uuid(self.schedule_id): errors.append("Invalid schedule ID format") else: schedule = self.schedule_repo.get_by_id(self.schedule_id) if not schedule: errors.append(f"Schedule with ID {self.schedule_id} not found") return { 'is_valid': len(errors) == 0, 'errors': errors } def execute(self) -> CommandResult[bool]: """ Execute the schedule 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 schedule success = self.schedule_repo.delete(self.schedule_id) if success: return CommandResult.success_result( True, f"Schedule {self.schedule_id} deleted successfully" ) else: return CommandResult.failure_result( "Failed to delete schedule", f"Schedule {self.schedule_id} could not be deleted" ) except Exception as e: return CommandResult.failure_result( str(e), "Failed to delete schedule" ) class EndScheduleCommand(Command): """ Command to end a schedule by setting its end date. """ def __init__( self, schedule_repo: ScheduleRepository, schedule_id: str, end_date: Optional[str] = None ): """ Initialize the end schedule command. Args: schedule_repo: Repository for schedule operations. schedule_id: ID of the schedule to end. end_date: End date for the schedule (defaults to today if not provided). """ self.schedule_repo = schedule_repo self.schedule_id = schedule_id self.end_date = end_date def validate(self) -> Dict[str, Any]: """ Validate the end schedule request. Returns: Dict[str, Any]: Validation result with 'is_valid' and optional 'errors'. """ errors = [] # Validate schedule exists if not is_valid_uuid(self.schedule_id): errors.append("Invalid schedule ID format") else: schedule = self.schedule_repo.get_by_id(self.schedule_id) if not schedule: errors.append(f"Schedule with ID {self.schedule_id} not found") elif schedule.end_date is not None: errors.append(f"Schedule is already ended") # Validate end date format if provided if not errors and self.end_date and not is_valid_date(self.end_date): errors.append("Invalid end date format. Use YYYY-MM-DD.") # Validate end date is after start date if provided if not errors and self.end_date: schedule = self.schedule_repo.get_by_id(self.schedule_id) if schedule: end = parse_date(self.end_date) start = parse_date(schedule.start_date) if end and start and start > end: errors.append("End date must be after the start date.") return { 'is_valid': len(errors) == 0, 'errors': errors } def execute(self) -> CommandResult[Schedule]: """ Execute the end schedule command. Returns: CommandResult[Schedule]: Result of the command execution. """ # Validate command data validation = self.validate() if not validation['is_valid']: return CommandResult.failure_result(validation['errors']) try: # End the schedule end_date = self.end_date or datetime.now().strftime('%Y-%m-%d') updated_schedule = self.schedule_repo.update( self.schedule_id, {'end_date': end_date} ) if updated_schedule: return CommandResult.success_result( updated_schedule, f"Schedule {self.schedule_id} ended successfully" ) else: return CommandResult.failure_result( "Failed to end schedule", f"Schedule {self.schedule_id} could not be ended" ) except Exception as e: return CommandResult.failure_result( str(e), "Failed to end schedule" ) class GetActiveSchedulesCommand(Command): """ Command to get all active schedules.py. """ def __init__( self, schedule_repo: ScheduleRepository, account_id: Optional[str] = None ): """ Initialize the get active schedules.py command. Args: schedule_repo: Repository for schedule operations. account_id: Optional account ID to filter by. """ self.schedule_repo = schedule_repo self.account_id = account_id def validate(self) -> Dict[str, Any]: """ Validate the get active schedules.py request. Returns: Dict[str, Any]: Validation result with 'is_valid' and optional 'errors'. """ errors = [] # Validate account ID if provided if self.account_id and not is_valid_uuid(self.account_id): errors.append("Invalid account ID format") return { 'is_valid': len(errors) == 0, 'errors': errors } def execute(self) -> CommandResult[List[Schedule]]: """ Execute the get active schedules.py command. Returns: CommandResult[List[Schedule]]: Result of the command execution with active schedules.py. """ # Validate command data validation = self.validate() if not validation['is_valid']: return CommandResult.failure_result(validation['errors']) try: # Get active schedules.py if self.account_id: active_schedule = self.schedule_repo.get_active_by_account(self.account_id) schedules = [active_schedule] if active_schedule else [] else: schedules = list(self.schedule_repo.get_active()) return CommandResult.success_result( schedules, f"Retrieved active schedule records" ) except Exception as e: return CommandResult.failure_result( str(e), "Failed to retrieve active schedule records" ) class GenerateServicesCommand(Command): """ Command to generate services based on a schedule. """ def __init__( self, schedule_repo: ScheduleRepository, schedule_id: str, start_date: str, end_date: str ): """ Initialize the generate services command. Args: schedule_repo: Repository for schedule operations. schedule_id: ID of the schedule to generate services from. start_date: Start date for service generation (YYYY-MM-DD). end_date: End date for service generation (YYYY-MM-DD). """ self.schedule_repo = schedule_repo self.schedule_id = schedule_id self.start_date = start_date self.end_date = end_date def validate(self) -> Dict[str, Any]: """ Validate the generate services request. Returns: Dict[str, Any]: Validation result with 'is_valid' and optional 'errors'. """ errors = [] # Validate schedule exists if not is_valid_uuid(self.schedule_id): errors.append("Invalid schedule ID format") else: schedule = self.schedule_repo.get_by_id(self.schedule_id) if not schedule: errors.append(f"Schedule with ID {self.schedule_id} not found") # Validate date formats if not errors and not is_valid_date(self.start_date): errors.append("Invalid start date format. Use YYYY-MM-DD.") if not errors and not is_valid_date(self.end_date): errors.append("Invalid end date format. Use YYYY-MM-DD.") # Validate start date is before end date if not errors: start = parse_date(self.start_date) end = parse_date(self.end_date) if start and end and start > end: errors.append("Start date must be before end date.") # Validate date range is not too large (e.g., limit to 3 months) if not errors: start = parse_date(self.start_date) end = parse_date(self.end_date) if start and end: date_diff = (end - start).days if date_diff > 90: # 3 months errors.append("Date range too large. Maximum range is 90 days.") return { 'is_valid': len(errors) == 0, 'errors': errors } def execute(self) -> CommandResult[List[Service]]: """ Execute the generate services command. Returns: CommandResult[List[Service]]: Result of the command execution with generated services. """ # Validate command data validation = self.validate() if not validation['is_valid']: return CommandResult.failure_result(validation['errors']) try: # Parse dates start_date_obj = parse_date(self.start_date) end_date_obj = parse_date(self.end_date) # Generate services services = self.schedule_repo.generate_services( self.schedule_id, start_date_obj, end_date_obj ) return CommandResult.success_result( services, f"Generated {len(services)} services from schedule {self.schedule_id}" ) except Exception as e: return CommandResult.failure_result( str(e), "Failed to generate services from schedule" ) class GetScheduleByAccountCommand(Command): """ Command to get schedules.py for a specific account. """ def __init__( self, schedule_repo: ScheduleRepository, account_id: str ): """ Initialize the get schedule by account command. Args: schedule_repo: Repository for schedule operations. account_id: ID of the account to get schedules.py for. """ self.schedule_repo = schedule_repo self.account_id = account_id def validate(self) -> Dict[str, Any]: """ Validate the get schedule by account request. Returns: Dict[str, Any]: Validation result with 'is_valid' and optional 'errors'. """ errors = [] # Validate account ID if not is_valid_uuid(self.account_id): errors.append("Invalid account ID format") return { 'is_valid': len(errors) == 0, 'errors': errors } def execute(self) -> CommandResult[List[Schedule]]: """ Execute the get schedule by account command. Returns: CommandResult[List[Schedule]]: Result of the command execution with account schedules.py. """ # Validate command data validation = self.validate() if not validation['is_valid']: return CommandResult.failure_result(validation['errors']) try: # Get schedules.py for the account schedules = list(self.schedule_repo.get_by_account(self.account_id)) return CommandResult.success_result( schedules, f"Retrieved {len(schedules)} schedules.py for account {self.account_id}" ) except Exception as e: return CommandResult.failure_result( str(e), "Failed to retrieve schedules.py for account" ) class SearchSchedulesCommand(Command): """ Command to search for schedules.py. """ def __init__( self, schedule_repo: ScheduleRepository, search_term: str ): """ Initialize the search schedules.py command. Args: schedule_repo: Repository for schedule operations. search_term: Term to search for. """ self.schedule_repo = schedule_repo self.search_term = search_term def validate(self) -> Dict[str, Any]: """ Validate the search schedules.py request. Returns: Dict[str, Any]: Validation result with 'is_valid' and optional 'errors'. """ errors = [] # Validate search term if not self.search_term or len(self.search_term.strip()) < 2: errors.append("Search term must be at least 2 characters long") return { 'is_valid': len(errors) == 0, 'errors': errors } def execute(self) -> CommandResult[List[Schedule]]: """ Execute the search schedules.py command. Returns: CommandResult[List[Schedule]]: Result of the command execution with matching schedules.py. """ # Validate command data validation = self.validate() if not validation['is_valid']: return CommandResult.failure_result(validation['errors']) try: # Search for schedules.py schedules = list(self.schedule_repo.search(self.search_term)) return CommandResult.success_result( schedules, f"Found {len(schedules)} schedules.py matching '{self.search_term}'" ) except Exception as e: return CommandResult.failure_result( str(e), "Failed to search for schedules.py" )