2026-01-26 10:30:49 -05:00

612 lines
21 KiB
Python

"""
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))