""" Factory for creating Service objects. """ from datetime import datetime, date, timedelta from typing import List, Dict, Any, Optional from backend.core.models import Schedule, Service, Profile from backend.core.utils.helpers import get_month_start_end, dict_to_json class ServiceFactory: """ Factory for creating Service objects based on schedules. """ @staticmethod def serialize_service(service: Service) -> Dict[str, Any]: """ Convert a Service object to a serializable dictionary. Args: service: The Service object to serialize Returns: Dict[str, Any]: A dictionary representation of the Service object """ return { 'id': str(service.id), 'account_id': str(service.account.id), 'account_name': service.account.name, 'date': service.date.isoformat(), 'status': service.status, 'notes': service.notes, 'deadline_start': service.deadline_start.isoformat(), 'deadline_end': service.deadline_end.isoformat(), 'created_at': service.created_at.isoformat(), 'updated_at': service.updated_at.isoformat(), 'completed_at': service.completed_at.isoformat() if service.completed_at else None, 'team_member_ids': [str(member.id) for member in service.team_members.all()], 'team_member_names': service.team_member_names } @classmethod def create_service(cls, schedule: Schedule, service_date: date, is_weekend: bool = False) -> Service: """ Create a Service object for a specific date based on a schedule. Args: schedule: The schedule to base the service on service_date: The date for the service is_weekend: Whether this is a weekend service Returns: Service: The created service object """ # Set default deadline times (6:00pm the day of service to 6:00am the following day) deadline_start = datetime.combine(service_date, datetime.min.time()).replace(hour=18) # 6:00pm deadline_end = datetime.combine(service_date + timedelta(days=1), datetime.min.time()).replace(hour=6) # 6:00am next day notes = None if is_weekend: notes = "Weekend Service" # Create the service service = Service.objects.create( account=schedule.account, date=service_date, status='scheduled', notes=notes, deadline_start=deadline_start, deadline_end=deadline_end ) # Add admin profile to team_members by default admin_profile_id = "7dc00b89-72d1-4dea-a1ed-4cbef220aa0c" try: admin_profile = Profile.objects.get(id=admin_profile_id) service.team_members.add(admin_profile) except Profile.DoesNotExist: # If admin profile doesn't exist, continue without adding it pass return service @classmethod def generate_services_for_month(cls, schedule: Schedule, year: int, month: int) -> Dict[str, Any]: """ Generate services for a specific month based on a schedule. Args: schedule: The schedule to base the services on year: The year month: The month (1-12) Returns: Dict[str, Any]: Dictionary with generated services and any errors """ if schedule.schedule_exception: return { 'services': [], 'success': False, 'errors': [f"Schedule exception: {schedule.schedule_exception}"], 'message': "Schedule has an exception and requires manual scheduling" } # Get the start and end dates of the month month_start = date(year, month, 1) month_end = get_month_start_end(datetime(year, month, 1))[1].date() services = [] errors = [] # Iterate through each day in the month current_date = month_start while current_date <= month_end: weekday = current_date.weekday() # Monday is 0, Sunday is 6 day_name = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'][weekday] # Check if service is required for this day is_service_day = schedule.has_service_on_day(day_name) is_weekend = weekday >= 5 and schedule.weekend_service # If it's a service day or a weekend with weekend_service enabled if is_service_day or is_weekend: # TODO: Check if it's a holiday (would need a holiday model/service) # For now, we'll assume it's not a holiday is_holiday = False if not is_holiday: try: # For weekend services, use the actual date but mark as weekend service service = cls.create_service(schedule, current_date, is_weekend=is_weekend) # Serialize the service object to make it JSON serializable serialized_service = cls.serialize_service(service) services.append(serialized_service) except Exception as e: errors.append(f"Error creating service for {current_date}: {str(e)}") current_date += timedelta(days=1) return { 'services': services, 'success': len(services) > 0, 'errors': errors, 'message': f"Generated {len(services)} services for {month}/{year}" } @classmethod def generate_services_for_accounts(cls, schedules: List[Schedule], year: int, month: int) -> Dict[str, Any]: """ Generate services for multiple accounts based on their schedules. Args: schedules: List of schedules to generate services for year: The year month: The month (1-12) Returns: Dict[str, Any]: Dictionary with results for each account """ results = {} overall_success = True overall_errors = [] for schedule in schedules: account_id = str(schedule.account.id) account_name = schedule.account.name # Skip inactive schedules if not schedule.is_active: results[account_id] = { 'account_name': account_name, 'services': [], 'success': False, 'errors': ["Schedule is inactive"], 'message': "Schedule is inactive" } continue # Generate services for this account account_result = cls.generate_services_for_month(schedule, year, month) # Add account name to the result account_result['account_name'] = account_name # Store the result results[account_id] = account_result # Update overall success and errors if not account_result['success']: overall_success = False if account_result['errors']: for error in account_result['errors']: overall_errors.append(f"{account_name}: {error}") return { 'account_results': results, 'success': overall_success, 'errors': overall_errors, 'message': f"Generated services for {len(results)} accounts for {month}/{year}" }