feat: add email notice for evaluation

This commit is contained in:
2025-11-16 14:48:42 +08:00
parent a2f48b6bf4
commit aec9cc6adc
2 changed files with 163 additions and 9 deletions
+73 -6
View File
@@ -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):
+87
View File
@@ -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):
"""获取人员汇总列表"""
@@ -102,9 +143,28 @@ class EvaluationRecordViewSet(viewsets.ModelViewSet):
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}'