Files
ETB/ETB-API/sla_oncall/serializers/sla.py
Iliyan Angelov 6b247e5b9f Updates
2025-09-19 11:58:53 +03:00

282 lines
9.9 KiB
Python

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