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