import logging from celery import shared_task from django.utils import timezone logger = logging.getLogger(__name__) @shared_task def process_scheduled_reports(): from apps.reports.models import ReportSchedule now = timezone.now() schedules = ReportSchedule.objects.filter( is_active=True, next_run_at__lte=now, ).select_related("report") dispatched = 0 for schedule in schedules: generate_and_deliver_report.delay(str(schedule.id)) dispatched += 1 logger.info(f"Dispatched {dispatched} scheduled reports") return {"dispatched": dispatched} @shared_task def generate_and_deliver_report(schedule_id): from apps.reports.models import ReportSchedule, GeneratedReport from apps.reports.services import ReportBuilderService from apps.notifications.services import NotificationService try: schedule = ReportSchedule.objects.select_related("report").get(id=schedule_id) except ReportSchedule.DoesNotExist: logger.error(f"ReportSchedule {schedule_id} not found") return {"status": "error", "reason": "not_found"} try: report = schedule.report report_data = ReportBuilderService.generate_report_data( data_source=report.data_source, filter_config=report.filter_config, column_config=report.column_config, grouping_config=report.grouping_config, sort_config=report.sort_config, ) summary = ReportBuilderService.generate_summary( data_source=report.data_source, filter_config=report.filter_config, ) row_count = len(report_data.get("rows", [])) generated = GeneratedReport.objects.create( saved_report=report, name=report.name, data_source=report.data_source, filter_config=report.filter_config, column_config=report.column_config, grouping_config=report.grouping_config, chart_config=report.chart_config, data=report_data, summary=summary, chart_data={}, row_count=row_count, hospital=report.hospital, ) report.last_run_at = timezone.now() report.last_run_count = row_count report.save(update_fields=["last_run_at", "last_run_count"]) schedule.last_run_at = timezone.now() schedule.last_error = "" next_run = schedule.calculate_next_run() if schedule.frequency == "once": schedule.is_active = False schedule.next_run_at = None else: schedule.next_run_at = next_run schedule.save(update_fields=["last_run_at", "next_run_at", "is_active", "last_error"]) subject = f"Scheduled Report: {report.name}" message = ( f"Report: {report.name}\n" f"Data Source: {report.get_data_source_display()}\n" f"Rows: {row_count}\n" f"Generated: {timezone.now().strftime('%Y-%m-%d %H:%M')}\n\n" f"View the full report in the PX360 dashboard." ) for recipient in schedule.recipients: try: NotificationService.send_email( email=recipient, subject=subject, message=message, related_object=generated, notification_type="report", ) except Exception as email_err: logger.error(f"Failed to send report email to {recipient}: {email_err}") logger.info(f"Generated and delivered report for schedule {schedule_id}") return {"status": "success", "generated_report_id": str(generated.id), "row_count": row_count} except Exception as e: error_msg = str(e) logger.error(f"Error generating report for schedule {schedule_id}: {error_msg}", exc_info=True) schedule.last_error = error_msg schedule.save(update_fields=["last_error"]) return {"status": "error", "reason": error_msg}