612 lines
21 KiB
Python
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))
|
|
|