175 lines
5.8 KiB
Python
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'}
|