327 lines
10 KiB
Python
327 lines
10 KiB
Python
"""
|
|
Celery tasks for automated consent management.
|
|
|
|
This module contains background tasks for:
|
|
- Checking consent expiry
|
|
- Sending expiry reminders
|
|
- Notifying staff about expired consents
|
|
"""
|
|
|
|
import logging
|
|
from datetime import date, timedelta
|
|
|
|
from celery import shared_task
|
|
from django.utils import timezone
|
|
|
|
from core.consent_service import ConsentManagementService, ConsentNotificationService
|
|
from core.models import Consent, Tenant
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@shared_task
|
|
def check_expiring_consents() -> int:
|
|
"""
|
|
Check for consents expiring within 30 days and send reminders.
|
|
|
|
This task runs daily at 8:00 AM to check for expiring consents
|
|
and send reminders to patients/caregivers.
|
|
|
|
Returns:
|
|
int: Number of reminders sent
|
|
"""
|
|
count = 0
|
|
|
|
# Get all active tenants
|
|
tenants = Tenant.objects.filter(is_active=True)
|
|
|
|
for tenant in tenants:
|
|
# Get consents expiring in 30, 14, and 7 days
|
|
for days_threshold in [30, 14, 7]:
|
|
target_date = date.today() + timedelta(days=days_threshold)
|
|
|
|
expiring_consents = Consent.objects.filter(
|
|
tenant=tenant,
|
|
is_active=True,
|
|
expiry_date=target_date
|
|
).select_related('patient')
|
|
|
|
for consent in expiring_consents:
|
|
try:
|
|
ConsentNotificationService.send_expiry_reminder(consent)
|
|
count += 1
|
|
logger.info(f"Sent expiry reminder for consent {consent.id} ({days_threshold} days)")
|
|
except Exception as e:
|
|
logger.error(f"Failed to send expiry reminder for consent {consent.id}: {e}")
|
|
|
|
logger.info(f"Sent {count} consent expiry reminders")
|
|
return count
|
|
|
|
|
|
@shared_task
|
|
def check_expired_consents() -> int:
|
|
"""
|
|
Check for expired consents and send notifications.
|
|
|
|
This task runs daily at 9:00 AM to check for newly expired consents
|
|
and notify patients/caregivers and reception staff.
|
|
|
|
Returns:
|
|
int: Number of expired consents found
|
|
"""
|
|
total_count = 0
|
|
|
|
# Get all active tenants
|
|
tenants = Tenant.objects.filter(is_active=True)
|
|
|
|
for tenant in tenants:
|
|
# Get consents that expired yesterday (newly expired)
|
|
yesterday = date.today() - timedelta(days=1)
|
|
|
|
newly_expired = Consent.objects.filter(
|
|
tenant=tenant,
|
|
is_active=True,
|
|
expiry_date=yesterday
|
|
).select_related('patient')
|
|
|
|
expired_list = []
|
|
|
|
for consent in newly_expired:
|
|
try:
|
|
# Send notification to patient/caregiver
|
|
ConsentNotificationService.send_expired_notification(consent)
|
|
|
|
# Add to list for reception notification
|
|
expired_list.append({
|
|
'consent_id': str(consent.id),
|
|
'patient_name': consent.patient.full_name_en,
|
|
'patient_mrn': consent.patient.mrn,
|
|
'consent_type': consent.get_consent_type_display(),
|
|
})
|
|
|
|
total_count += 1
|
|
logger.info(f"Processed expired consent {consent.id}")
|
|
except Exception as e:
|
|
logger.error(f"Failed to process expired consent {consent.id}: {e}")
|
|
|
|
# Notify reception staff
|
|
if expired_list:
|
|
try:
|
|
ConsentNotificationService.notify_reception_expired_consents(tenant, expired_list)
|
|
except Exception as e:
|
|
logger.error(f"Failed to notify reception for tenant {tenant.name}: {e}")
|
|
|
|
logger.info(f"Found {total_count} newly expired consents")
|
|
return total_count
|
|
|
|
|
|
@shared_task
|
|
def generate_consent_expiry_report() -> dict:
|
|
"""
|
|
Generate weekly consent expiry report for administrators.
|
|
|
|
This task runs weekly on Monday at 8:00 AM to generate
|
|
a comprehensive report of consent status.
|
|
|
|
Returns:
|
|
dict: Report data
|
|
"""
|
|
from core.tasks import send_email_task, create_notification_task
|
|
from core.models import User
|
|
|
|
report = {
|
|
'generated_at': timezone.now().isoformat(),
|
|
'tenants': []
|
|
}
|
|
|
|
# Get all active tenants
|
|
tenants = Tenant.objects.filter(is_active=True)
|
|
|
|
for tenant in tenants:
|
|
# Get statistics
|
|
stats = ConsentManagementService.get_consent_statistics(tenant)
|
|
|
|
# Get expiring and expired consents
|
|
expiring = ConsentManagementService.get_expiring_consents(tenant, days_threshold=30)
|
|
expired = ConsentManagementService.get_expired_consents(tenant)
|
|
|
|
tenant_report = {
|
|
'tenant_name': tenant.name,
|
|
'statistics': stats,
|
|
'expiring_count': len(expiring),
|
|
'expired_count': len(expired),
|
|
'expiring_consents': expiring[:10], # Top 10
|
|
'expired_consents': expired[:10], # Top 10
|
|
}
|
|
|
|
report['tenants'].append(tenant_report)
|
|
|
|
# Send report to administrators
|
|
admins = User.objects.filter(
|
|
tenant=tenant,
|
|
role=User.Role.ADMIN,
|
|
is_active=True
|
|
)
|
|
|
|
# Prepare email message
|
|
message = f"""
|
|
Consent Expiry Report - {tenant.name}
|
|
Generated: {timezone.now().strftime('%Y-%m-%d %H:%M:%S')}
|
|
|
|
Summary:
|
|
--------
|
|
Total Active Consents: {stats['total_active']}
|
|
Expiring in 30 days: {stats['expiring_30_days']}
|
|
Expiring in 7 days: {stats['expiring_7_days']}
|
|
Expired: {stats['expired']}
|
|
Patients without consent: {stats['patients_without_consent']}
|
|
|
|
Action Required:
|
|
---------------
|
|
- {len(expired)} expired consents need immediate renewal
|
|
- {len(expiring)} consents expiring within 30 days
|
|
|
|
Please review the consent management dashboard for full details.
|
|
|
|
Best regards,
|
|
Agdar HIS System
|
|
"""
|
|
|
|
for admin in admins:
|
|
# Send email
|
|
if admin.email:
|
|
send_email_task.delay(
|
|
subject=f"Weekly Consent Report - {tenant.name}",
|
|
message=message,
|
|
recipient_list=[admin.email]
|
|
)
|
|
|
|
# Create in-app notification
|
|
create_notification_task.delay(
|
|
user_id=str(admin.id),
|
|
title="Weekly Consent Report",
|
|
message=f"{stats['expired']} expired, {stats['expiring_30_days']} expiring soon",
|
|
notification_type='INFO',
|
|
related_object_type='consent',
|
|
related_object_id=None
|
|
)
|
|
|
|
logger.info(f"Generated consent expiry report for {len(tenants)} tenants")
|
|
return report
|
|
|
|
|
|
@shared_task
|
|
def auto_deactivate_expired_consents() -> int:
|
|
"""
|
|
Automatically deactivate consents that have been expired for more than 90 days.
|
|
|
|
This task runs monthly on the 1st at 2:00 AM to clean up old expired consents.
|
|
|
|
Returns:
|
|
int: Number of consents deactivated
|
|
"""
|
|
threshold_date = date.today() - timedelta(days=90)
|
|
|
|
# Get consents expired for more than 90 days
|
|
old_expired = Consent.objects.filter(
|
|
is_active=True,
|
|
expiry_date__lt=threshold_date
|
|
)
|
|
|
|
count = old_expired.count()
|
|
|
|
# Deactivate them
|
|
old_expired.update(is_active=False)
|
|
|
|
logger.info(f"Auto-deactivated {count} consents expired for more than 90 days")
|
|
return count
|
|
|
|
|
|
@shared_task
|
|
def send_consent_renewal_batch(consent_ids: list) -> dict:
|
|
"""
|
|
Send consent renewal reminders in batch.
|
|
|
|
Args:
|
|
consent_ids: List of consent IDs to send reminders for
|
|
|
|
Returns:
|
|
dict: Results of batch operation
|
|
"""
|
|
results = {
|
|
'sent': 0,
|
|
'failed': 0,
|
|
'errors': []
|
|
}
|
|
|
|
for consent_id in consent_ids:
|
|
try:
|
|
consent = Consent.objects.get(id=consent_id)
|
|
ConsentNotificationService.send_expiry_reminder(consent)
|
|
results['sent'] += 1
|
|
except Consent.DoesNotExist:
|
|
results['errors'].append(f"Consent {consent_id} not found")
|
|
results['failed'] += 1
|
|
except Exception as e:
|
|
results['errors'].append(f"Consent {consent_id}: {str(e)}")
|
|
results['failed'] += 1
|
|
|
|
logger.info(f"Batch consent renewal: {results['sent']} sent, {results['failed']} failed")
|
|
return results
|
|
|
|
|
|
@shared_task
|
|
def check_consent_before_appointment(appointment_id: str) -> bool:
|
|
"""
|
|
Check consent validity before appointment confirmation.
|
|
|
|
This task is triggered when an appointment is being confirmed
|
|
to ensure patient has valid consent.
|
|
|
|
Args:
|
|
appointment_id: UUID of the appointment
|
|
|
|
Returns:
|
|
bool: True if consent is valid
|
|
"""
|
|
try:
|
|
from appointments.models import Appointment
|
|
|
|
appointment = Appointment.objects.select_related('patient').get(id=appointment_id)
|
|
|
|
# Validate consent
|
|
is_valid, missing = ConsentManagementService.validate_consent_before_booking(
|
|
patient=appointment.patient
|
|
)
|
|
|
|
if not is_valid:
|
|
logger.warning(
|
|
f"Appointment {appointment_id} has invalid consent. Missing: {missing}"
|
|
)
|
|
|
|
# Send notification to reception
|
|
from core.tasks import create_notification_task
|
|
from core.models import User
|
|
|
|
reception_users = User.objects.filter(
|
|
tenant=appointment.tenant,
|
|
role=User.Role.FRONT_DESK,
|
|
is_active=True
|
|
)
|
|
|
|
for user in reception_users:
|
|
create_notification_task.delay(
|
|
user_id=str(user.id),
|
|
title="Consent Required",
|
|
message=f"Patient {appointment.patient.mrn} needs consent renewal before appointment",
|
|
notification_type='WARNING',
|
|
related_object_type='appointment',
|
|
related_object_id=str(appointment_id)
|
|
)
|
|
|
|
return is_valid
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to check consent for appointment {appointment_id}: {e}")
|
|
return False
|