282 lines
9.9 KiB
Python
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
|