import logging from django.db.models.signals import pre_save from django.dispatch import receiver from django.utils import timezone from .models import Ride logger = logging.getLogger(__name__) @receiver(pre_save, sender=Ride) def handle_ride_status(sender, instance, **kwargs): """ Handle ride status changes based on closing date. Integrates with FSM transitions by using transition methods when available. """ if not instance.closing_date: return today = timezone.now().date() # If we've reached the closing date and status is "CLOSING" if today >= instance.closing_date and instance.status == "CLOSING": target_status = instance.post_closing_status or "SBNO" logger.info( f"Ride {instance.pk} closing date reached, " f"transitioning to {target_status}" ) # Try to use FSM transition method if available transition_method_name = f'transition_to_{target_status.lower()}' if hasattr(instance, transition_method_name): # Check if transition is allowed before attempting if hasattr(instance, 'can_proceed'): can_proceed = getattr(instance, f'can_transition_to_{target_status.lower()}', None) if can_proceed and callable(can_proceed): if not can_proceed(): logger.warning( f"FSM transition to {target_status} not allowed " f"for ride {instance.pk}" ) # Fall back to direct status change instance.status = target_status instance.status_since = instance.closing_date return try: method = getattr(instance, transition_method_name) method() instance.status_since = instance.closing_date logger.info( f"Applied FSM transition to {target_status} for ride {instance.pk}" ) except Exception as e: logger.exception( f"Failed to apply FSM transition for ride {instance.pk}: {e}" ) # Fall back to direct status change instance.status = target_status instance.status_since = instance.closing_date else: # No FSM transition method, use direct assignment instance.status = target_status instance.status_since = instance.closing_date @receiver(pre_save, sender=Ride) def validate_closing_status(sender, instance, **kwargs): """ Validate that post_closing_status is set when entering CLOSING state. """ # Only validate if this is an existing ride being updated if not instance.pk: return # Check if we're transitioning to CLOSING if instance.status == "CLOSING": # Ensure post_closing_status is set if not instance.post_closing_status: logger.warning( f"Ride {instance.pk} entering CLOSING without post_closing_status set" ) # Default to SBNO if not set instance.post_closing_status = "SBNO" # Ensure closing_date is set if not instance.closing_date: logger.warning( f"Ride {instance.pk} entering CLOSING without closing_date set" ) # Default to today's date instance.closing_date = timezone.now().date() # FSM transition signal handlers def handle_ride_transition_to_closing(instance, source, target, user, **kwargs): """ Validate transition to CLOSING status. This function is called by the FSM callback system before a ride transitions to CLOSING status. Args: instance: The Ride instance. source: The source state. target: The target state. user: The user who initiated the transition. Returns: True if transition should proceed, False to abort. """ if target != 'CLOSING': return True if not instance.post_closing_status: logger.error( f"Cannot transition ride {instance.pk} to CLOSING: " "post_closing_status not set" ) return False if not instance.closing_date: logger.warning( f"Ride {instance.pk} transitioning to CLOSING without closing_date" ) return True def apply_post_closing_status(instance, user=None): """ Apply the post_closing_status to a ride in CLOSING state. This function can be called by the FSM callback system or directly when a ride's closing date is reached. Args: instance: The Ride instance in CLOSING state. user: The user initiating the change (optional). Returns: True if status was applied, False otherwise. """ if instance.status != 'CLOSING': logger.debug( f"Ride {instance.pk} not in CLOSING state, skipping" ) return False target_status = instance.post_closing_status if not target_status: logger.warning( f"Ride {instance.pk} in CLOSING but no post_closing_status set" ) return False # Try to use FSM transition transition_method_name = f'transition_to_{target_status.lower()}' if hasattr(instance, transition_method_name): try: method = getattr(instance, transition_method_name) method(user=user) instance.post_closing_status = None instance.save(update_fields=['post_closing_status']) logger.info( f"Applied post_closing_status {target_status} to ride {instance.pk}" ) return True except Exception as e: logger.exception( f"Failed to apply post_closing_status for ride {instance.pk}: {e}" ) return False else: # Direct status change instance.status = target_status instance.post_closing_status = None instance.status_since = timezone.now().date() instance.save(update_fields=['status', 'post_closing_status', 'status_since']) logger.info( f"Applied post_closing_status {target_status} to ride {instance.pk} (direct)" ) return True