From 1d9bd47bac304581d05c8cc0221ba24016efd8c8 Mon Sep 17 00:00:00 2001 From: 27942 Date: Wed, 31 Dec 2025 12:28:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86=E8=BD=AF=E5=88=A0?= =?UTF-8?q?=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- User/migrations/0014_operationlog.py | 40 ++++ User/models.py | 24 ++ User/urls.py | 3 +- User/utils.py | 75 +++++++ User/views.py | 209 +++++++++++++++++- ..._alter_bid_biddingannouncement_and_more.py | 28 +++ business/views.py | 184 ++++++++++++++- finance/views.py | 123 ++++++++++- 8 files changed, 680 insertions(+), 6 deletions(-) create mode 100644 User/migrations/0014_operationlog.py create mode 100644 business/migrations/0026_alter_bid_biddingannouncement_and_more.py diff --git a/User/migrations/0014_operationlog.py b/User/migrations/0014_operationlog.py new file mode 100644 index 0000000..88c9753 --- /dev/null +++ b/User/migrations/0014_operationlog.py @@ -0,0 +1,40 @@ +# Generated migration for operation log + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('User', '0013_add_is_deleted_fields'), + ] + + operations = [ + migrations.CreateModel( + name='OperationLog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('operator', models.CharField(max_length=100)), + ('operator_id', models.IntegerField(blank=True, null=True)), + ('operation_type', models.CharField(max_length=50)), + ('module', models.CharField(max_length=50)), + ('action', models.CharField(max_length=200)), + ('target_type', models.CharField(max_length=100)), + ('target_id', models.CharField(blank=True, max_length=100, null=True)), + ('target_name', models.CharField(blank=True, max_length=200, null=True)), + ('old_data', models.TextField(blank=True, null=True)), + ('new_data', models.TextField(blank=True, null=True)), + ('ip_address', models.CharField(blank=True, max_length=50, null=True)), + ('user_agent', models.CharField(blank=True, max_length=500, null=True)), + ('request_path', models.CharField(blank=True, max_length=500, null=True)), + ('remark', models.TextField(blank=True, null=True)), + ('create_time', models.DateTimeField(auto_now_add=True)), + ], + options={ + 'verbose_name': '操作日志', + 'verbose_name_plural': '操作日志', + 'db_table': 'operation_log', + 'ordering': ['-create_time'], + }, + ), + ] diff --git a/User/models.py b/User/models.py index 96bcd67..58a693e 100644 --- a/User/models.py +++ b/User/models.py @@ -44,3 +44,27 @@ class Approval(models.Model): is_deleted = models.BooleanField(default=False) # 软删除标记 +class OperationLog(models.Model): + """操作日志模型 - 记录高风险操作""" + operator = models.CharField(max_length=100) # 操作人用户名 + operator_id = models.IntegerField(null=True, blank=True) # 操作人ID + operation_type = models.CharField(max_length=50) # 操作类型:DELETE, CREATE, UPDATE, APPROVE等 + module = models.CharField(max_length=50) # 模块:User, Business, Finance等 + action = models.CharField(max_length=200) # 操作描述:删除用户、创建立项等 + target_type = models.CharField(max_length=100) # 目标类型:User, ProjectRegistration等 + target_id = models.CharField(max_length=100, null=True, blank=True) # 目标ID + target_name = models.CharField(max_length=200, null=True, blank=True) # 目标名称(如用户名、项目名等) + old_data = models.TextField(null=True, blank=True) # 操作前的数据(JSON格式) + new_data = models.TextField(null=True, blank=True) # 操作后的数据(JSON格式) + ip_address = models.CharField(max_length=50, null=True, blank=True) # IP地址 + user_agent = models.CharField(max_length=500, null=True, blank=True) # 用户代理 + request_path = models.CharField(max_length=500, null=True, blank=True) # 请求路径 + remark = models.TextField(null=True, blank=True) # 备注 + create_time = models.DateTimeField(auto_now_add=True) # 操作时间 + + class Meta: + db_table = 'operation_log' + ordering = ['-create_time'] + verbose_name = '操作日志' + verbose_name_plural = '操作日志' + diff --git a/User/urls.py b/User/urls.py index 9183db2..b383ca0 100644 --- a/User/urls.py +++ b/User/urls.py @@ -1,5 +1,5 @@ from django.urls import path -from .views import CreateUserView,LoginView,EditorialStaffView,PersonnelDetailsView,DepartmentView,PersonnelListView,AddDepartment,DeleteDepartment,Personlist,roxyExhibition,approvalProcessing,personneldisplay,DeleteUser +from .views import CreateUserView,LoginView,EditorialStaffView,PersonnelDetailsView,DepartmentView,PersonnelListView,AddDepartment,DeleteDepartment,Personlist,roxyExhibition,approvalProcessing,personneldisplay,DeleteUser,OperationLogView urlpatterns = [ path('create-user',CreateUserView.as_view(),name='create-user'), path('login',LoginView.as_view(),name='login'), @@ -14,4 +14,5 @@ urlpatterns = [ path('approval_processing',approvalProcessing.as_view(),name='approval_processing'), path('personneldisplay',personneldisplay.as_view(),name='personneldisplay'), path('deleteUser',DeleteUser.as_view(),name='deleteUser'), + path('operation-log',OperationLogView.as_view(),name='operation-log'), ] \ No newline at end of file diff --git a/User/utils.py b/User/utils.py index 650c903..4e9c88b 100644 --- a/User/utils.py +++ b/User/utils.py @@ -1,6 +1,9 @@ """ 审批相关的工具函数 """ +import json +from datetime import datetime +from .models import OperationLog, User def is_department_id(value): """ @@ -60,3 +63,75 @@ def format_personincharge(value, is_department=False): # 审批员用户名:保持原样,但确保是字符串 return str(value).strip() + +def log_operation(request, operation_type, module, action, target_type, target_id=None, + target_name=None, old_data=None, new_data=None, remark=None): + """ + 记录操作日志 + + Args: + request: Django request对象 + operation_type: 操作类型(DELETE, CREATE, UPDATE, APPROVE等) + module: 模块名称(User, Business, Finance等) + action: 操作描述(如"删除用户"、"创建立项"等) + target_type: 目标类型(如User, ProjectRegistration等) + target_id: 目标ID + target_name: 目标名称(如用户名、项目名等) + old_data: 操作前的数据(字典,会自动转换为JSON) + new_data: 操作后的数据(字典,会自动转换为JSON) + remark: 备注信息 + + Returns: + OperationLog对象 + """ + try: + # 获取操作人信息 + token = request.META.get('token') or request.META.get('HTTP_AUTHORIZATION', '').replace('Bearer ', '') + operator = '未知用户' + operator_id = None + + if token: + try: + user = User.objects.get(token=token, is_deleted=False) + operator = user.username + operator_id = user.id + except User.DoesNotExist: + pass + + # 获取IP地址 + ip_address = request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')[0].strip() + if not ip_address: + ip_address = request.META.get('REMOTE_ADDR', '') + + # 获取用户代理 + user_agent = request.META.get('HTTP_USER_AGENT', '') + + # 转换数据为JSON字符串 + old_data_str = json.dumps(old_data, ensure_ascii=False) if old_data else None + new_data_str = json.dumps(new_data, ensure_ascii=False) if new_data else None + + # 创建日志记录 + log = OperationLog.objects.create( + operator=operator, + operator_id=operator_id, + operation_type=operation_type, + module=module, + action=action, + target_type=target_type, + target_id=str(target_id) if target_id else None, + target_name=target_name, + old_data=old_data_str, + new_data=new_data_str, + ip_address=ip_address, + user_agent=user_agent, + request_path=request.path, + remark=remark + ) + return log + except Exception as e: + # 日志记录失败不应该影响主业务流程 + import logging + logger = logging.getLogger(__name__) + logger.error(f"记录操作日志失败: {str(e)}") + return None + diff --git a/User/views.py b/User/views.py index af6b5d1..6d347ea 100644 --- a/User/views.py +++ b/User/views.py @@ -4,7 +4,7 @@ from rest_framework.response import Response from rest_framework import status import json import ast -from .models import User, Approval, Department +from .models import User, Approval, Department, OperationLog from business.models import permission from finance.models import Income, Accounts, Payment, Reimbursement, BonusChange from finance.models import Invoice @@ -15,7 +15,7 @@ from django.contrib.sessions.backends.db import SessionStore from django.db.models import Count, Q from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from collections import defaultdict -from .utils import is_department_id +from .utils import is_department_id, log_operation class CreateUserView(APIView): @@ -157,6 +157,28 @@ class CreateUserView(APIView): user.role.add(*role_list) if department_id: user.department.add(*department_id) + + # 记录操作日志 + new_data = { + 'id': user.id, + 'username': user.username, + 'account': user.account, + 'position': user.position, + 'team': user.team, + 'state': user.state + } + log_operation( + request=request, + operation_type='CREATE', + module='User', + action='新增用户', + target_type='User', + target_id=user.id, + target_name=user.username, + new_data=new_data, + remark=f'新增用户:{user.username}(账号:{user.account})' + ) + return Response({'message': '添加人员成功', 'code': 0}, status=status.HTTP_200_OK) except Exception as e: # 捕获数据库操作异常 @@ -276,7 +298,44 @@ class EditorialStaffView(APIView): role_id = [role_id] user.role.set(role_id) + # 记录操作前的数据 + old_data = { + 'id': user.id, + 'username': user.username, + 'account': user.account, + 'position': user.position, + 'team': user.team, + 'state': user.state, + 'salary': user.salary + } + user.save() + + # 记录操作后的数据 + new_data = { + 'id': user.id, + 'username': user.username, + 'account': user.account, + 'position': user.position, + 'team': user.team, + 'state': user.state, + 'salary': user.salary + } + + # 记录操作日志 + log_operation( + request=request, + operation_type='UPDATE', + module='User', + action='编辑用户信息', + target_type='User', + target_id=user.id, + target_name=user.username, + old_data=old_data, + new_data=new_data, + remark=f'编辑用户信息:{user.username}(账号:{user.account})' + ) + return Response({'message': '修改成功', 'code': 0}, status=status.HTTP_200_OK) @@ -310,6 +369,18 @@ class LoginView(APIView): user.token = token user.save() + # 记录登录日志 + log_operation( + request=request, + operation_type='LOGIN', + module='User', + action='用户登录', + target_type='User', + target_id=user.id, + target_name=user.username, + remark=f'用户登录:{user.username}(账号:{user.account})' + ) + # 创建会话 session = SessionStore() session.create() @@ -505,9 +576,29 @@ class DeleteDepartment(APIView): return Response({'status': 'error', 'message': '删除失败,该部门还存在人员,请及时转移', 'code': 1}, status=status.HTTP_400_BAD_REQUEST) + # 记录操作前的数据 + old_data = { + 'id': dep.id, + 'username': dep.username + } + # 软删除:更新 is_deleted 字段 dep.is_deleted = True dep.save() + + # 记录操作日志 + log_operation( + request=request, + operation_type='DELETE', + module='User', + action='删除部门', + target_type='Department', + target_id=dep.id, + target_name=dep.username, + old_data=old_data, + remark=f'删除部门:{dep.username}' + ) + return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK) @@ -805,7 +896,121 @@ class DeleteUser(APIView): except User.DoesNotExist: return Response({'status': 'error', 'message': '用户不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND) + # 记录操作前的数据 + old_data = { + 'id': user.id, + 'username': user.username, + 'account': user.account, + 'state': user.state, + 'position': user.position, + 'team': user.team + } + # 软删除:更新 is_deleted 字段 user.is_deleted = True user.save() + + # 记录操作日志 + log_operation( + request=request, + operation_type='DELETE', + module='User', + action='删除用户', + target_type='User', + target_id=user.id, + target_name=user.username, + old_data=old_data, + remark=f'删除用户:{user.username}(账号:{user.account})' + ) + return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK) + + +class OperationLogView(APIView): + """操作日志查询接口""" + + def post(self, request, *args, **kwargs): + """ + 查询操作日志 + 支持按操作人、操作类型、模块、时间范围等条件查询 + """ + page = request.data.get('page', 1) + per_page = request.data.get('per_page', 20) + operator = request.data.get('operator') # 操作人 + operation_type = request.data.get('operation_type') # 操作类型 + module = request.data.get('module') # 模块 + target_type = request.data.get('target_type') # 目标类型 + start_time = request.data.get('start_time') # 开始时间 + end_time = request.data.get('end_time') # 结束时间 + + # 构建查询条件 + query = Q() + + if operator: + query &= Q(operator__icontains=operator) + + if operation_type: + query &= Q(operation_type=operation_type) + + if module: + query &= Q(module=module) + + if target_type: + query &= Q(target_type=target_type) + + if start_time: + try: + start_datetime = datetime.datetime.strptime(start_time, "%Y-%m-%d") + query &= Q(create_time__gte=start_datetime) + except: + pass + + if end_time: + try: + end_datetime = datetime.datetime.strptime(end_time, "%Y-%m-%d") + # 结束时间包含当天,所以加一天 + end_datetime = end_datetime + datetime.timedelta(days=1) + query &= Q(create_time__lt=end_datetime) + except: + pass + + # 查询日志 + logs = OperationLog.objects.filter(query).order_by('-create_time') + total = logs.count() + + # 分页 + paginator = Paginator(logs, per_page) + try: + logs_page = paginator.page(page) + except PageNotAnInteger: + logs_page = paginator.page(1) + except EmptyPage: + logs_page = paginator.page(paginator.num_pages) + + # 格式化数据 + data = [] + for log in logs_page.object_list: + data.append({ + 'id': log.id, + 'operator': log.operator, + 'operator_id': log.operator_id, + 'operation_type': log.operation_type, + 'module': log.module, + 'action': log.action, + 'target_type': log.target_type, + 'target_id': log.target_id, + 'target_name': log.target_name, + 'ip_address': log.ip_address, + 'request_path': log.request_path, + 'remark': log.remark, + 'create_time': log.create_time.strftime('%Y-%m-%d %H:%M:%S'), + 'old_data': json.loads(log.old_data) if log.old_data else None, + 'new_data': json.loads(log.new_data) if log.new_data else None, + }) + + return Response({ + 'message': '查询成功', + 'total': total, + 'data': data, + 'code': 0 + }, status=status.HTTP_200_OK) diff --git a/business/migrations/0026_alter_bid_biddingannouncement_and_more.py b/business/migrations/0026_alter_bid_biddingannouncement_and_more.py new file mode 100644 index 0000000..79997bc --- /dev/null +++ b/business/migrations/0026_alter_bid_biddingannouncement_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.25 on 2025-12-31 04:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('business', '0025_add_is_deleted_fields'), + ] + + operations = [ + migrations.AlterField( + model_name='bid', + name='BiddingAnnouncement', + field=models.TextField(), + ), + migrations.AlterField( + model_name='projectregistration', + name='contract', + field=models.TextField(), + ), + migrations.AlterField( + model_name='role', + name='permissionId', + field=models.CharField(max_length=1000), + ), + ] diff --git a/business/views.py b/business/views.py index 1d43920..99b5bc7 100644 --- a/business/views.py +++ b/business/views.py @@ -4,7 +4,7 @@ from rest_framework import status import json import ast from User.models import User, Approval -from User.utils import format_personincharge +from User.utils import format_personincharge, log_operation from .models import PreFiling, ProjectRegistration, Bid, Case, Invoice, Caselog, SealApplication, Warehousing, \ RegisterPlatform, Announcement, LawyerFlie, Schedule, permission, role from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger @@ -33,7 +33,7 @@ class registration(APIView): if not all([times, description, Undertaker]): return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST) - PreFiling.objects.create( + prefiling = PreFiling.objects.create( times=times, client_username=client_username, party_username=party_username, @@ -41,6 +41,26 @@ class registration(APIView): Undertaker=Undertaker, submit=user ) + + # 记录操作日志 + new_data = { + 'id': prefiling.id, + 'client_username': prefiling.client_username, + 'party_username': prefiling.party_username, + 'undertaker': prefiling.Undertaker + } + log_operation( + request=request, + operation_type='CREATE', + module='Business', + action='新增预立案登记', + target_type='PreFiling', + target_id=prefiling.id, + target_name=f'{prefiling.client_username} vs {prefiling.party_username}', + new_data=new_data, + remark=f'新增预立案登记:委托人 {prefiling.client_username},相对方 {prefiling.party_username}' + ) + return Response({'message': '登记成功', 'code': 0}, status=status.HTTP_200_OK) @@ -214,6 +234,27 @@ class Project(APIView): type="立项登记", user_id=pro.id ) + + # 记录操作日志 + new_data = { + 'id': pro.id, + 'contract_no': pro.ContractNo, + 'type': pro.type, + 'responsiblefor': pro.responsiblefor, + 'times': pro.times + } + log_operation( + request=request, + operation_type='CREATE', + module='Business', + action='新增立项登记', + target_type='ProjectRegistration', + target_id=pro.id, + target_name=pro.ContractNo, + new_data=new_data, + remark=f'新增立项登记:合同编号 {pro.ContractNo},负责人 {pro.responsiblefor}' + ) + return Response({'message': '登记成功', 'code': 0}, status=status.HTTP_200_OK) @@ -387,9 +428,29 @@ class EditProject(APIView): pro.state = "审核中" update_fields_list.append('state') + # 记录操作前的数据 + old_data = { + 'id': pro.id, + 'contract_no': original_ContractNo, + 'type': original_type, + 'responsiblefor': original_responsiblefor, + 'times': original_times, + 'charge': original_charge + } + if update_fields_list: pro.save(update_fields=update_fields_list) + # 记录操作后的数据 + new_data = { + 'id': pro.id, + 'contract_no': pro.ContractNo, + 'type': pro.type, + 'responsiblefor': pro.responsiblefor, + 'times': pro.times, + 'charge': pro.charge + } + today = datetime.datetime.now() formatted_date = today.strftime("%Y-%m-%d") Approval.objects.create( @@ -401,6 +462,20 @@ class EditProject(APIView): type="立项登记", user_id=pro.id ) + + # 记录操作日志 + log_operation( + request=request, + operation_type='UPDATE', + module='Business', + action='编辑立项登记', + target_type='ProjectRegistration', + target_id=pro.id, + target_name=pro.ContractNo, + old_data=old_data, + new_data=new_data, + remark=f'编辑立项登记:合同编号 {pro.ContractNo}' + ) return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK) @@ -429,9 +504,36 @@ class DeleteProject(APIView): if case_exists: return Response({'status': 'error', 'message': '该立项已被案件管理关联,无法删除', 'code': 1}, status=status.HTTP_400_BAD_REQUEST) + # 记录操作前的数据 + try: + prefiling = PreFiling.objects.get(id=pro.user_id, is_deleted=False) + old_data = { + 'id': pro.id, + 'contract_no': pro.ContractNo, + 'type': pro.type, + 'responsiblefor': pro.responsiblefor, + 'client_username': prefiling.client_username if prefiling else None + } + except: + old_data = {'id': pro.id, 'contract_no': pro.ContractNo} + # 软删除:更新 is_deleted 字段 pro.is_deleted = True pro.save() + + # 记录操作日志 + log_operation( + request=request, + operation_type='DELETE', + module='Business', + action='删除立项登记', + target_type='ProjectRegistration', + target_id=pro.id, + target_name=pro.ContractNo, + old_data=old_data, + remark=f'删除立项登记:合同编号 {pro.ContractNo}' + ) + return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK) @@ -608,9 +710,30 @@ class DeleteBid(APIView): except Bid.DoesNotExist: return Response({'status': 'error', 'message': '投标登记不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND) + # 记录操作前的数据 + old_data = { + 'id': bid.id, + 'project_name': bid.ProjectName, + 'times': bid.times + } + # 软删除:更新 is_deleted 字段 bid.is_deleted = True bid.save() + + # 记录操作日志 + log_operation( + request=request, + operation_type='DELETE', + module='Business', + action='删除投标登记', + target_type='Bid', + target_id=bid.id, + target_name=bid.ProjectName, + old_data=old_data, + remark=f'删除投标登记:{bid.ProjectName}' + ) + return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK) @@ -866,9 +989,30 @@ class DeleteCase(APIView): except Case.DoesNotExist: return Response({'status': 'error', 'message': '案件管理不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND) + # 记录操作前的数据 + old_data = { + 'id': case.id, + 'times': case.times, + 'state': case.state + } + # 软删除:更新 is_deleted 字段 case.is_deleted = True case.save() + + # 记录操作日志 + log_operation( + request=request, + operation_type='DELETE', + module='Business', + action='删除案件管理', + target_type='Case', + target_id=case.id, + target_name=f'案件-{case.id}', + old_data=old_data, + remark=f'删除案件管理:ID {case.id}' + ) + return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK) @@ -1888,9 +2032,27 @@ class DeleteRermission(APIView): ID = request.data.get('id') try: perm = permission.objects.get(id=ID, is_deleted=False) + # 记录操作前的数据 + old_data = { + 'id': perm.id, + 'permission_name': perm.permission_name, + 'permission_logo': perm.permission_logo + } # 软删除:更新 is_deleted 字段 perm.is_deleted = True perm.save() + # 记录操作日志 + log_operation( + request=request, + operation_type='DELETE', + module='Business', + action='删除权限', + target_type='Permission', + target_id=perm.id, + target_name=perm.permission_name, + old_data=old_data, + remark=f'删除权限:{perm.permission_name}' + ) return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK) except permission.DoesNotExist: return Response({'status': 'error', 'message': '权限不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND) @@ -1938,9 +2100,27 @@ class DeleteRole(APIView): id = request.data.get('id') try: r = role.objects.get(id=id, is_deleted=False) + # 记录操作前的数据 + old_data = { + 'id': r.id, + 'role_name': r.RoleName, + 'permission_id': r.permissionId + } # 软删除:更新 is_deleted 字段 r.is_deleted = True r.save() + # 记录操作日志 + log_operation( + request=request, + operation_type='DELETE', + module='Business', + action='删除角色', + target_type='Role', + target_id=r.id, + target_name=r.RoleName, + old_data=old_data, + remark=f'删除角色:{r.RoleName}' + ) return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK) except role.DoesNotExist: return Response({'status': 'error', 'message': '角色不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND) diff --git a/finance/views.py b/finance/views.py index c5d281a..2c1edc7 100644 --- a/finance/views.py +++ b/finance/views.py @@ -4,7 +4,7 @@ from rest_framework import status import json import ast from User.models import User,Approval,Department -from User.utils import format_personincharge +from User.utils import format_personincharge, log_operation import datetime from .models import Invoice,Income,Accounts,Payment,Reimbursement,BonusChange from utility.utility import flies @@ -68,6 +68,28 @@ class UserRegister(APIView): ) user.save(update_fields=['card', 'salary', 'username', 'Dateofjoining', 'position']) + + # 记录操作日志 + new_data = { + 'user_id': user.id, + 'username': user.username, + 'card': user.card, + 'position': user.position, + 'salary': user.salary, + 'state': user.state + } + log_operation( + request=request, + operation_type='CREATE', + module='Finance', + action='新增财务登记', + target_type='User', + target_id=user.id, + target_name=user.username, + new_data=new_data, + remark=f'新增财务登记:{user.username},岗位 {user.position},薪资 {user.salary}' + ) + return Response({'message': '登记成功', 'code': 0}, status=status.HTTP_200_OK) @@ -117,6 +139,27 @@ class issueAnInvoice(APIView): type="开票", user_id = invoice.id ) + + # 记录操作日志 + new_data = { + 'id': invoice.id, + 'contract_no': invoice.ContractNo, + 'amount': invoice.amount, + 'type': invoice.type, + 'unit': invoice.unit + } + log_operation( + request=request, + operation_type='CREATE', + module='Finance', + action='新增开票申请', + target_type='Invoice', + target_id=invoice.id, + target_name=invoice.ContractNo, + new_data=new_data, + remark=f'新增开票申请:合同号 {invoice.ContractNo},金额 {invoice.amount}' + ) + return Response({'message': '提交成功', 'code': 0}, status=status.HTTP_200_OK) @@ -223,9 +266,42 @@ class EditInvoice(APIView): invoice.bank = bank update_fields_list.append('bank') + # 记录操作前的数据 + old_data = { + 'id': invoice.id, + 'contract_no': invoice.ContractNo, + 'amount': invoice.amount, + 'type': invoice.type, + 'unit': invoice.unit + } + if update_fields_list: invoice.save(update_fields=update_fields_list) + # 记录操作后的数据 + invoice.refresh_from_db() + new_data = { + 'id': invoice.id, + 'contract_no': invoice.ContractNo, + 'amount': invoice.amount, + 'type': invoice.type, + 'unit': invoice.unit + } + + # 记录操作日志 + log_operation( + request=request, + operation_type='UPDATE', + module='Finance', + action='编辑开票申请', + target_type='Invoice', + target_id=invoice.id, + target_name=invoice.ContractNo, + old_data=old_data, + new_data=new_data, + remark=f'编辑开票申请:合同号 {invoice.ContractNo}' + ) + return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK) @@ -247,9 +323,31 @@ class DeleteInvoice(APIView): except Invoice.DoesNotExist: return Response({'status': 'error', 'message': '开票申请不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND) + # 记录操作前的数据 + old_data = { + 'id': invoice.id, + 'contract_no': invoice.ContractNo, + 'amount': invoice.amount, + 'unit': invoice.unit + } + # 软删除:更新 is_deleted 字段 invoice.is_deleted = True invoice.save() + + # 记录操作日志 + log_operation( + request=request, + operation_type='DELETE', + module='Finance', + action='删除开票申请', + target_type='Invoice', + target_id=invoice.id, + target_name=invoice.ContractNo, + old_data=old_data, + remark=f'删除开票申请:合同号 {invoice.ContractNo},金额 {invoice.amount}' + ) + return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK) @@ -1329,6 +1427,15 @@ class DeleteUserDeparture(APIView): except (User.DoesNotExist, ValueError): return Response({'status': 'error', 'message': '用户不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND) + # 记录操作前的数据 + old_data = { + 'user_id': user.id, + 'username': user.username, + 'state': user.state, + 'dateofdeparture': str(user.Dateofdeparture) if user.Dateofdeparture else None, + 'approval_id': approval.id + } + # 恢复用户状态 user.state = "在职" user.Dateofdeparture = None @@ -1338,5 +1445,19 @@ class DeleteUserDeparture(APIView): approval.is_deleted = True approval.save() + # 记录操作日志 + log_operation( + request=request, + operation_type='DELETE', + module='Finance', + action='删除离职登记', + target_type='Approval', + target_id=approval.id, + target_name=f'离职登记-{user.username}', + old_data=old_data, + new_data={'user_id': user.id, 'username': user.username, 'state': '在职'}, + remark=f'删除离职登记,恢复用户 {user.username} 状态为"在职"' + ) + return Response({'message': '删除成功,用户状态已恢复为"在职"', 'code': 0}, status=status.HTTP_200_OK)