HH/apps/complaints/tasks.py
2025-12-24 12:42:31 +03:00

175 lines
5.8 KiB
Python

"""
Complaints Celery tasks
This module contains tasks for:
- Checking overdue complaints
- Sending SLA reminders
- Triggering resolution satisfaction surveys
- Creating PX actions from complaints
"""
import logging
from celery import shared_task
from django.db import transaction
from django.utils import timezone
logger = logging.getLogger(__name__)
@shared_task
def check_overdue_complaints():
"""
Periodic task to check for overdue complaints.
Runs every 15 minutes (configured in config/celery.py).
Updates is_overdue flag for complaints past their SLA deadline.
"""
from apps.complaints.models import Complaint, ComplaintStatus
# Get active complaints (not closed or cancelled)
active_complaints = Complaint.objects.filter(
status__in=[ComplaintStatus.OPEN, ComplaintStatus.IN_PROGRESS, ComplaintStatus.RESOLVED]
).select_related('hospital', 'patient')
overdue_count = 0
for complaint in active_complaints:
if complaint.check_overdue():
overdue_count += 1
logger.warning(
f"Complaint {complaint.id} is overdue: {complaint.title} "
f"(due: {complaint.due_at})"
)
# TODO: Trigger escalation (Phase 6)
# from apps.px_action_center.tasks import escalate_complaint
# escalate_complaint.delay(str(complaint.id))
if overdue_count > 0:
logger.info(f"Found {overdue_count} overdue complaints")
return {'overdue_count': overdue_count}
@shared_task
def send_complaint_resolution_survey(complaint_id):
"""
Send resolution satisfaction survey when complaint is closed.
This task is triggered when a complaint status changes to CLOSED.
Args:
complaint_id: UUID of the Complaint
Returns:
dict: Result with survey_instance_id
"""
from apps.complaints.models import Complaint
from apps.core.services import create_audit_log
from apps.surveys.models import SurveyInstance, SurveyTemplate
try:
complaint = Complaint.objects.select_related(
'patient', 'hospital'
).get(id=complaint_id)
# Check if survey already sent
if complaint.resolution_survey:
logger.info(f"Resolution survey already sent for complaint {complaint_id}")
return {'status': 'skipped', 'reason': 'already_sent'}
# Get resolution satisfaction survey template
try:
survey_template = SurveyTemplate.objects.get(
hospital=complaint.hospital,
survey_type='complaint_resolution',
is_active=True
)
except SurveyTemplate.DoesNotExist:
logger.warning(
f"No resolution satisfaction survey template found for hospital {complaint.hospital.name}"
)
return {'status': 'skipped', 'reason': 'no_template'}
# Create survey instance
with transaction.atomic():
survey_instance = SurveyInstance.objects.create(
survey_template=survey_template,
patient=complaint.patient,
encounter_id=complaint.encounter_id,
delivery_channel='sms', # Default
recipient_phone=complaint.patient.phone,
recipient_email=complaint.patient.email,
metadata={
'complaint_id': str(complaint.id),
'complaint_title': complaint.title
}
)
# Link survey to complaint
complaint.resolution_survey = survey_instance
complaint.resolution_survey_sent_at = timezone.now()
complaint.save(update_fields=['resolution_survey', 'resolution_survey_sent_at'])
# Send survey
from apps.notifications.services import NotificationService
notification_log = NotificationService.send_survey_invitation(
survey_instance=survey_instance,
language='en' # TODO: Get from patient preference
)
# Update survey status
survey_instance.status = 'active'
survey_instance.sent_at = timezone.now()
survey_instance.save(update_fields=['status', 'sent_at'])
# Log audit event
create_audit_log(
event_type='survey_sent',
description=f"Resolution satisfaction survey sent for complaint: {complaint.title}",
content_object=survey_instance,
metadata={
'complaint_id': str(complaint.id),
'survey_template': survey_template.name
}
)
logger.info(
f"Resolution satisfaction survey sent for complaint {complaint.id}"
)
return {
'status': 'sent',
'survey_instance_id': str(survey_instance.id),
'notification_log_id': str(notification_log.id)
}
except Complaint.DoesNotExist:
error_msg = f"Complaint {complaint_id} not found"
logger.error(error_msg)
return {'status': 'error', 'reason': error_msg}
except Exception as e:
error_msg = f"Error sending resolution survey: {str(e)}"
logger.error(error_msg, exc_info=True)
return {'status': 'error', 'reason': error_msg}
@shared_task
def create_action_from_complaint(complaint_id):
"""
Create PX Action from complaint (if configured).
This task is triggered when a complaint is created,
if the hospital configuration requires automatic action creation.
Args:
complaint_id: UUID of the Complaint
Returns:
dict: Result with action_id
"""
# TODO: Implement in Phase 6
logger.info(f"Should create PX Action from complaint {complaint_id}")
return {'status': 'pending_phase_6'}