""" Serializers for SLA & On-Call Management """ from datetime import timedelta from rest_framework import serializers from django.contrib.auth import get_user_model from sla_oncall.models import ( BusinessHours, SLADefinition, EscalationPolicy, OnCallRotation, OnCallAssignment, SLAInstance, EscalationInstance, NotificationTemplate, ) User = get_user_model() class BusinessHoursSerializer(serializers.ModelSerializer): """Serializer for BusinessHours model""" class Meta: model = BusinessHours fields = [ 'id', 'name', 'description', 'timezone', 'weekday_start', 'weekday_end', 'weekend_start', 'weekend_end', 'day_overrides', 'holiday_calendar', 'is_active', 'is_default', 'created_by', 'created_at', 'updated_at' ] read_only_fields = ['id', 'created_at', 'updated_at'] def validate(self, data): """Validate business hours configuration""" if data.get('weekday_start') >= data.get('weekday_end'): raise serializers.ValidationError( "Weekday start time must be before weekday end time" ) if data.get('weekend_start') >= data.get('weekend_end'): raise serializers.ValidationError( "Weekend start time must be before weekend end time" ) return data class SLADefinitionSerializer(serializers.ModelSerializer): """Serializer for SLADefinition model""" business_hours_name = serializers.CharField( source='business_hours.name', read_only=True ) class Meta: model = SLADefinition fields = [ 'id', 'name', 'description', 'sla_type', 'incident_categories', 'incident_severities', 'incident_priorities', 'target_duration_minutes', 'business_hours_only', 'business_hours', 'business_hours_name', 'escalation_enabled', 'escalation_threshold_percent', 'is_active', 'is_default', 'created_by', 'created_at', 'updated_at' ] read_only_fields = ['id', 'created_at', 'updated_at'] def validate_escalation_threshold_percent(self, value): """Validate escalation threshold percentage""" if not (0 <= value <= 100): raise serializers.ValidationError( "Escalation threshold must be between 0 and 100 percent" ) return value class EscalationPolicySerializer(serializers.ModelSerializer): """Serializer for EscalationPolicy model""" class Meta: model = EscalationPolicy fields = [ 'id', 'name', 'description', 'escalation_type', 'trigger_condition', 'incident_severities', 'incident_categories', 'trigger_delay_minutes', 'escalation_steps', 'notification_channels', 'notification_templates', 'is_active', 'is_default', 'created_by', 'created_at', 'updated_at' ] read_only_fields = ['id', 'created_at', 'updated_at'] class OnCallRotationSerializer(serializers.ModelSerializer): """Serializer for OnCallRotation model""" current_oncall = serializers.SerializerMethodField() class Meta: model = OnCallRotation fields = [ 'id', 'name', 'description', 'rotation_type', 'status', 'team_name', 'team_description', 'schedule_config', 'timezone', 'external_system', 'external_system_id', 'integration_config', 'current_oncall', 'created_by', 'created_at', 'updated_at' ] read_only_fields = ['id', 'created_at', 'updated_at', 'current_oncall'] def get_current_oncall(self, obj): """Get current on-call person""" current_assignment = obj.get_current_oncall() if current_assignment: return { 'user_id': current_assignment.user.id, 'username': current_assignment.user.username, 'start_time': current_assignment.start_time, 'end_time': current_assignment.end_time } return None class OnCallAssignmentSerializer(serializers.ModelSerializer): """Serializer for OnCallAssignment model""" user_name = serializers.CharField(source='user.username', read_only=True) user_email = serializers.CharField(source='user.email', read_only=True) rotation_name = serializers.CharField(source='rotation.name', read_only=True) class Meta: model = OnCallAssignment fields = [ 'id', 'rotation', 'rotation_name', 'user', 'user_name', 'user_email', 'start_time', 'end_time', 'status', 'handoff_notes', 'handed_off_from', 'handoff_time', 'incidents_handled', 'response_time_avg', 'created_at', 'updated_at' ] read_only_fields = [ 'id', 'created_at', 'updated_at', 'user_name', 'user_email', 'rotation_name' ] def validate(self, data): """Validate assignment times""" if data.get('start_time') >= data.get('end_time'): raise serializers.ValidationError( "Start time must be before end time" ) return data class SLAInstanceSerializer(serializers.ModelSerializer): """Serializer for SLAInstance model""" incident_title = serializers.CharField(source='incident.title', read_only=True) sla_definition_name = serializers.CharField(source='sla_definition.name', read_only=True) is_breached = serializers.BooleanField(read_only=True) time_remaining = serializers.DurationField(read_only=True) breach_time = serializers.DurationField(read_only=True) class Meta: model = SLAInstance fields = [ 'id', 'sla_definition', 'sla_definition_name', 'incident', 'incident_title', 'status', 'target_time', 'started_at', 'met_at', 'breached_at', 'escalation_policy', 'escalation_triggered', 'escalation_triggered_at', 'escalation_level', 'response_time', 'resolution_time', 'is_breached', 'time_remaining', 'breach_time', 'created_at', 'updated_at' ] read_only_fields = [ 'id', 'created_at', 'updated_at', 'started_at', 'is_breached', 'time_remaining', 'breach_time', 'incident_title', 'sla_definition_name' ] class EscalationInstanceSerializer(serializers.ModelSerializer): """Serializer for EscalationInstance model""" incident_title = serializers.CharField(source='incident.title', read_only=True) escalation_policy_name = serializers.CharField(source='escalation_policy.name', read_only=True) class Meta: model = EscalationInstance fields = [ 'id', 'escalation_policy', 'escalation_policy_name', 'incident', 'incident_title', 'sla_instance', 'status', 'escalation_level', 'current_step', 'triggered_at', 'acknowledged_at', 'resolved_at', 'notifications_sent', 'actions_taken', 'created_at', 'updated_at' ] read_only_fields = [ 'id', 'created_at', 'updated_at', 'incident_title', 'escalation_policy_name' ] class NotificationTemplateSerializer(serializers.ModelSerializer): """Serializer for NotificationTemplate model""" class Meta: model = NotificationTemplate fields = [ 'id', 'name', 'template_type', 'channel_type', 'subject_template', 'body_template', 'variables', 'is_active', 'is_default', 'created_by', 'created_at', 'updated_at' ] read_only_fields = ['id', 'created_at', 'updated_at'] class SLAInstanceCreateSerializer(serializers.ModelSerializer): """Serializer for creating SLA instances""" class Meta: model = SLAInstance fields = [ 'sla_definition', 'incident', 'escalation_policy' ] def create(self, validated_data): """Create SLA instance with calculated target time""" sla_instance = super().create(validated_data) # Calculate target time based on SLA definition sla_definition = sla_instance.sla_definition incident = sla_instance.incident if sla_definition.business_hours_only and sla_definition.business_hours: # Calculate target time considering business hours target_time = sla_definition.business_hours.calculate_target_time( incident.created_at, sla_definition.target_duration_minutes ) else: # Simple time calculation target_time = incident.created_at + timedelta( minutes=sla_definition.target_duration_minutes ) sla_instance.target_time = target_time sla_instance.save() return sla_instance class OnCallAssignmentCreateSerializer(serializers.ModelSerializer): """Serializer for creating on-call assignments""" class Meta: model = OnCallAssignment fields = [ 'rotation', 'user', 'start_time', 'end_time', 'handoff_notes' ] def create(self, validated_data): """Create on-call assignment with validation""" assignment = super().create(validated_data) # Check for overlapping assignments overlapping = OnCallAssignment.objects.filter( rotation=assignment.rotation, user=assignment.user, status__in=['SCHEDULED', 'ACTIVE'], start_time__lt=assignment.end_time, end_time__gt=assignment.start_time ).exclude(id=assignment.id) if overlapping.exists(): raise serializers.ValidationError( "This assignment overlaps with existing assignments for the same user" ) return assignment