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 json
import logging import logging
import threading
from django.conf import settings from django.conf import settings
from django.core.mail import send_mail from django.core.mail import send_mail
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.utils import timezone
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -75,6 +77,19 @@ LABEL_MAP = {
'qq': 'QQ', 'qq': 'QQ',
'email': '邮箱', 'email': '邮箱',
'grader_major': '年级专业', 'grader_major': '年级专业',
# 考评相关
'personnel': '人员姓名',
'grade': '年级',
'item_description': '加/扣分事项说明',
'bonus_score': '加分数值',
'deduction_score': '扣分数值',
'total_score': '总分',
'evaluation_date': '考评日期',
'remarks': '备注',
'count': '记录数量',
'records_count': '记录数量',
'personnel_count': '人员数量',
} }
# 这些key为元信息,不参与详情表格展示 # 这些key为元信息,不参与详情表格展示
@@ -126,6 +141,21 @@ def _build_data_items(instance_data: dict):
class EmailNotificationService: 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 @staticmethod
def get_notification_settings(): def get_notification_settings():
"""获取通知设置""" """获取通知设置"""
@@ -229,7 +259,7 @@ class EmailNotificationService:
@staticmethod @staticmethod
def send_item_operation_notification(operation_type, item_instance, user_info=None): def send_item_operation_notification(operation_type, item_instance, user_info=None):
"""发送物品操作通知""" """异步发送物品操作通知"""
try: try:
instance_data = { instance_data = {
'id': getattr(item_instance, 'id', None), 'id': getattr(item_instance, 'id', None),
@@ -241,15 +271,15 @@ class EmailNotificationService:
'timestamp': str(getattr(item_instance, 'updated_at', '')), 'timestamp': str(getattr(item_instance, 'updated_at', '')),
} }
EmailNotificationService.send_operation_notification( EmailNotificationService._send_notification_async(
operation_type, '物品', instance_data, user_info operation_type, '物品', instance_data, user_info
) )
except Exception as e: except Exception as e:
logger.error(f"发送物品操作通知失败: {e}") logger.error(f"准备物品操作通知失败: {e}")
@staticmethod @staticmethod
def send_finance_operation_notification(operation_type, finance_instance, user_info=None): def send_finance_operation_notification(operation_type, finance_instance, user_info=None):
"""发送财务记录操作通知""" """异步发送财务记录操作通知"""
try: try:
instance_data = { instance_data = {
'id': getattr(finance_instance, 'id', None), 'id': getattr(finance_instance, 'id', None),
@@ -262,11 +292,48 @@ class EmailNotificationService:
'timestamp': str(getattr(finance_instance, 'transaction_date', '')), 'timestamp': str(getattr(finance_instance, 'transaction_date', '')),
} }
EmailNotificationService.send_operation_notification( EmailNotificationService._send_notification_async(
operation_type, '财务记录', instance_data, user_info operation_type, '财务记录', instance_data, user_info
) )
except Exception as e: 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 @staticmethod
def update_notification_emails(emails_data): def update_notification_emails(emails_data):
+90 -3
View File
@@ -1,5 +1,6 @@
import csv import csv
import io import io
import logging
from datetime import datetime, date, timedelta from datetime import datetime, date, timedelta
from decimal import Decimal from decimal import Decimal
import os import os
@@ -20,11 +21,14 @@ from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, PatternFill from openpyxl.styles import Font, Alignment, PatternFill
from finance.models import Department from finance.models import Department
from email_notice.services import EmailNotificationService
from .filters import EvaluationRecordFilter from .filters import EvaluationRecordFilter
from .models import EvaluationRecord from .models import EvaluationRecord
from .serializers import EvaluationRecordSerializer, PersonnelSummarySerializer from .serializers import EvaluationRecordSerializer, PersonnelSummarySerializer
logger = logging.getLogger(__name__)
class EvaluationRecordViewSet(viewsets.ModelViewSet): 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_fields = ['evaluation_date', 'created_at', 'total_score', 'bonus_score', 'deduction_score']
ordering = ['-evaluation_date', '-created_at'] 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') @action(detail=False, methods=['get'], url_path='personnel-summary')
def personnel_summary(self, request, *args, **kwargs): def personnel_summary(self, request, *args, **kwargs):
"""获取人员汇总列表""" """获取人员汇总列表"""
@@ -98,13 +139,32 @@ class EvaluationRecordViewSet(viewsets.ModelViewSet):
# 查找要删除的记录 # 查找要删除的记录
queryset = self.get_queryset().filter(**filter_params) queryset = self.get_queryset().filter(**filter_params)
deleted_count = queryset.count() deleted_count = queryset.count()
if deleted_count == 0: if deleted_count == 0:
return Response({'detail': '未找到要删除的记录'}, status=status.HTTP_404_NOT_FOUND) 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() 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({ return Response({
'detail': f'成功删除 {deleted_count} 条记录', 'detail': f'成功删除 {deleted_count} 条记录',
'deleted_count': deleted_count 'deleted_count': deleted_count
@@ -204,6 +264,18 @@ class EvaluationRecordViewSet(viewsets.ModelViewSet):
output.seek(0) output.seek(0)
filename = f'人员考评记录_{timezone.now().strftime("%Y%m%d_%H%M%S")}.xlsx' 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( response = HttpResponse(
output.getvalue(), output.getvalue(),
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
@@ -504,6 +576,21 @@ class EvaluationRecordViewSet(viewsets.ModelViewSet):
EvaluationRecord.objects.all().delete() EvaluationRecord.objects.all().delete()
EvaluationRecord.objects.bulk_create(records_to_create) 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 '' skip_msg = f',跳过 {skipped_count} 条已存在的记录' if skipped_count > 0 else ''
return Response({ return Response({
'detail': f'成功导入 {len(records_to_create)} 条记录{skip_msg}' 'detail': f'成功导入 {len(records_to_create)} 条记录{skip_msg}'