feat: add email notice for evaluation
This commit is contained in:
@@ -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):
|
||||||
|
|||||||
@@ -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}'
|
||||||
|
|||||||
Reference in New Issue
Block a user