""" Celery tasks for MDT app. Handles automated workflows, notifications, and scheduled tasks. """ from celery import shared_task from django.utils import timezone from django.db.models import Q from datetime import timedelta from .models import ( MDTNote, MDTContribution, MDTApproval, MDTMention, ) from .services import ( MDTWorkflowService, MDTStatisticsService, MDTNotificationService, ) from core.models import User from notifications.models import Notification @shared_task def check_stale_mdt_notes(): """ Daily task to check for stale MDT notes (in draft for >30 days). Runs every day at 9:00 AM. """ stale_notes = MDTWorkflowService.get_stale_notes(days=30) notifications_sent = 0 for note in stale_notes: if note.initiated_by: Notification.objects.create( user=note.initiated_by, notification_type='MDT_REMINDER', title=f"Stale MDT Note: {note.title}", message=f"MDT note has been in draft for over 30 days. Please finalize or archive.", ) notifications_sent += 1 return { 'task': 'check_stale_mdt_notes', 'stale_notes_found': len(stale_notes), 'notifications_sent': notifications_sent, 'timestamp': timezone.now().isoformat(), } @shared_task def remind_pending_contributions(): """ Daily task to remind contributors about pending contributions. Runs every day at 10:00 AM. """ # Get notes in draft or pending approval with non-final contributions notes_with_pending = MDTNote.objects.filter( status__in=[MDTNote.Status.DRAFT, MDTNote.Status.PENDING_APPROVAL], contributions__is_final=False ).distinct() total_reminders = 0 for note in notes_with_pending: reminders = MDTWorkflowService.remind_pending_contributors(note) total_reminders += reminders return { 'task': 'remind_pending_contributions', 'notes_checked': notes_with_pending.count(), 'reminders_sent': total_reminders, 'timestamp': timezone.now().isoformat(), } @shared_task def remind_pending_approvals(): """ Daily task to remind approvers about pending approvals. Runs every day at 11:00 AM. """ # Get notes pending approval notes_pending_approval = MDTNote.objects.filter( status=MDTNote.Status.PENDING_APPROVAL ) total_reminders = 0 for note in notes_pending_approval: reminders = MDTWorkflowService.remind_pending_approvers(note) total_reminders += reminders return { 'task': 'remind_pending_approvals', 'notes_checked': notes_pending_approval.count(), 'reminders_sent': total_reminders, 'timestamp': timezone.now().isoformat(), } @shared_task def auto_finalize_ready_notes(): """ Hourly task to auto-finalize notes that meet requirements. Runs every hour. """ # Get notes pending approval that might be ready notes_pending = MDTNote.objects.filter( status=MDTNote.Status.PENDING_APPROVAL ) finalized_count = 0 for note in notes_pending: if MDTWorkflowService.check_and_auto_finalize(note): finalized_count += 1 return { 'task': 'auto_finalize_ready_notes', 'notes_checked': notes_pending.count(), 'notes_finalized': finalized_count, 'timestamp': timezone.now().isoformat(), } @shared_task def generate_weekly_mdt_summary(): """ Weekly task to generate MDT statistics summary. Runs every Monday at 8:00 AM. """ from core.models import Tenant summaries_generated = 0 for tenant in Tenant.objects.filter(is_active=True): # Get statistics for the past week stats = MDTStatisticsService.get_tenant_statistics( str(tenant.id), start_date=timezone.now().date() - timedelta(days=7) ) # Send to clinical coordinator coordinators = User.objects.filter( tenant=tenant, role='CLINICAL_COORDINATOR', is_active=True ) for coordinator in coordinators: message = f""" Weekly MDT Summary: - Total Notes: {stats['total_notes']} - Draft: {stats['draft_notes']} - Pending Approval: {stats['pending_approval']} - Finalized: {stats['finalized_notes']} - Total Contributions: {stats['total_contributions']} - Unique Contributors: {stats['unique_contributors']} - Departments Involved: {stats['departments_involved']} """.strip() Notification.objects.create( user=coordinator, notification_type='WEEKLY_SUMMARY', title=f"Weekly MDT Summary", message=message, ) summaries_generated += 1 return { 'task': 'generate_weekly_mdt_summary', 'summaries_generated': summaries_generated, 'timestamp': timezone.now().isoformat(), } @shared_task def notify_unread_mentions(): """ Daily task to remind users about unread mentions. Runs every day at 4:00 PM. """ # Get unread mentions older than 24 hours cutoff_time = timezone.now() - timedelta(hours=24) unread_mentions = MDTMention.objects.filter( viewed_at__isnull=True, created_at__lt=cutoff_time ).select_related('mentioned_user', 'contribution__mdt_note') # Group by user users_with_unread = {} for mention in unread_mentions: user = mention.mentioned_user if user not in users_with_unread: users_with_unread[user] = [] users_with_unread[user].append(mention) notifications_sent = 0 for user, mentions in users_with_unread.items(): Notification.objects.create( user=user, notification_type='MDT_REMINDER', title=f"Unread MDT Mentions", message=f"You have {len(mentions)} unread mentions in MDT notes", ) notifications_sent += 1 return { 'task': 'notify_unread_mentions', 'unread_mentions': unread_mentions.count(), 'users_notified': notifications_sent, 'timestamp': timezone.now().isoformat(), } @shared_task def archive_old_finalized_notes(): """ Monthly task to archive old finalized notes. Runs on the 1st of each month at 2:00 AM. """ # Archive notes finalized more than 6 months ago cutoff_date = timezone.now() - timedelta(days=180) old_notes = MDTNote.objects.filter( status=MDTNote.Status.FINALIZED, finalized_at__lt=cutoff_date ) archived_count = old_notes.update(status=MDTNote.Status.ARCHIVED) return { 'task': 'archive_old_finalized_notes', 'notes_archived': archived_count, 'cutoff_date': cutoff_date.isoformat(), 'timestamp': timezone.now().isoformat(), }