diff --git a/src/backend/email_notice/services.py b/src/backend/email_notice/services.py index e287518..389499f 100644 --- a/src/backend/email_notice/services.py +++ b/src/backend/email_notice/services.py @@ -1,10 +1,12 @@ import json import logging +import threading from django.conf import settings from django.core.mail import send_mail from django.template.loader import render_to_string from django.core.exceptions import ObjectDoesNotExist +from django.utils import timezone logger = logging.getLogger(__name__) @@ -75,6 +77,19 @@ LABEL_MAP = { 'qq': 'QQ', 'email': '邮箱', 'grader_major': '年级专业', + + # 考评相关 + 'personnel': '人员姓名', + 'grade': '年级', + 'item_description': '加/扣分事项说明', + 'bonus_score': '加分数值', + 'deduction_score': '扣分数值', + 'total_score': '总分', + 'evaluation_date': '考评日期', + 'remarks': '备注', + 'count': '记录数量', + 'records_count': '记录数量', + 'personnel_count': '人员数量', } # 这些key为元信息,不参与详情表格展示 @@ -126,6 +141,21 @@ def _build_data_items(instance_data: dict): class EmailNotificationService: """邮件通知服务""" + @staticmethod + def _send_notification_async(operation_type, model_name, instance_data, user_info=None): + """异步发送邮件通知的内部方法""" + def send_email_task(): + try: + EmailNotificationService.send_operation_notification( + operation_type, model_name, instance_data, user_info + ) + except Exception as e: + logger.error(f"异步发送邮件通知失败: {e}") + + # 使用线程异步发送 + thread = threading.Thread(target=send_email_task, daemon=True) + thread.start() + @staticmethod def get_notification_settings(): """获取通知设置""" @@ -229,7 +259,7 @@ class EmailNotificationService: @staticmethod def send_item_operation_notification(operation_type, item_instance, user_info=None): - """发送物品操作通知""" + """异步发送物品操作通知""" try: instance_data = { 'id': getattr(item_instance, 'id', None), @@ -241,15 +271,15 @@ class EmailNotificationService: 'timestamp': str(getattr(item_instance, 'updated_at', '')), } - EmailNotificationService.send_operation_notification( + EmailNotificationService._send_notification_async( operation_type, '物品', instance_data, user_info ) except Exception as e: - logger.error(f"发送物品操作通知失败: {e}") + logger.error(f"准备物品操作通知失败: {e}") @staticmethod def send_finance_operation_notification(operation_type, finance_instance, user_info=None): - """发送财务记录操作通知""" + """异步发送财务记录操作通知""" try: instance_data = { 'id': getattr(finance_instance, 'id', None), @@ -262,11 +292,48 @@ class EmailNotificationService: 'timestamp': str(getattr(finance_instance, 'transaction_date', '')), } - EmailNotificationService.send_operation_notification( + EmailNotificationService._send_notification_async( operation_type, '财务记录', instance_data, user_info ) except Exception as e: - logger.error(f"发送财务记录操作通知失败: {e}") + logger.error(f"准备财务记录操作通知失败: {e}") + + @staticmethod + def send_evaluation_operation_notification(operation_type, evaluation_instance=None, user_info=None, operation_description=None): + """异步发送考评操作通知""" + try: + if evaluation_instance: + # 单个考评记录的操作通知 + instance_data = { + 'id': getattr(evaluation_instance, 'id', None), + 'personnel': getattr(evaluation_instance, 'personnel', ''), + 'department': str(getattr(evaluation_instance, 'department', '')), + 'grade': getattr(evaluation_instance, 'grade', ''), + 'item_description': getattr(evaluation_instance, 'item_description', ''), + 'bonus_score': str(getattr(evaluation_instance, 'bonus_score', 0)), + 'deduction_score': str(getattr(evaluation_instance, 'deduction_score', 0)), + 'total_score': str(getattr(evaluation_instance, 'total_score', 0)), + 'evaluation_date': str(getattr(evaluation_instance, 'evaluation_date', '')), + 'remarks': getattr(evaluation_instance, 'remarks', ''), + 'timestamp': str(getattr(evaluation_instance, 'updated_at', '')), + } + model_name = '考评记录' + else: + # 批量操作通知(导入/导出/删除人员) + instance_data = { + 'operation_type': operation_description or operation_type, + 'timestamp': str(timezone.now()), + } + model_name = '考评人员' + + if operation_description: + instance_data['operation_type'] = operation_description + + EmailNotificationService._send_notification_async( + operation_type, model_name, instance_data, user_info + ) + except Exception as e: + logger.error(f"准备考评操作通知失败: {e}") @staticmethod def update_notification_emails(emails_data): diff --git a/src/backend/evaluation/views.py b/src/backend/evaluation/views.py index 25a8471..3419900 100644 --- a/src/backend/evaluation/views.py +++ b/src/backend/evaluation/views.py @@ -1,5 +1,6 @@ import csv import io +import logging from datetime import datetime, date, timedelta from decimal import Decimal import os @@ -20,11 +21,14 @@ from openpyxl import Workbook from openpyxl.styles import Font, Alignment, PatternFill from finance.models import Department +from email_notice.services import EmailNotificationService from .filters import EvaluationRecordFilter from .models import EvaluationRecord from .serializers import EvaluationRecordSerializer, PersonnelSummarySerializer +logger = logging.getLogger(__name__) + class EvaluationRecordViewSet(viewsets.ModelViewSet): """考评记录视图集""" @@ -37,6 +41,43 @@ class EvaluationRecordViewSet(viewsets.ModelViewSet): ordering_fields = ['evaluation_date', 'created_at', 'total_score', 'bonus_score', 'deduction_score'] ordering = ['-evaluation_date', '-created_at'] + def perform_create(self, serializer): + """创建考评记录时发送邮箱通知""" + instance = serializer.save() + + # 异步发送邮箱通知 + user_info = getattr(self.request.user, 'username', '系统') if hasattr(self.request, 'user') else '系统' + EmailNotificationService.send_evaluation_operation_notification( + 'CREATE', + evaluation_instance=instance, + user_info=user_info + ) + + def perform_update(self, serializer): + """更新考评记录时发送邮箱通知""" + instance = serializer.save() + + # 异步发送邮箱通知 + user_info = getattr(self.request.user, 'username', '系统') if hasattr(self.request, 'user') else '系统' + EmailNotificationService.send_evaluation_operation_notification( + 'UPDATE', + evaluation_instance=instance, + user_info=user_info + ) + + def perform_destroy(self, instance): + """删除考评记录时发送邮箱通知""" + # 异步发送邮箱通知 + user_info = getattr(self.request.user, 'username', '系统') if hasattr(self.request, 'user') else '系统' + EmailNotificationService.send_evaluation_operation_notification( + 'DELETE', + evaluation_instance=instance, + user_info=user_info + ) + + # 执行删除 + instance.delete() + @action(detail=False, methods=['get'], url_path='personnel-summary') def personnel_summary(self, request, *args, **kwargs): """获取人员汇总列表""" @@ -98,13 +139,32 @@ class EvaluationRecordViewSet(viewsets.ModelViewSet): # 查找要删除的记录 queryset = self.get_queryset().filter(**filter_params) deleted_count = queryset.count() - + if deleted_count == 0: return Response({'detail': '未找到要删除的记录'}, status=status.HTTP_404_NOT_FOUND) - + + # 收集删除的记录信息用于通知 + deleted_info = { + 'personnel': personnel_name, + 'department': department_name if department_name else '未指定', + 'grade': grade if grade else '未指定', + 'count': deleted_count + } + # 删除记录 queryset.delete() - + + # 异步发送邮箱通知 + user_info = getattr(request.user, 'username', '系统') if hasattr(request, 'user') else '系统' + operation_description = f"删除人员考评记录 - {personnel_name}({deleted_info['department']}-{deleted_info['grade']}),共{deleted_count}条记录" + + EmailNotificationService.send_evaluation_operation_notification( + 'DELETE', + evaluation_instance=None, + user_info=user_info, + operation_description=operation_description + ) + return Response({ 'detail': f'成功删除 {deleted_count} 条记录', 'deleted_count': deleted_count @@ -204,6 +264,18 @@ class EvaluationRecordViewSet(viewsets.ModelViewSet): output.seek(0) filename = f'人员考评记录_{timezone.now().strftime("%Y%m%d_%H%M%S")}.xlsx' + + # 异步发送邮箱通知 + user_info = getattr(request.user, 'username', '系统') if hasattr(request, 'user') else '系统' + operation_description = f"导出考评记录 - {len(personnel_groups)}名人员,共{queryset.count()}条记录" + + EmailNotificationService.send_evaluation_operation_notification( + 'CREATE', + evaluation_instance=None, + user_info=user_info, + operation_description=operation_description + ) + response = HttpResponse( output.getvalue(), content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' @@ -504,6 +576,21 @@ class EvaluationRecordViewSet(viewsets.ModelViewSet): EvaluationRecord.objects.all().delete() EvaluationRecord.objects.bulk_create(records_to_create) + # 异步发送邮箱通知 + user_info = getattr(request.user, 'username', '系统') if hasattr(request, 'user') else '系统' + operation_description = f"导入考评人员 - {len(records_to_create)}条记录" + if is_template_format: + operation_description += "(样表格式)" + else: + operation_description += "(完整格式)" + + EmailNotificationService.send_evaluation_operation_notification( + 'CREATE', + evaluation_instance=None, + user_info=user_info, + operation_description=operation_description + ) + skip_msg = f',跳过 {skipped_count} 条已存在的记录' if skipped_count > 0 else '' return Response({ 'detail': f'成功导入 {len(records_to_create)} 条记录{skip_msg}'