Files
jyls_django/User/views.py
2026-02-05 16:44:17 +08:00

3136 lines
158 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from django.contrib.auth.models import Permission
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
import json
import ast
from .models import User, Approval, Department, OperationLog, Team
from business.models import permission
from finance.models import Income, Accounts, Payment, Reimbursement, BonusChange
from finance.models import Invoice
from business.models import ProjectRegistration, Case, SealApplication, PreFiling, Bid, CaseChangeRequest, Propaganda, Schedule
import datetime
from utility.utility import flies
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,
log_operation,
get_approvers_from_record,
is_finance_personincharge,
get_finance_personincharge_candidates,
)
class CreateUserView(APIView):
def post(self, request, *args, **kwargs):
"""人事管理-人员添加"""
role = request.data.get('role')
username = request.data.get('username') # 姓名
account = request.data.get('account') # 账号
password = request.data.get('password') # 密码
nation = request.data.get('nation') # 民族
IdCard = request.data.get('IdCard') # 身份证
department = request.data.get('department') # 归属部门
mobilePhone = request.data.get('mobilePhone') # 手机号
position = request.data.get('position') # 岗位
team = request.data.get('team') # 所属团队
Dateofjoining = request.data.get('Dateofjoining') # 入职时间
Confirmationtime = request.data.get('Confirmationtime') # 转正时间
Practicingcertificatetime = request.data.get('Practicingcertificatetime') # 执业证时间
AcademicResume = request.FILES.getlist('AcademicResume') # 学业简历
academic = request.data.get('academic') # 学历
contract = request.FILES.getlist('contract') # 合同
ApplicationForm = request.FILES.getlist('ApplicationForm') # 入职申请表
salary = request.data.get('salary') # 工资标准
# 详细的参数验证,提供更明确的错误信息
missing_params = []
if not username:
missing_params.append('username(姓名)')
if not account:
missing_params.append('account(账号)')
if not password:
missing_params.append('password(密码)')
if not IdCard:
missing_params.append('IdCard(身份证)')
if not department:
missing_params.append('department(归属部门)')
if not position:
missing_params.append('position(岗位)')
if not nation:
missing_params.append('nation(民族)')
if not mobilePhone:
missing_params.append('mobilePhone(手机号)')
if not team:
missing_params.append('team(所属团队)')
if not Dateofjoining:
missing_params.append('Dateofjoining(入职时间)')
if not academic:
missing_params.append('academic(学历)')
if not contract or (isinstance(contract, list) and len(contract) == 0):
missing_params.append('contract(合同文件)')
if missing_params:
return Response({
'status': 'error',
'message': f'缺少参数: {", ".join(missing_params)}',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 检查用户名是否已被未软删除的用户使用(允许软删除后重新注册同名用户)
if User.objects.filter(username=username, is_deleted=False).exists():
return Response({'status': 'error', 'message': '用户名已存在,不能重复', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
AcademicResume_url = flies(AcademicResume)
ApplicationForm_url = flies(ApplicationForm)
contract_url = flies(contract)
# 日期格式验证和解析
try:
Dateofjoining = datetime.datetime.strptime(Dateofjoining, "%Y-%m-%d")
except ValueError:
return Response({'status': 'error', 'message': '入职时间格式错误应为YYYY-MM-DD格式', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
# 处理转正时间,支持 'null' 字符串
if Confirmationtime and Confirmationtime not in ['null', 'None', 'NULL', 'NONE', '']:
try:
Confirmationtime = datetime.datetime.strptime(Confirmationtime, "%Y-%m-%d")
except ValueError:
return Response({'status': 'error', 'message': '转正时间格式错误应为YYYY-MM-DD格式', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
else:
Confirmationtime = None
# 处理执业证时间,支持 'null' 字符串
if Practicingcertificatetime and Practicingcertificatetime not in ['null', 'None', 'NULL', 'NONE', '']:
try:
# 支持两种格式YYYY-MM精确到月和 YYYY-MM-DD精确到日
if len(Practicingcertificatetime) == 7 and Practicingcertificatetime.count('-') == 1:
# YYYY-MM 格式,转换为该月的第一天
Practicingcertificatetime = datetime.datetime.strptime(Practicingcertificatetime + "-01", "%Y-%m-%d")
else:
# YYYY-MM-DD 格式
Practicingcertificatetime = datetime.datetime.strptime(Practicingcertificatetime, "%Y-%m-%d")
except ValueError:
return Response({
'status': 'error',
'message': '执业证时间格式错误应为YYYY-MM精确到月或YYYY-MM-DD精确到日格式',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
else:
Practicingcertificatetime = None
# 解析角色和部门ID列表
try:
if role:
role_list = ast.literal_eval(role) if isinstance(role, str) else role
if not isinstance(role_list, list):
role_list = [role_list] if role_list else []
else:
role_list = []
except (ValueError, SyntaxError) as e:
return Response({'status': 'error', 'message': f'角色参数格式错误: {str(e)}', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
try:
if department:
department_id = ast.literal_eval(department) if isinstance(department, str) else department
if not isinstance(department_id, list):
department_id = [department_id] if department_id else []
else:
department_id = []
except (ValueError, SyntaxError) as e:
return Response({'status': 'error', 'message': f'部门参数格式错误: {str(e)}', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
# 创建用户,添加异常处理
try:
user = User.objects.create(
username=username,
account=account,
password=password,
ethnicity=nation,
card=IdCard,
mobilePhone=mobilePhone,
position=position,
team=team,
Dateofjoining=Dateofjoining,
Confirmationtime=Confirmationtime,
Practicingcertificatetime=Practicingcertificatetime,
AcademicResume=json.dumps(AcademicResume_url),
academic=academic,
contract=json.dumps(contract_url),
ApplicationForm=json.dumps(ApplicationForm_url),
state="待登记",
salary=salary
)
# 添加角色和部门关联
if role_list:
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:
# 捕获数据库操作异常
error_msg = str(e)
if 'Duplicate entry' in error_msg or 'UNIQUE constraint' in error_msg:
if 'username' in error_msg:
return Response({'status': 'error', 'message': '用户名已存在,不能重复', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
else:
return Response({'status': 'error', 'message': '数据已存在,请检查唯一性约束', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
else:
return Response({'status': 'error', 'message': f'创建用户失败: {error_msg}', 'code': 1},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class EditorialStaffView(APIView):
def post(self, request, *args, **kwargs):
"""修改人员信息"""
id = request.data.get('id')
username = request.data.get('username') # 姓名
account = request.data.get('account') # 账号
password = request.data.get('password') # 密码
nation = request.data.get('nation') # 民族
IdCard = request.data.get('IdCard') # 身份证
department = request.data.get('department') # 归属部门
mobilePhone = request.data.get('mobilePhone') # 手机号
position = request.data.get('position') # 岗位
team = request.data.get('team') # 所属团队
Dateofjoining = request.data.get('Dateofjoining') # 入职时间
Confirmationtime = request.data.get('Confirmationtime') # 转正时间
Practicingcertificatetime = request.data.get('Practicingcertificatetime') # 执业证时间
AcademicResume = request.FILES.getlist('AcademicResume') # 学业简历
academic = request.data.get('academic') # 学历
contract = request.FILES.getlist('contract') # 合同
ApplicationForm = request.FILES.getlist('ApplicationForm') # 入职申请表
salary = request.data.get('salary') # 工资标准
# 密码改为可选参数,其他参数仍为必填
if not all([username, account, IdCard, department, position, nation, mobilePhone, team, Dateofjoining,
academic]):
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
user = User.objects.get(id=id, is_deleted=False)
# 检查用户名是否已被其他用户使用(排除当前用户)
if username and User.objects.filter(username=username, is_deleted=False).exclude(id=id).exists():
return Response({'status': 'error', 'message': '用户名已存在,不能重复', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
if AcademicResume:
AcademicResume_url = flies(AcademicResume)
user.AcademicResume = json.dumps(AcademicResume_url)
if ApplicationForm:
ApplicationForm_url = flies(ApplicationForm)
user.ApplicationForm = json.dumps(ApplicationForm_url)
if contract:
contract_url = flies(contract)
user.contract = json.dumps(contract_url)
def parse_date_str(date_str, allow_none=False):
"""解析日期字符串,处理各种空值情况
Args:
date_str: 日期字符串
allow_none: 是否允许返回 None用于可选字段
Returns:
日期字符串YYYY-MM-DD格式或 None如果 allow_none=True 且输入为空)
Raises:
ValueError: 如果 allow_none=False 且输入为空或无效
"""
# 处理 None、空字符串、'null'、'None' 等情况
if not date_str or date_str in ['null', 'None', 'NULL', 'NONE', '']:
if allow_none:
return None # 可选字段返回 None
raise ValueError('日期不能为空')
# 处理无效日期格式
if date_str == "0000-00-00":
if allow_none:
return None
raise ValueError('日期格式无效')
try:
return datetime.datetime.strptime(date_str, "%Y-%m-%d").strftime('%Y-%m-%d')
except ValueError:
# 如果解析失败,根据 allow_none 决定返回值
if allow_none:
return None
raise ValueError('日期格式错误应为YYYY-MM-DD格式')
def parse_practicing_certificate_time(date_str):
"""解析执业证时间支持YYYY-MM精确到月和YYYY-MM-DD精确到日格式"""
# 处理 None、空字符串、'null'、'None' 等情况
if not date_str or date_str in ['null', 'None', 'NULL', 'NONE', '']:
return None # 可选字段返回 None
# 处理无效日期格式
if date_str == "0000-00-00":
return None
try:
# 支持两种格式YYYY-MM精确到月和 YYYY-MM-DD精确到日
if len(date_str) == 7 and date_str.count('-') == 1:
# YYYY-MM 格式,转换为该月的第一天
return datetime.datetime.strptime(date_str + "-01", "%Y-%m-%d").strftime('%Y-%m-%d')
else:
# YYYY-MM-DD 格式
return datetime.datetime.strptime(date_str, "%Y-%m-%d").strftime('%Y-%m-%d')
except ValueError:
raise ValueError('执业证时间格式错误应为YYYY-MM精确到月或YYYY-MM-DD精确到日格式')
# 入职时间是必填字段,不允许为空
try:
Dateofjoining = parse_date_str(Dateofjoining, allow_none=False)
except ValueError as e:
return Response({'status': 'error', 'message': str(e), 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
# 转正时间是可选字段,允许为 None
Confirmationtime = parse_date_str(Confirmationtime, allow_none=True)
try:
# 执业证时间是可选字段,允许为 None
Practicingcertificatetime = parse_practicing_certificate_time(Practicingcertificatetime)
except ValueError as e:
return Response({'status': 'error', 'message': str(e), 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
user.Confirmationtime = Confirmationtime
user.Practicingcertificatetime = Practicingcertificatetime
user.Dateofjoining = Dateofjoining
user.username = username
user.account = account
# 密码修改权限控制:只有超级管理员才能通过此接口修改密码
# 普通用户修改密码请使用专门的 /user/change-password 接口
if password and password.strip():
# 获取当前登录用户
token = request.META.get('token')
try:
current_user = User.objects.get(token=token, is_deleted=False)
is_admin = (current_user.username == 'admin' or current_user.account == 'admin')
if is_admin:
# 超级管理员可以修改任何用户的密码
user.password = password
else:
# 非超级管理员不能通过此接口修改密码
return Response({
'status': 'error',
'message': '非超级管理员不能通过此接口修改密码,请使用密码修改接口',
'code': 1
}, status=status.HTTP_403_FORBIDDEN)
except User.DoesNotExist:
return Response({
'status': 'error',
'message': '用户不存在或已被删除',
'code': 1
}, status=status.HTTP_401_UNAUTHORIZED)
user.ethnicity = nation
user.card = IdCard
user.mobilePhone = mobilePhone
user.position = position
user.team = team
user.academic = academic
user.salary = salary
# 处理 ManyToMany 字段 department
if department:
try:
# 尝试解析 JSON 格式的字符串,如 "[1,2]" 或 "1,2"
if isinstance(department, str):
# 如果是 "1,2" 格式,先转换为列表格式
if ',' in department and not department.startswith('['):
department_id = [int(x.strip()) for x in department.split(',')]
else:
department_id = ast.literal_eval(department)
else:
department_id = department
# 确保 department_id 是列表格式ManyToMany 字段的 set 方法需要可迭代对象)
if not isinstance(department_id, (list, tuple)):
department_id = [department_id]
user.department.set(department_id)
except (ValueError, SyntaxError):
# 如果解析失败尝试作为单个ID处理
try:
user.department.set([int(department)])
except (ValueError, TypeError):
pass
# 处理 ManyToMany 字段 role如果有传入
role = request.data.get('role')
if role:
role_id = ast.literal_eval(role) if isinstance(role, str) else role
# 确保 role_id 是列表格式ManyToMany 字段的 set 方法需要可迭代对象)
if not isinstance(role_id, (list, tuple)):
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)
class LoginView(APIView):
"""
登录页面
"""
def post(self, request):
token = request.META.get('token')
username = request.data.get('username')
password = request.data.get('password')
if not all([username, password]):
return Response({'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
try:
user = User.objects.get(account=username, is_deleted=False)
# 只有"在职"状态的用户才能登录,新创建的用户需要完成财务登记审批流程
if user.state != '在职' and user.state !="已通过":
if user.state == '待登记':
return Response(
{'message': '您的账号尚未完成财务登记,请联系财务部门完成入职财务登记审批。', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
else:
return Response({'message': '你的账号已经封存。', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
# 比较密码
if user.password == password or user.token == token:
# if '123456' == password or user.token == token:
# 更新 encryption 字段到数据库
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()
session['user_id'] = user.id
session.save()
# 置 sessionid 到响应的 cookie 中
response = Response({
'id': user.id,
'message': '登录成功',
'code': 0
}, status=status.HTTP_200_OK)
response.set_cookie(key='sessionid', value=session.session_key, httponly=True)
return response
else:
return Response({'message': '密码错误', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
except User.DoesNotExist:
return Response({'status': 'error', 'message': '用户不存在', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
class PersonnelDetailsView(APIView):
"""展示人员信息"""
def post(self, request, *args, **kwargs):
token = request.META.get('token')
user = User.objects.prefetch_related('role', 'department').get(token=token, is_deleted=False)
# 如果是admin用户直接返回最高权限标识
if user.username == 'admin' or user.account == 'admin':
permission_data = ['*:*:*']
else:
# 非admin用户从数据库查询权限
permissionId = list(user.role.values("permissionId"))
permission_ids_raw = [item['permissionId'] for item in permissionId if item['permissionId']]
permission_ids = []
# 解析权限ID支持多种格式
for perm_str in permission_ids_raw:
if not perm_str:
continue
try:
# 处理逗号分隔的字符串格式(如 "1,2,3"
if ',' in perm_str and not perm_str.strip().startswith('['):
perm_list = [int(x.strip()) for x in perm_str.split(',') if x.strip()]
permission_ids.extend(perm_list)
else:
# 处理 JSON 列表格式(如 "[1,2,3]")或单个数字字符串(如 "1"
parsed = ast.literal_eval(perm_str)
if isinstance(parsed, list):
permission_ids.extend(parsed)
elif isinstance(parsed, int):
# 单个数字的情况
permission_ids.append(parsed)
except (ValueError, SyntaxError):
# 如果解析失败跳过这个权限ID
continue
permissions = permission.objects.filter(id__in=permission_ids)
permission_data = []
for per in permissions:
permission_data.append(per.permission_logo)
data = {
'id': user.id,
'username': user.username,
'account': user.account,
"ethnicity": user.ethnicity,
"card": user.card,
"mobilePhone": user.mobilePhone,
"position": user.position,
"team": user.team,
"Dateofjoining": user.Dateofjoining,
"Confirmationtime": user.Confirmationtime,
'Practicingcertificatetime': user.Practicingcertificatetime,
"Dateofdeparture": user.Dateofdeparture,
"AcademicResume": user.AcademicResume,
"academic": user.academic,
"contract": user.contract,
"ApplicationForm": user.ApplicationForm,
"state": user.state,
# 角色数据
"role": list(user.role.values('id', 'RoleName', "permissionId")), # 假设Role模型有name字段
# 如果需要部门数据
"department": list(user.department.values('id', 'username')),
"permission_data": permission_data,
"content": Propaganda.objects.all().first().content
}
return Response({'message': '详细人员信息展示成功', "data": data, 'code': 0}, status=status.HTTP_200_OK)
class DepartmentView(APIView):
def post(self, request, *args, **kwargs):
"""部门列表"""
name = request.data.get('name')
Q_obj = Q()
if name:
Q_obj &= Q(username__icontains=name)
deps = Department.objects.filter(Q_obj, is_deleted=False)
data = []
for dep in deps:
data.append({
'id': dep.id,
'name': dep.username,
})
return Response({'message': '展示成功', "data": data, 'code': 0}, status=status.HTTP_200_OK)
class PersonnelListView(APIView):
def post(self, request, *args, **kwargs):
"""
人员列表
:param request:
:param args:
:param kwargs:
:return:
"""
page = request.data.get('page')
per_page = request.data.get('per_page')
username = request.data.get('username')
department = request.data.get('department')
position = request.data.get('position')
team = request.data.get('team')
academic = request.data.get('academic')
endtime = request.data.get('endtime')
if not all([page, per_page]):
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
Q_obj = Q(is_deleted=False) # 只查询未软删除的用户
if username:
Q_obj &= Q(username__icontains=username)
if department:
Q_obj &= Q(department__username__icontains=department)
if position:
Q_obj &= Q(position=position)
if team:
Q_obj &= Q(team=team)
if academic:
Q_obj &= Q(academic__icontains=academic)
if endtime:
Q_obj &= Q(Practicingcertificatetime__lte=endtime)
users = User.objects.prefetch_related('department', 'role').filter(Q_obj).order_by('-id')
total = len(users)
paginator = Paginator(users, per_page)
try:
user_agents_page = paginator.page(page)
except PageNotAnInteger:
user_agents_page = paginator.page(1)
except EmptyPage:
user_agents_page = paginator.page(paginator.num_pages)
data = []
for info in user_agents_page.object_list:
data.append({
'id': info.id,
"username": info.username, # 姓名
"account": info.account, # 账号
"nation": info.ethnicity, # 名族
"IdCard": info.card, # 身份证
"mobilePhone": info.mobilePhone, # 手机号
"department": list(info.department.values('id', 'username')),
"role": list(info.role.values('id', 'RoleName', 'permissionId')), # 角色信息
"position": info.position, # 岗位
"team": info.team, # 所属团队
"Dateofjoining": info.Dateofjoining, # 入职时间
"Confirmationtime": info.Confirmationtime, # # 转正时间
'Practicingcertificatetime': info.Practicingcertificatetime, # 执业证时间
"Dateofdeparture": info.Dateofdeparture, # 离职时间
"AcademicResume": info.AcademicResume, # 学业简历
"academic": info.academic, # 学历信息
"contract": info.contract, # 合同
"ApplicationForm": info.ApplicationForm, # 入职申请表
"salary": info.salary, # 工资
"state": info.state, # 状态
})
return Response({'message': '查看成功', "total": total, 'data': data, 'code': 0}, status=status.HTTP_200_OK)
class AddDepartment(APIView):
def post(self, request, *args, **kwargs):
name = request.data.get('name')
if not all([name]):
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
dep = Department.objects.filter(username=name, is_deleted=False).first()
if dep:
return Response({'message': '添加失败,部门存在', 'code': 0}, status=status.HTTP_400_BAD_REQUEST)
Department.objects.create(username=name)
return Response({'message': '添加部门成功', 'code': 0}, status=status.HTTP_200_OK)
class DeleteDepartment(APIView):
def post(self, request, *args, **kwargs):
id = request.data.get('id')
if not all([id]):
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
try:
dep = Department.objects.get(id=id, is_deleted=False)
except Department.DoesNotExist:
return Response({'status': 'error', 'message': '部门不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
department = User.objects.filter(department=dep, is_deleted=False)
if department.exists():
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)
class Personlist(APIView):
"""
部门列表展示
"""
def post(self, request, *args, **kwargs):
users = Department.objects.filter(is_deleted=False).order_by('id')
page = request.data.get('page')
per_page = request.data.get('per_page')
total = len(users)
paginator = Paginator(users, per_page)
try:
user_agents_page = paginator.page(page)
except PageNotAnInteger:
user_agents_page = paginator.page(1)
except EmptyPage:
user_agents_page = paginator.page(paginator.num_pages)
department_dict = []
for user in user_agents_page.object_list:
department_dict.append({
'id': user.id,
"name": user.username, # 修改为 name与前端期望的字段名一致
"department": user.username, # 同时保留 username 字段以兼容
})
return Response({'message': '展示成功', "total": total, 'data': department_dict, 'code': 0},
status=status.HTTP_200_OK)
class roxyExhibition(APIView):
def post(self, request, *args, **kwargs):
"""
代办展示
:param request:
:param args:
:param kwargs:
:return:
"""
page = request.data.get('page')
per_page = request.data.get('per_page')
token = request.META.get('token')
# 筛选条件参数
filter_type = request.data.get('type') # 审批类型筛选(如"案件管理"、"立项登记"等,支持逗号分割多个值)
filter_state = request.data.get('state') # 审批状态筛选(如"审核中"、"已通过"、"已抄送财务"等,支持逗号分割多个值)
filter_status = request.data.get('status') # 优化后的状态筛选("审批中"、"已完成",支持逗号分割多个值)
filter_times_start = request.data.get('times_start') # 开始时间筛选
filter_times_end = request.data.get('times_end') # 结束时间筛选
if not all([page, per_page]):
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
user = User.objects.prefetch_related('department').get(token=token)
# 获取用户所属的所有部门ID转为字符串因为personincharge存储的是字符串
user_department_ids = list(user.department.values_list('id', flat=True))
user_department_ids_str = [str(did) for did in user_department_ids]
# 获取用户的部门名称列表
user_departments = user.department.values_list('username', flat=True)
user_department_names = list(user_departments)
# 判断用户是否是财务部人员:通过部门名称判断(包含"财务"关键词)
# 这与 get_finance_personincharge_candidates() 中的查询条件保持一致
is_finance_user = any('财务' in name for name in user_department_names)
finance_personincharge_candidates = get_finance_personincharge_candidates()
# 构建查询条件:
# personincharge字段统一规则
# - 纯数字字符串 = 部门ID该部门下所有人员都能看到
# - 非纯数字字符串 = 审批员用户名(只有该审批员能看到)
# - "财务" = 财务部门(只有财务部人员能看到)
# 状态:审核中 或 已抄送财务(财务能看到)
# 重要多人团队审核时只有当前审核人personincharge等于当前用户名才能看到审核记录
# 如果用户传入了 filter_state则使用用户指定的状态筛选否则使用默认的状态筛选
if filter_state:
# 按审批状态筛选(覆盖默认的状态筛选),支持逗号分割的多个值
if isinstance(filter_state, str) and ',' in filter_state:
# 如果包含逗号,分割成列表
state_list = [s.strip() for s in filter_state.split(',') if s.strip()]
query = Q(state__in=state_list)
elif isinstance(filter_state, list):
# 如果已经是列表,直接使用
query = Q(state__in=filter_state)
else:
# 单个值
query = Q(state=filter_state)
else:
# 默认状态筛选:审核中、已抄送财务、待查看(投标/立项最后一步给申请人)、已通过
query = Q(state__in=["审核中", "已抄送财务", "待查看", "已通过"])
# 添加筛选条件
if filter_type:
# 按审批类型筛选,支持逗号分割的多个值
if isinstance(filter_type, str) and ',' in filter_type:
# 如果包含逗号,分割成列表
type_list = [t.strip() for t in filter_type.split(',') if t.strip()]
query &= Q(type__in=type_list)
elif isinstance(filter_type, list):
# 如果已经是列表,直接使用
query &= Q(type__in=filter_type)
else:
# 单个值
query &= Q(type=filter_type)
if filter_times_start:
# 按开始时间筛选
query &= Q(times__gte=filter_times_start)
if filter_times_end:
# 按结束时间筛选
query &= Q(times__lte=filter_times_end)
# 部门匹配personincharge字段是纯数字字符串且匹配用户所属部门
department_query = Q()
if user_department_ids_str:
# 匹配personincharge字段等于用户所属的任一部门ID纯数字字符串
department_query = Q(personincharge__in=user_department_ids_str)
# 审批员匹配personincharge字段是用户名非纯数字字符串
# 重要只匹配当前审核人确保第一个审核人审核完后personincharge被更新为第二个审核人时第一个审核人不再看到
person_query = Q(personincharge=user.username)
# 财务匹配personincharge字段是"财务",且状态为"已抄送财务"或"已通过"
# 重要:只有财务部人员才能看到财务审核的审批记录
# 财务部可以看到:已抄送财务(待查看)和已通过(已完成)的待办
finance_query = Q()
if is_finance_user:
finance_query = Q(personincharge__in=finance_personincharge_candidates, state__in=["已抄送财务", "已通过"])
# 已通过的待办查询:
# 1. 财务部人员:可以看到所有状态为"已通过"的待办不管personincharge是谁
# 2. 其他人员:可以看到自己提交的、状态为"已通过"的待办
completed_todo_query = Q()
if is_finance_user:
# 财务部:可以看到所有状态为"已通过"的待办
completed_todo_query = Q(type="待办", state="已通过")
else:
# 其他人员:只能看到自己提交的、状态为"已通过"的待办
# 通过user_id关联到Schedule判断submit字段
from business.models import Schedule
user_submitted_schedule_ids = Schedule.objects.filter(
submit=user.username,
is_deleted=False
).values_list('id', flat=True)
user_submitted_schedule_ids_str = [str(sid) for sid in user_submitted_schedule_ids]
if user_submitted_schedule_ids_str:
completed_todo_query = Q(type="待办", state="已通过", user_id__in=user_submitted_schedule_ids_str)
# 投标登记审批完成后,申请人与审批人均可见
completed_bid_query = Q()
completed_bid_applicant = Q(type="投标登记", state="已通过", applicant=user.username)
try:
approved_bid_ids = list(Approval.objects.filter(type="投标登记", state="已通过").values_list('user_id', flat=True))
bid_ids_where_user_is_approver = []
if approved_bid_ids:
bid_ids_int = [int(x) for x in approved_bid_ids if str(x).isdigit()]
from business.models import Bid
for bid in Bid.objects.filter(id__in=bid_ids_int, is_deleted=False):
try:
order = json.loads(bid.approvers_order or '[]')
if isinstance(order, list) and user.username in order:
bid_ids_where_user_is_approver.append(bid.id)
except (json.JSONDecodeError, TypeError):
pass
if bid_ids_where_user_is_approver:
completed_bid_query = completed_bid_applicant | Q(type="投标登记", state="已通过", user_id__in=[str(x) for x in bid_ids_where_user_is_approver])
else:
completed_bid_query = completed_bid_applicant
except Exception:
completed_bid_query = completed_bid_applicant
# 立项登记审批完成后,申请人与审批人均可见
completed_project_query = Q()
completed_project_applicant = Q(type="立项登记", state="已通过", applicant=user.username)
try:
approved_project_ids = list(Approval.objects.filter(type="立项登记", state="已通过").values_list('user_id', flat=True))
project_ids_where_user_is_approver = []
if approved_project_ids:
project_ids_int = [int(x) for x in approved_project_ids if str(x).isdigit()]
from business.models import ProjectRegistration
for proj in ProjectRegistration.objects.filter(id__in=project_ids_int, is_deleted=False):
try:
order = json.loads(proj.approvers_order or '[]')
if isinstance(order, list) and user.username in order:
project_ids_where_user_is_approver.append(proj.id)
except (json.JSONDecodeError, TypeError):
pass
if project_ids_where_user_is_approver:
completed_project_query = completed_project_applicant | Q(type="立项登记", state="已通过", user_id__in=[str(x) for x in project_ids_where_user_is_approver])
else:
completed_project_query = completed_project_applicant
except Exception:
completed_project_query = completed_project_applicant
# 组合查询:部门匹配 OR 审批员匹配 OR 财务匹配 OR 已通过的待办查询 OR 已通过的投标/立项登记(申请人/审批人可见)
# 如果用户有部门,使用部门匹配;否则只使用审批员匹配
# 注意:审批员匹配必须精确匹配当前用户名,确保多人团队审核时,只有当前审核人能看到
if user_department_ids_str:
query &= (department_query | person_query | finance_query | completed_todo_query | completed_bid_query | completed_project_query)
else:
# 如果用户没有部门,匹配审批员或财务(仅财务部人员)或已通过的待办或已通过的投标/立项登记
query &= (person_query | finance_query | completed_todo_query | completed_bid_query | completed_project_query)
# 添加调试日志
import logging
logger = logging.getLogger(__name__)
logger.info(f"roxyExhibition: 用户={user.username}, 部门IDs={user_department_ids_str}, 部门名称={user_department_names}")
logger.info(f"roxyExhibition: 是否是财务部人员={is_finance_user} (仅通过部门名称判断)")
logger.info(
f"roxyExhibition: 查询条件 - personincharge={user.username} "
f"OR personincharge in 财务标识{finance_personincharge_candidates}(已抄送财务,仅财务部) "
f"OR personincharge IN {user_department_ids_str}"
)
approvals = Approval.objects.filter(query, is_deleted=False).order_by('-id')
# 记录查询结果
logger.info(f"roxyExhibition: 查询到 {approvals.count()} 条审批记录")
for approval in approvals[:5]: # 只记录前5条
logger.info(f"roxyExhibition: 审批记录 ID={approval.id}, type={approval.type}, personincharge={approval.personincharge}, state={approval.state}, content={approval.content[:50]}")
# 过滤掉关联的业务记录已被删除的审批记录
# 获取所有已删除的案件ID转为字符串因为Approval.user_id是CharField
deleted_case_ids = Case.objects.filter(is_deleted=True).values_list('id', flat=True)
deleted_case_ids_str = [str(cid) for cid in deleted_case_ids]
# 获取所有已删除的变更申请ID
deleted_change_request_ids = CaseChangeRequest.objects.filter(is_deleted=True).values_list('id', flat=True)
deleted_change_request_ids_str = [str(crid) for crid in deleted_change_request_ids]
# 获取所有已删除的投标登记ID
deleted_bid_ids = Bid.objects.filter(is_deleted=True).values_list('id', flat=True)
deleted_bid_ids_str = [str(bid) for bid in deleted_bid_ids]
# 获取所有已删除的立项登记ID
deleted_project_ids = ProjectRegistration.objects.filter(is_deleted=True).values_list('id', flat=True)
deleted_project_ids_str = [str(pid) for pid in deleted_project_ids]
# 获取所有已删除的收入确认ID
deleted_income_ids = Income.objects.filter(is_deleted=True).values_list('id', flat=True)
deleted_income_ids_str = [str(iid) for iid in deleted_income_ids]
# 过滤已删除的开票记录
from finance.models import Invoice
deleted_invoice_ids = Invoice.objects.filter(is_deleted=True).values_list('id', flat=True)
deleted_invoice_ids_str = [str(iid) for iid in deleted_invoice_ids]
# 获取所有已删除的调账申请ID
deleted_accounts_ids = Accounts.objects.filter(is_deleted=True).values_list('id', flat=True)
deleted_accounts_ids_str = [str(aid) for aid in deleted_accounts_ids]
# 获取所有已删除的付款申请ID
deleted_payment_ids = Payment.objects.filter(is_deleted=True).values_list('id', flat=True)
deleted_payment_ids_str = [str(pid) for pid in deleted_payment_ids]
# 获取所有已删除的报销申请ID
deleted_reimbursement_ids = Reimbursement.objects.filter(is_deleted=True).values_list('id', flat=True)
deleted_reimbursement_ids_str = [str(rid) for rid in deleted_reimbursement_ids]
# 获取所有已删除的工资/奖金变更ID
deleted_bonus_change_ids = BonusChange.objects.filter(is_deleted=True).values_list('id', flat=True)
deleted_bonus_change_ids_str = [str(bid) for bid in deleted_bonus_change_ids]
# 获取所有已删除的申请用印ID
deleted_seal_application_ids = SealApplication.objects.filter(is_deleted=True).values_list('id', flat=True)
deleted_seal_application_ids_str = [str(sid) for sid in deleted_seal_application_ids]
# 获取所有已删除的用户ID用于入职财务登记和离职财务登记
deleted_user_ids = User.objects.filter(is_deleted=True).values_list('id', flat=True)
deleted_user_ids_str = [str(uid) for uid in deleted_user_ids]
# 获取所有已删除的待办ID
from business.models import Schedule
deleted_schedule_ids = Schedule.objects.filter(is_deleted=True).values_list('id', flat=True)
deleted_schedule_ids_str = [str(sid) for sid in deleted_schedule_ids]
# 排除关联的业务记录已被删除的审批记录
exclude_conditions = Q()
# 排除 type="案件管理" 且关联的案件已被删除的审批记录
if deleted_case_ids_str:
exclude_conditions |= Q(type="案件管理") & Q(user_id__in=deleted_case_ids_str)
# 排除 type="案件变更" 且关联的变更申请已被删除的审批记录
if deleted_change_request_ids_str:
exclude_conditions |= Q(type="案件变更") & Q(user_id__in=deleted_change_request_ids_str)
# 排除 type="投标登记" 且关联的投标登记已被删除的审批记录
if deleted_bid_ids_str:
exclude_conditions |= Q(type="投标登记") & Q(user_id__in=deleted_bid_ids_str)
# 排除 type="立项登记" 且关联的立项登记已被删除的审批记录
if deleted_project_ids_str:
exclude_conditions |= Q(type="立项登记") & Q(user_id__in=deleted_project_ids_str)
# 排除 type="收入确认" 且关联的收入确认已被删除的审批记录
if deleted_income_ids_str:
exclude_conditions |= Q(type="收入确认") & Q(user_id__in=deleted_income_ids_str)
# 过滤已删除的开票记录
if deleted_invoice_ids_str:
exclude_conditions |= Q(type="开票") & Q(user_id__in=deleted_invoice_ids_str)
# 排除 type="调账申请" 且关联的调账申请已被删除的审批记录
if deleted_accounts_ids_str:
exclude_conditions |= Q(type="调账申请") & Q(user_id__in=deleted_accounts_ids_str)
# 排除 type="付款申请" 且关联的付款申请已被删除的审批记录
if deleted_payment_ids_str:
exclude_conditions |= Q(type="付款申请") & Q(user_id__in=deleted_payment_ids_str)
# 排除 type="报销申请" 且关联的报销申请已被删除的审批记录
if deleted_reimbursement_ids_str:
exclude_conditions |= Q(type="报销申请") & Q(user_id__in=deleted_reimbursement_ids_str)
# 排除 type="工资/奖金变更" 且关联的工资/奖金变更已被删除的审批记录
if deleted_bonus_change_ids_str:
exclude_conditions |= Q(type="工资/奖金变更") & Q(user_id__in=deleted_bonus_change_ids_str)
# 排除 type="申请用印" 且关联的申请用印已被删除的审批记录
if deleted_seal_application_ids_str:
exclude_conditions |= Q(type="申请用印") & Q(user_id__in=deleted_seal_application_ids_str)
# 排除 type="入职财务登记" 且关联的用户已被删除的审批记录
if deleted_user_ids_str:
exclude_conditions |= Q(type="入职财务登记") & Q(user_id__in=deleted_user_ids_str)
# 排除 type="离职财务登记" 且关联的用户已被删除的审批记录
if deleted_user_ids_str:
exclude_conditions |= Q(type="离职财务登记") & Q(user_id__in=deleted_user_ids_str)
# 排除 type="待办" 且关联的待办已被删除的审批记录
if deleted_schedule_ids_str:
exclude_conditions |= Q(type="待办") & Q(user_id__in=deleted_schedule_ids_str)
# 结案申请同样使用 Schedule 作为承载对象
exclude_conditions |= Q(type="结案申请") & Q(user_id__in=deleted_schedule_ids_str)
if exclude_conditions:
approvals = approvals.exclude(exclude_conditions)
# 调试信息(生产环境可以注释掉)
# print(f"用户: {user.username}, 部门IDs: {user_department_ids_str}")
# print(f"查询到的审批数量: {approvals.count()}")
# 如果设置了 status 筛选,需要先计算所有记录的 status然后过滤
# 因为 status 是计算字段,不能直接在数据库查询中过滤
import re
filtered_approvals = []
if filter_status:
# 解析 status 筛选条件,支持逗号分割的多个值
status_list = []
if isinstance(filter_status, str) and ',' in filter_status:
# 如果包含逗号,分割成列表
status_list = [s.strip() for s in filter_status.split(',') if s.strip()]
elif isinstance(filter_status, list):
# 如果已经是列表,直接使用
status_list = filter_status
else:
# 单个值
status_list = [filter_status]
# 需要先遍历所有记录,计算 status然后过滤
# 优化后:不需要查询业务记录状态,直接根据审批记录状态判断
for info in approvals:
# 计算 approval_status与返回数据时的逻辑保持一致
approval_status = "审批中" # 默认状态
# 待办类型的状态显示逻辑:审核中->审核中,已抄送财务/待查看->待查看,已通过->已完成
if info.type == "待办":
if info.state == "审核中":
approval_status = "审核中"
elif info.state in ("已抄送财务", "待查看"):
approval_status = "待查看"
elif info.state == "已通过":
approval_status = "已完成"
elif info.state == "未通过":
approval_status = "审核中" # 未通过也可以继续审批流程
else:
# 其他类型:审核中->审批中,已抄送财务/待查看->待查看,已通过->已完成
if info.state in ("已抄送财务", "待查看"):
approval_status = "待查看"
elif info.state == "已通过":
approval_status = "已完成"
elif info.state == "审核中":
approval_status = "审批中"
elif info.state == "未通过":
approval_status = "审批中" # 未通过也可以继续审批流程
# 如果 status 匹配(支持多个值),添加到过滤列表
if approval_status in status_list:
filtered_approvals.append(info)
approvals = filtered_approvals
total = len(approvals)
else:
total = len(approvals)
paginator = Paginator(approvals, per_page)
try:
user_agents_page = paginator.page(page)
except PageNotAnInteger:
user_agents_page = paginator.page(1)
except EmptyPage:
user_agents_page = paginator.page(paginator.num_pages)
data = []
for info in user_agents_page.object_list:
# 待查看状态仅当申请人主动调用审批处理接口approval_processing时才更新为已通过此处不再自动更新
title = info.title
content = info.content
# 处理title字段中的JSON格式数据将委托人和相对方的JSON格式转换为只显示name
# 处理委托人:委托人:[{\"index\":1,\"name\":\"测试111\",\"idNumber\":\"111\"}] 或 委托人:[{\"index\":1
title_client_pattern = r'委托人:(\[.*?)(?= vs ||$)'
def replace_title_client(match):
json_str = match.group(1)
try:
# 处理转义的引号,将 \" 转换为 "
json_str = json_str.replace('\\"', '"')
# 尝试补全不完整的 JSON如果以 [ 开头但没有 ] 结尾)
if json_str.startswith('[') and not json_str.endswith(']'):
# 尝试找到最后一个完整的对象
# 查找最后一个 "name" 字段
name_match = re.search(r'"name"\s*:\s*"([^"]+)"', json_str)
if name_match:
return f"委托人:{name_match.group(1)}"
# 尝试解析完整的 JSON
client_list = json.loads(json_str + ']' if json_str.startswith('[') and not json_str.endswith(']') else json_str)
if isinstance(client_list, list):
names = [item.get('name', '') for item in client_list if isinstance(item, dict) and item.get('name')]
return f"委托人:{''.join(names)}" if names else match.group(0)
except (json.JSONDecodeError, TypeError, ValueError):
# 如果解析失败,尝试直接提取 name 字段
name_match = re.search(r'"name"\s*:\s*"([^"]+)"', json_str)
if name_match:
return f"委托人:{name_match.group(1)}"
return match.group(0)
title = re.sub(title_client_pattern, replace_title_client, title)
# 处理相对方:相对方:[{\"index\":1,\"name\":\"测试222\",\"idNumber\":\"222\"}] 或 相对方:[{\"index\":1
title_party_pattern = r'相对方:(\[.*?)(?=|$)'
def replace_title_party(match):
json_str = match.group(1)
try:
# 处理转义的引号,将 \" 转换为 "
json_str = json_str.replace('\\"', '"')
# 尝试补全不完整的 JSON
if json_str.startswith('[') and not json_str.endswith(']'):
name_match = re.search(r'"name"\s*:\s*"([^"]+)"', json_str)
if name_match:
return f"相对方:{name_match.group(1)}"
# 尝试解析完整的 JSON
party_list = json.loads(json_str + ']' if json_str.startswith('[') and not json_str.endswith(']') else json_str)
if isinstance(party_list, list):
names = [item.get('name', '') for item in party_list if isinstance(item, dict) and item.get('name')]
return f"相对方:{''.join(names)}" if names else match.group(0)
except (json.JSONDecodeError, TypeError, ValueError):
# 如果解析失败,尝试直接提取 name 字段
name_match = re.search(r'"name"\s*:\s*"([^"]+)"', json_str)
if name_match:
return f"相对方:{name_match.group(1)}"
return match.group(0)
title = re.sub(title_party_pattern, replace_title_party, title)
# 处理content字段中的JSON格式数据将客户名称和相对方名称的JSON格式转换为只显示name
# 处理客户名称:客户名称:[{\"index\":1,\"name\":\"测试111\",\"idNumber\":\"111\"}]
# 匹配从"客户名称:"开始到下一个""或""或字符串结尾的JSON数组
client_name_pattern = r'客户名称:(\[.*?\](?=||$))'
def replace_client_name(match):
json_str = match.group(1)
try:
# 处理转义的引号
json_str = json_str.replace('\\"', '"')
client_list = json.loads(json_str)
if isinstance(client_list, list):
names = [item.get('name', '') for item in client_list if isinstance(item, dict) and item.get('name')]
return f"客户名称:{''.join(names)}" if names else match.group(0)
except (json.JSONDecodeError, TypeError, ValueError):
pass
return match.group(0)
content = re.sub(client_name_pattern, replace_client_name, content)
# 处理相对方名称:相对方名称:[{\"index\":1,\"name\":\"测试222\",\"idNumber\":\"222\"}]
party_name_pattern = r'相对方名称:(\[.*?\](?=||$))'
def replace_party_name(match):
json_str = match.group(1)
try:
# 处理转义的引号,将 \" 转换为 "
json_str = json_str.replace('\\"', '"')
party_list = json.loads(json_str)
if isinstance(party_list, list):
names = [item.get('name', '') for item in party_list if isinstance(item, dict) and item.get('name')]
return f"相对方名称:{''.join(names)}" if names else match.group(0)
except (json.JSONDecodeError, TypeError, ValueError):
pass
return match.group(0)
content = re.sub(party_name_pattern, replace_party_name, content)
# 处理委托人:委托人:[{\"index\":1,\"name\":\"测试111\",\"idNumber\":\"111\"}] 或 委托人:[{\"index\":1
client_pattern = r'委托人:(\[.*?)(?=||$| - |相对方)'
def replace_client(match):
json_str = match.group(1)
try:
# 处理转义的引号,将 \" 转换为 "
json_str = json_str.replace('\\"', '"')
# 尝试补全不完整的 JSON
if json_str.startswith('[') and not json_str.endswith(']'):
name_match = re.search(r'"name"\s*:\s*"([^"]+)"', json_str)
if name_match:
return f"委托人:{name_match.group(1)}"
# 尝试解析完整的 JSON
client_list = json.loads(json_str + ']' if json_str.startswith('[') and not json_str.endswith(']') else json_str)
if isinstance(client_list, list):
names = [item.get('name', '') for item in client_list if isinstance(item, dict) and item.get('name')]
return f"委托人:{''.join(names)}" if names else match.group(0)
except (json.JSONDecodeError, TypeError, ValueError):
# 如果解析失败,尝试直接提取 name 字段
name_match = re.search(r'"name"\s*:\s*"([^"]+)"', json_str)
if name_match:
return f"委托人:{name_match.group(1)}"
return match.group(0)
content = re.sub(client_pattern, replace_client, content)
# 处理相对方:相对方:[{\"index\":1,\"name\":\"测试222\",\"idNumber\":\"222\"}] 或 相对方:[{\"index\":1
party_pattern = r'相对方:(\[.*?)(?=||$| - |\s)'
def replace_party(match):
json_str = match.group(1)
try:
# 处理转义的引号,将 \" 转换为 "
json_str = json_str.replace('\\"', '"')
# 尝试补全不完整的 JSON
if json_str.startswith('[') and not json_str.endswith(']'):
name_match = re.search(r'"name"\s*:\s*"([^"]+)"', json_str)
if name_match:
return f"相对方:{name_match.group(1)}"
# 尝试解析完整的 JSON
party_list = json.loads(json_str + ']' if json_str.startswith('[') and not json_str.endswith(']') else json_str)
if isinstance(party_list, list):
names = [item.get('name', '') for item in party_list if isinstance(item, dict) and item.get('name')]
return f"相对方:{''.join(names)}" if names else match.group(0)
except (json.JSONDecodeError, TypeError, ValueError):
# 如果解析失败,尝试直接提取 name 字段
name_match = re.search(r'"name"\s*:\s*"([^"]+)"', json_str)
if name_match:
return f"相对方:{name_match.group(1)}"
return match.group(0)
content = re.sub(party_pattern, replace_party, content)
# 如果是案件管理类型的审批,添加委托人名字和相对方名字以便区分
if info.type == "案件管理":
try:
case_id = int(info.user_id)
case = Case.objects.filter(id=case_id, is_deleted=False).select_related('project').first()
if case:
# 从Case模型的client_name和party_name字段获取信息
# 或者从关联的project获取
client_name = case.client_name or ""
party_name = case.party_name or ""
# 如果Case中没有尝试从project获取
if not client_name and case.project:
client_name = case.project.client_info or ""
if not party_name and case.project:
party_name = case.project.party_info or ""
# 解析名字处理JSON格式和普通格式
def extract_names_from_info(info_str):
"""从信息字符串中提取名字支持JSON格式和普通格式"""
if not info_str:
return '未知'
try:
# 尝试解析为JSON数组
info_list = json.loads(info_str)
if isinstance(info_list, list) and len(info_list) > 0:
# 提取所有name字段
names = [item.get('name', '') for item in info_list if isinstance(item, dict) and item.get('name')]
return ''.join(names) if names else '未知'
except (json.JSONDecodeError, TypeError, AttributeError):
# 如果不是JSON格式尝试提取名字部分
# 可能是完整信息,如"张三身份证号xxx"
name_str = info_str.split('')[0].split(',')[0].strip()
return name_str if name_str else '未知'
return '未知'
client_str = extract_names_from_info(client_name)
party_str = extract_names_from_info(party_name)
# 添加到title中格式案件管理信息提交委托人XXX vs 相对方XXX
title = f"案件管理信息提交(委托人:{client_str} vs 相对方:{party_str}"
# 也更新content添加案件信息
if "提交了一份案件信息" in content:
content = f"{content} - 委托人:{client_str},相对方:{party_str}"
except (ValueError, TypeError, AttributeError):
# 如果解析失败保持原有title和content
pass
# 计算优化后的状态:审核中/审批中、待查看、已完成
# 注意status 字段的计算只依赖于审批记录的状态,不需要查询业务记录状态
# 状态判断逻辑:
# 1. 待办类型:
# - "审核中" -> "审核中"(在指定人员审核时)
# - "已抄送财务" -> "待查看"(抄送到财务部时)
# - "已通过" -> "已完成"(财务查看了之后)
# 2. 其他类型:
# - "审核中" -> "审批中"(在审核人审核时)
# - "已抄送财务" -> "待查看"(抄送到财务部时)
# - "已通过" -> "已完成"(财务查看了之后)
approval_status = "审批中" # 默认状态
# 待办类型的状态显示逻辑:审核中->审核中,已抄送财务/待查看->待查看,已通过->已完成
if info.type == "待办":
if info.state == "审核中":
approval_status = "审核中"
elif info.state in ("已抄送财务", "待查看"):
approval_status = "待查看"
elif info.state == "已通过":
approval_status = "已完成"
elif info.state == "未通过":
approval_status = "审核中" # 未通过也可以继续审批流程
else:
# 其他类型:审核中->审批中,已抄送财务/待查看->待查看(投标/立项最后一步给申请人),已通过->已完成
if info.state in ("已抄送财务", "待查看"):
approval_status = "待查看"
elif info.state == "已通过":
approval_status = "已完成"
elif info.state == "审核中":
approval_status = "审批中"
elif info.state == "未通过":
approval_status = "审批中" # 未通过也可以继续审批流程
itme = {
'id': info.id,
"title": title,
"content": content,
"times": info.times,
"completeTiem": info.completeTiem,
"personincharge": info.personincharge,
"type": info.type,
"status": approval_status, # 优化后的状态:审批中、已完成
"rejection_reason": info.rejection_reason if info.rejection_reason else None, # 不通过原因(审核不通过时填写)
}
# 如果是离职财务登记类型从content中解析结算工资
if info.type == "离职财务登记":
settlement_salary = None
if content and "结算工资:" in content:
import re
# 匹配格式结算工资xxx元 或 结算工资:待审批人指定
match = re.search(r'结算工资:([^]*)', content)
if match:
salary_str = match.group(1).strip()
# 如果包含"元"字,提取数字部分
if '' in salary_str:
salary_match = re.search(r'(\d+(?:\.\d+)?)', salary_str)
if salary_match:
settlement_salary = salary_match.group(1)
elif salary_str == "待审批人指定":
settlement_salary = None
else:
# 尝试直接提取数字(处理其他格式)
salary_match = re.search(r'(\d+(?:\.\d+)?)', salary_str)
if salary_match:
settlement_salary = salary_match.group(1)
itme["settlement_salary"] = settlement_salary
# 如果是立项登记类型:返回申请人、审批流程、冲突信息等(审批人--->申请人,审批完成后申请人/审批人可见)
if info.type == "立项登记":
# 申请人:优先从 Approval.applicant 取,否则从 content 解析「申请人xxx」
submitter_proj = getattr(info, 'applicant', None)
if not submitter_proj and content and "申请人:" in content:
import re
m = re.search(r'申请人:([^,→\s]+)', content)
if m:
submitter_proj = m.group(1).strip()
itme["submitter"] = submitter_proj or ""
itme["applicant"] = submitter_proj or ""
try:
project_id = int(info.user_id)
project = ProjectRegistration.objects.filter(id=project_id, is_deleted=False).first()
approvers_order_list_proj = []
if project and getattr(project, 'approvers_order', None):
try:
order = json.loads(project.approvers_order or '[]')
approvers_order_list_proj = order if isinstance(order, list) else []
except (json.JSONDecodeError, TypeError):
pass
itme["approvers_order"] = approvers_order_list_proj
content_val_proj = itme.get("content") or ""
if submitter_proj and "申请人:" not in content_val_proj and "提交人:" not in content_val_proj:
content_val_proj = content_val_proj.rstrip() + ",申请人:" + submitter_proj
# 待办展示:负责人统一显示为承办人
itme["content"] = content_val_proj.replace("负责人:", "承办人:").replace("负责人:", "承办人:")
except (ValueError, TypeError, AttributeError):
itme["approvers_order"] = []
try:
from business.views import conflict_search
project_id = int(info.user_id)
project = ProjectRegistration.objects.filter(id=project_id, is_deleted=False).first()
# 修复:只要有委托人或相对方任一不为空就进行冲突检索(相对方为非必填)
if project and (project.client_info or project.party_info):
# 使用 conflict_search 函数进行冲突检索(支持更灵活的参数组合)
conflict_records = conflict_search(
client_info=project.client_info,
party_info=project.party_info,
exclude_project_id=project_id
)
# 处理冲突记录:将 client_info、party_info、BiddingUnit、client_username、party_username 从 JSON 解析为列表,
# 并归一化为前端期望的格式 [{ index, name, idNumber }, ...](兼容前端 isValidFormat 与查看冲突展示)
def _normalize_person_list(val):
"""将 JSON 字符串或列表归一化为 [{ index, name, idNumber }, ...],兼容前端;解析失败返回 None 以保留原值"""
if not val:
return []
try:
lst = json.loads(val) if isinstance(val, str) else val
if not isinstance(lst, list):
return None
out = []
for i, item in enumerate(lst):
if not isinstance(item, dict):
continue
name = item.get('name') or item.get('name_original') or ''
id_num = item.get('idNumber') or item.get('id_number') or ''
if not isinstance(name, str):
name = str(name) if name else ''
if not isinstance(id_num, str):
id_num = str(id_num) if id_num else ''
out.append({
'index': item.get('index') if isinstance(item.get('index'), (int, float)) else (i + 1),
'name': name,
'idNumber': id_num
})
return out
except (json.JSONDecodeError, TypeError, ValueError):
return None
def parse_json_fields(records):
"""将记录中的 JSON 字符串字段解析为列表,并归一化为前端格式"""
processed = []
for record in records:
new_record = dict(record)
for key in ('client_info', 'party_info', 'BiddingUnit', 'client_username', 'party_username'):
if key not in new_record or not new_record[key]:
continue
normalized = _normalize_person_list(new_record[key])
if normalized is not None:
new_record[key] = normalized
processed.append(new_record)
return processed
# 添加三个冲突字段,解析 JSON 字符串为列表
itme["prefiling_conflicts"] = parse_json_fields(conflict_records.get('prefiling_conflicts', []))
itme["project_conflicts"] = parse_json_fields(conflict_records.get('project_conflicts', []))
itme["bid_conflicts"] = parse_json_fields(conflict_records.get('bid_conflicts', []))
else:
# 如果没有找到项目或缺少信息,返回空数组
itme["prefiling_conflicts"] = []
itme["project_conflicts"] = []
itme["bid_conflicts"] = []
except (ValueError, TypeError, AttributeError, ImportError):
# 如果解析失败,返回空数组
itme["prefiling_conflicts"] = []
itme["project_conflicts"] = []
itme["bid_conflicts"] = []
# 如果是投标登记类型:返回申请人、审批流程、冲突信息等(与立项相同逻辑)
if info.type == "投标登记":
# 申请人:优先从 Approval.applicant 取,否则从 content 解析「申请人xxx」
submitter = getattr(info, 'applicant', None)
if not submitter and content and "申请人:" in content:
import re
m = re.search(r'申请人:([^,→\s]+)', content)
if m:
submitter = m.group(1).strip()
itme["submitter"] = submitter or ""
itme["applicant"] = submitter or "" # 投标待办展示“申请人”字段(审批人--->申请人)
try:
from business.views import conflict_search
bid_id = int(info.user_id)
bid = Bid.objects.filter(id=bid_id, is_deleted=False).first()
# 审批流程:从 Bid.approvers_order 解析为名单列表
approvers_order_list = []
if bid and getattr(bid, 'approvers_order', None):
try:
order = json.loads(bid.approvers_order or '[]')
approvers_order_list = order if isinstance(order, list) else []
except (json.JSONDecodeError, TypeError):
pass
itme["approvers_order"] = approvers_order_list # 审批流程(审批人顺序)
# 将申请人拼接到待办展示信息content便于列表一眼看到
content_val = itme.get("content") or ""
if submitter and "申请人:" not in content_val and "提交人:" not in content_val:
itme["content"] = content_val.rstrip() + ",申请人:" + submitter
if bid and bid.BiddingUnit:
conflict_result = conflict_search(bidding_unit=bid.BiddingUnit, exclude_bid_id=bid_id)
# 与立项登记共用同一套解析与归一化逻辑(预立案含 client_username/party_username立项/投标含 client_info/party_info/BiddingUnit
def _normalize_person_list_bid(val):
if not val:
return []
try:
lst = json.loads(val) if isinstance(val, str) else val
if not isinstance(lst, list):
return None
out = []
for i, item in enumerate(lst):
if not isinstance(item, dict):
continue
name = item.get('name') or item.get('name_original') or ''
id_num = item.get('idNumber') or item.get('id_number') or ''
if not isinstance(name, str):
name = str(name) if name else ''
if not isinstance(id_num, str):
id_num = str(id_num) if id_num else ''
out.append({
'index': item.get('index') if isinstance(item.get('index'), (int, float)) else (i + 1),
'name': name,
'idNumber': id_num
})
return out
except (json.JSONDecodeError, TypeError, ValueError):
return None
def parse_json_fields_bid(records):
processed = []
for record in records:
new_record = dict(record)
for key in ('client_info', 'party_info', 'BiddingUnit', 'client_username', 'party_username'):
if key not in new_record or not new_record[key]:
continue
normalized = _normalize_person_list_bid(new_record[key])
if normalized is not None:
new_record[key] = normalized
processed.append(new_record)
return processed
itme["prefiling_conflicts"] = parse_json_fields_bid(conflict_result.get('prefiling_conflicts', []))
itme["project_conflicts"] = parse_json_fields_bid(conflict_result.get('project_conflicts', []))
itme["bid_conflicts"] = parse_json_fields_bid(conflict_result.get('bid_conflicts', []))
else:
itme["prefiling_conflicts"] = []
itme["project_conflicts"] = []
itme["bid_conflicts"] = []
except (ValueError, TypeError, AttributeError, ImportError):
itme["prefiling_conflicts"] = []
itme["project_conflicts"] = []
itme["bid_conflicts"] = []
# 案件变更、结案申请:返回上传文件 URL统一用 attachment_urls字符串单个为 URL多个用逗号拼接无文件为空字符串
if info.type == "案件变更":
try:
change_request = CaseChangeRequest.objects.filter(id=int(info.user_id), is_deleted=False).first()
if change_request and change_request.change_agreement:
try:
agreement_list = json.loads(change_request.change_agreement)
lst = agreement_list if isinstance(agreement_list, list) else []
itme["attachment_urls"] = lst[0] if len(lst) == 1 else (",".join(str(u) for u in lst) if lst else "")
except (json.JSONDecodeError, TypeError):
itme["attachment_urls"] = ""
else:
itme["attachment_urls"] = ""
except (ValueError, TypeError):
itme["attachment_urls"] = ""
elif info.type == "结案申请":
try:
schedule = Schedule.objects.filter(id=int(info.user_id), is_deleted=False).first()
if schedule and schedule.remark:
try:
remark_data = json.loads(schedule.remark)
closing_files = remark_data.get("closing_application_files", [])
lst = closing_files if isinstance(closing_files, list) else []
itme["attachment_urls"] = lst[0] if len(lst) == 1 else (",".join(str(u) for u in lst) if lst else "")
except (json.JSONDecodeError, TypeError):
itme["attachment_urls"] = ""
else:
itme["attachment_urls"] = ""
except (ValueError, TypeError):
itme["attachment_urls"] = ""
data.append(itme)
return Response({'message': '展示成功', "total": total, 'data': data, 'code': 0}, status=status.HTTP_200_OK)
class approvalProcessing(APIView):
def post(self, request, *args, **kwargs):
"""
消除代办
财务查看时只需要传type和id不需要传state默认为查看通过
财务部只需要查看,不需要审批,查看后直接完成(审批记录和业务记录都更新为"已通过"
:param request:
:param args:
:param kwargs:
:return:
"""
state = request.data.get('state')
type = request.data.get('type')
id = request.data.get('id')
rejection_reason = request.data.get('rejection_reason') # 不通过原因(审核不通过时填写)
if not all([type, id]):
return Response({'status': 'error', 'message': '缺少参数type或id', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
# 如果审核不通过,必须填写不通过原因
if state == "未通过" and not rejection_reason:
return Response({
'status': 'error',
'message': '审核不通过时必须填写不通过原因',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
try:
approval = Approval.objects.get(id=id, is_deleted=False)
except Approval.DoesNotExist:
return Response({'status': 'error', 'message': '审批记录不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
# 财务查看逻辑如果只传了type和id没有传state且当前是财务审核则默认为查看通过
from User.utils import is_finance_personincharge
is_finance_view = False
if not state and is_finance_personincharge(approval.personincharge) and approval.state == "已抄送财务":
state = "已通过" # 财务查看默认通过
is_finance_view = True
if type == "入职财务登记":
try:
user = User.objects.get(id=approval.user_id, is_deleted=False)
except User.DoesNotExist:
return Response({'status': 'error', 'message': '用户记录不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
# 检查当前是否已经是财务审核
if is_finance_personincharge(approval.personincharge) and approval.state == "已抄送财务":
# 财务部审核逻辑如果只传了type和id不传state则默认为"已通过"
if not state:
state = "已通过"
if state == "已通过":
approval.state = "已通过"
user.state = "在职"
else:
approval.state = "未通过"
user.state = "异常"
# 保存不通过原因
if rejection_reason:
approval.rejection_reason = rejection_reason
approval.save(update_fields=['state', 'rejection_reason'])
user.save(update_fields=['state'])
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
# 使用统一的审核流程处理函数
# 非财务查看时state参数是必填的
if not state:
return Response({'status': 'error', 'message': '缺少参数state审核状态已通过/未通过)', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
from User.utils import process_approval_flow
import logging
logger = logging.getLogger(__name__)
current_approver = approval.personincharge
logger.info(f"approvalProcessing-入职财务登记: 审批ID={approval.id}, 当前审核人={current_approver}, 审核状态={state}, 审批内容={approval.content}")
is_completed, error = process_approval_flow(
approval=approval,
business_record=user, # User模型没有approvers_order字段但可以更新state
current_approver=current_approver,
state=state,
approval_type="入职财务登记",
final_state_map={"已通过": "在职", "未通过": "异常"},
rejection_reason=rejection_reason
)
# 刷新审批记录,查看更新后的状态
approval.refresh_from_db()
logger.info(f"approvalProcessing-入职财务登记: 审核后 - personincharge={approval.personincharge}, state={approval.state}")
if error:
return Response({'status': 'error', 'message': error, 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
# 如果审核不通过,更新用户状态
if state == "未通过":
user.state = "异常"
user.save(update_fields=['state'])
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
if type == "开票":
from finance.models import Invoice
try:
invoice = Invoice.objects.get(id=approval.user_id, is_deleted=False)
except Invoice.DoesNotExist:
return Response({'status': 'error', 'message': '开票记录不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
# 检查当前是否已经是财务审核
if is_finance_personincharge(approval.personincharge) and approval.state == "已抄送财务":
# 财务部审核逻辑如果只传了type和id不传state则默认为"已通过"
if not state:
state = "已通过"
if state == "已通过":
approval.state = "已通过"
invoice.state = "已通过"
else:
approval.state = "未通过"
invoice.state = "未通过"
# 保存不通过原因
if rejection_reason:
approval.rejection_reason = rejection_reason
invoice.save(update_fields=['state'])
approval.save(update_fields=['state', 'rejection_reason'])
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
# 使用统一的审核流程处理函数
# 非财务查看时state参数是必填的
if not state:
return Response({'status': 'error', 'message': '缺少参数state审核状态已通过/未通过)', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
from User.utils import process_approval_flow
current_approver = approval.personincharge
is_completed, error = process_approval_flow(
approval=approval,
business_record=invoice,
current_approver=current_approver,
state=state,
approval_type="开票",
final_state_map={"已通过": "已通过", "未通过": "未通过"},
rejection_reason=rejection_reason
)
if error:
return Response({'status': 'error', 'message': error, 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
if type == "收入确认":
try:
income = Income.objects.get(id=approval.user_id, is_deleted=False)
except Income.DoesNotExist:
return Response({'status': 'error', 'message': '收入确认记录不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
# 检查当前是否已经是财务审核
if is_finance_personincharge(approval.personincharge) and approval.state == "已抄送财务":
# 财务部只需要查看,不需要审批,查看后直接完成
# 如果财务标记为未通过,则更新状态
if state == "未通过":
approval.state = "未通过"
income.state = "未通过"
# 保存不通过原因
if rejection_reason:
approval.rejection_reason = rejection_reason
income.save(update_fields=['state'])
approval.save(update_fields=['state', 'rejection_reason'])
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
else:
# 财务查看通过默认或不传state直接完成
# 更新审批记录状态为"已通过"(表示财务已查看)
approval.state = "已通过"
approval.save(update_fields=['state'])
# 确保业务记录状态也为"已通过"(财务查看即完成)
if income.state != "已通过":
income.state = "已通过"
income.save(update_fields=['state'])
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
# 负责人填写收入分配
allocate = request.data.get('allocate') # 收入分配(负责人必须填写)
# 如果当前状态是"待负责人填写分配",负责人必须填写收入分配
if income.state == "待负责人填写分配":
if not allocate:
return Response({
'status': 'error',
'message': '请填写收入分配',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 更新收入分配
income.allocate = allocate
# 负责人填写收入分配后,抄送财务时就已经审核通过,财务只是查看
income.state = "已通过"
# 更新审批内容,添加分配信息
if "收入分配:待负责人指定" in approval.content:
approval.content = approval.content.replace("收入分配:待负责人指定", f"收入分配:{allocate}")
else:
approval.content = approval.content + f",收入分配:{allocate}"
income.save(update_fields=['allocate', 'state'])
# 负责人填写收入分配后,抄送给财务负责人
from User.utils import get_finance_personincharge_value
finance_personincharge = get_finance_personincharge_value()
# 更新审批记录,抄送给财务(状态已经是"已通过"
approval.personincharge = finance_personincharge
approval.state = "已抄送财务"
approval.content = approval.content + ",已抄送财务部"
approval.save(update_fields=['personincharge', 'state', 'content'])
return Response({
'message': '收入分配已提交,已抄送给财务',
'code': 0,
'data': {
'allocate': allocate,
'state': income.state
}
}, status=status.HTTP_200_OK)
# 审批人审批时必须填写收入分配
# 如果收入分配还是"待负责人指定",且审批人要通过审批,则必须填写收入分配
if state == "已通过" and income.allocate == "待负责人指定":
if not allocate:
return Response({
'status': 'error',
'message': '请填写收入分配后再通过审批',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 如果审批人填写了收入分配,更新到记录中
if allocate:
income.allocate = allocate
# 更新审批内容,添加分配信息
if "收入分配:待审批人指定" in approval.content:
approval.content = approval.content.replace("收入分配:待审批人指定", f"收入分配:{allocate}")
elif "收入分配:待负责人指定" in approval.content:
approval.content = approval.content.replace("收入分配:待负责人指定", f"收入分配:{allocate}")
else:
approval.content = approval.content + f",收入分配:{allocate}"
income.save(update_fields=['allocate'])
approval.save(update_fields=['content'])
# 使用统一的审核流程处理函数
# 非财务查看时state参数是必填的
if not state:
return Response({'status': 'error', 'message': '缺少参数state审核状态已通过/未通过)', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
from User.utils import process_approval_flow
current_approver = approval.personincharge
is_completed, error = process_approval_flow(
approval=approval,
business_record=income,
current_approver=current_approver,
state=state,
approval_type="收入确认",
final_state_map={"已通过": "已通过", "未通过": "未通过"},
rejection_reason=rejection_reason
)
if error:
return Response({'status': 'error', 'message': error, 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
if type == "调账申请":
try:
account = Accounts.objects.get(id=approval.user_id, is_deleted=False)
except Accounts.DoesNotExist:
return Response({'status': 'error', 'message': '调账申请记录不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
# 调账申请不需要审核流程,直接抄送财务查看即可
# 检查当前是否已经是财务审核
if is_finance_personincharge(approval.personincharge) and approval.state == "已抄送财务":
# 财务部只需要查看,不需要审批,查看后直接完成
# 如果财务标记为未通过,则更新状态
if state == "未通过":
approval.state = "未通过"
account.state = "未通过"
# 保存不通过原因
if rejection_reason:
approval.rejection_reason = rejection_reason
account.save(update_fields=['state'])
approval.save(update_fields=['state', 'rejection_reason'])
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
else:
# 财务查看通过默认或不传state直接完成
# 更新审批记录状态为"已通过"(表示财务已查看)
approval.state = "已通过"
approval.save(update_fields=['state'])
# 确保业务记录状态也为"已通过"(财务查看即完成)
if account.state != "已通过":
account.state = "已通过"
account.save(update_fields=['state'])
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
# 其他情况(兼容旧数据)
return Response({'message': '调账申请不需要审核,已直接抄送财务', 'code': 0}, status=status.HTTP_200_OK)
if type == "付款申请":
try:
payment = Payment.objects.get(id=approval.user_id, is_deleted=False)
except Payment.DoesNotExist:
return Response({'status': 'error', 'message': '付款申请记录不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
# 检查当前是否已经是财务审核
if is_finance_personincharge(approval.personincharge) and approval.state == "已抄送财务":
# 财务部审核逻辑如果只传了type和id不传state则默认为"已通过"
if not state:
state = "已通过"
if state == "已通过":
approval.state = "已通过"
payment.state = "已通过"
else:
approval.state = "未通过"
payment.state = "未通过"
# 保存不通过原因
if rejection_reason:
approval.rejection_reason = rejection_reason
payment.save(update_fields=['state'])
approval.save(update_fields=['state', 'rejection_reason'])
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
# 使用统一的审核流程处理函数(与离职逻辑一样)
# 非财务查看时state参数是必填的
if not state:
return Response({'status': 'error', 'message': '缺少参数state审核状态已通过/未通过)', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
from User.utils import process_approval_flow
current_approver = approval.personincharge
is_completed, error = process_approval_flow(
approval=approval,
business_record=payment,
current_approver=current_approver,
state=state,
approval_type="付款申请",
final_state_map={"已通过": "已通过", "未通过": "未通过"},
rejection_reason=rejection_reason
)
if error:
return Response({'status': 'error', 'message': error, 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
if type == "报销申请":
try:
reimbursement = Reimbursement.objects.get(id=approval.user_id, is_deleted=False)
except Reimbursement.DoesNotExist:
return Response({'status': 'error', 'message': '报销申请记录不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
# 检查当前是否已经是财务审核
if is_finance_personincharge(approval.personincharge) and approval.state == "已抄送财务":
# 财务部审核逻辑如果只传了type和id不传state则默认为"已通过"
if not state:
state = "已通过"
if state == "已通过":
approval.state = "已通过"
reimbursement.state = "已通过"
else:
approval.state = "未通过"
reimbursement.state = "未通过"
# 保存不通过原因
if rejection_reason:
approval.rejection_reason = rejection_reason
reimbursement.save(update_fields=['state'])
approval.save(update_fields=['state', 'rejection_reason'])
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
# 使用统一的审核流程处理函数(与付款申请逻辑一样)
# 非财务查看时state参数是必填的
if not state:
return Response({'status': 'error', 'message': '缺少参数state审核状态已通过/未通过)', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
from User.utils import process_approval_flow
current_approver = approval.personincharge
is_completed, error = process_approval_flow(
approval=approval,
business_record=reimbursement,
current_approver=current_approver,
state=state,
approval_type="报销申请",
final_state_map={"已通过": "已通过", "未通过": "未通过"},
rejection_reason=rejection_reason
)
if error:
return Response({'status': 'error', 'message': error, 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
if type == "工资/奖金变更":
try:
bonus = BonusChange.objects.get(id=approval.user_id, is_deleted=False)
except BonusChange.DoesNotExist:
return Response({'status': 'error', 'message': '工资/奖金变更记录不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
# 检查当前是否已经是财务审核
if is_finance_personincharge(approval.personincharge) and approval.state == "已抄送财务":
# 财务部审核逻辑如果只传了type和id不传state则默认为"已通过"
if not state:
state = "已通过"
if state == "已通过":
approval.state = "已通过"
bonus.state = "已通过"
else:
approval.state = "未通过"
bonus.state = "未通过"
# 保存不通过原因
if rejection_reason:
approval.rejection_reason = rejection_reason
bonus.save(update_fields=['state'])
approval.save(update_fields=['state', 'rejection_reason'])
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
# 使用统一的审核流程处理函数(与付款申请、报销申请逻辑一样)
# 非财务查看时state参数是必填的
if not state:
return Response({'status': 'error', 'message': '缺少参数state审核状态已通过/未通过)', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
from User.utils import process_approval_flow
current_approver = approval.personincharge
is_completed, error = process_approval_flow(
approval=approval,
business_record=bonus,
current_approver=current_approver,
state=state,
approval_type="工资/奖金变更",
final_state_map={"已通过": "已通过", "未通过": "未通过"},
rejection_reason=rejection_reason
)
if error:
return Response({'status': 'error', 'message': error, 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
if type == "立项登记":
try:
project = ProjectRegistration.objects.get(id=approval.user_id, is_deleted=False)
except ProjectRegistration.DoesNotExist:
return Response({'status': 'error', 'message': '立项登记记录不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
# 申请人待查看阶段:只传 type/id、或 state=已通过/已查看 时,视为申请人查看完成(更新为已通过)
if approval.state == "待查看" and getattr(approval, 'applicant', None) and approval.personincharge == approval.applicant:
if state in (None, "", "已通过", "已查看"):
state = "已通过"
# 使用统一的审核流程处理函数
from User.utils import process_approval_flow
current_approver = approval.personincharge
is_completed, error = process_approval_flow(
approval=approval,
business_record=project,
current_approver=current_approver,
state=state,
approval_type="立项登记",
final_state_map={"已通过": "已通过", "未通过": "未通过"},
rejection_reason=rejection_reason
)
if error:
return Response({'status': 'error', 'message': error, 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
if type == "投标登记":
try:
bid = Bid.objects.get(id=approval.user_id, is_deleted=False)
except Bid.DoesNotExist:
return Response({'status': 'error', 'message': '投标登记记录不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
# 申请人待查看阶段:只传 type/id、或 state=已通过/已查看 时,视为申请人查看完成(更新为已通过)
if approval.state == "待查看" and getattr(approval, 'applicant', None) and approval.personincharge == approval.applicant:
if state in (None, "", "已通过", "已查看"):
state = "已通过"
# 使用统一的审核流程处理函数
from User.utils import process_approval_flow
current_approver = approval.personincharge
is_completed, error = process_approval_flow(
approval=approval,
business_record=bid,
current_approver=current_approver,
state=state,
approval_type="投标登记",
final_state_map={"已通过": "已通过", "未通过": "未通过"},
rejection_reason=rejection_reason
)
if error:
return Response({'status': 'error', 'message': error, 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
if type == "案件管理":
try:
approval = Approval.objects.get(id=id, is_deleted=False)
except Approval.DoesNotExist:
return Response({'status': 'error', 'message': '审批记录不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
try:
case = Case.objects.get(id=approval.user_id, is_deleted=False)
except Case.DoesNotExist:
return Response({'status': 'error', 'message': '案件记录不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
# 检查当前是否已经是财务审核
if is_finance_personincharge(approval.personincharge) and approval.state == "已抄送财务":
# 财务部只需要查看,不需要审批,查看后直接完成
# 如果只传了type和id不传state则默认为"已通过"
if not state:
state = "已通过"
if state == "已通过":
approval.state = "已通过"
case.state = "已通过"
else:
approval.state = "未通过"
case.state = "未通过"
# 保存不通过原因
if rejection_reason:
approval.rejection_reason = rejection_reason
approval.save(update_fields=['state', 'rejection_reason'])
case.save(update_fields=['state'])
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
# 使用统一的审核流程处理函数
from User.utils import process_approval_flow
current_approver = approval.personincharge
is_completed, error = process_approval_flow(
approval=approval,
business_record=case, # Case模型有approvers_order字段
current_approver=current_approver,
state=state,
approval_type="案件管理",
final_state_map={"已通过": "已通过", "未通过": "未通过"},
rejection_reason=rejection_reason
)
if error:
return Response({'status': 'error', 'message': error, 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
if type == "案件变更":
try:
approval = Approval.objects.get(id=id, is_deleted=False)
except Approval.DoesNotExist:
return Response({'status': 'error', 'message': '审批记录不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
try:
change_request = CaseChangeRequest.objects.get(id=approval.user_id, is_deleted=False)
except CaseChangeRequest.DoesNotExist:
return Response({'status': 'error', 'message': '变更申请记录不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
# 审核中时必须传 state已通过/未通过);待查看时申请人查看可传 已通过/已查看 或不传
if approval.state == "审核中" and not state:
return Response({'status': 'error', 'message': '缺少参数state审核状态已通过/未通过)', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
# 申请人待查看阶段:只传 type/id、或 state=已通过/已查看、或不传/undefined 时,视为申请人查看完成(更新为已通过,消除待办)
if approval.state == "待查看" and getattr(approval, 'applicant', None) and approval.personincharge == approval.applicant:
state_normalized = (state or "").strip().lower() if isinstance(state, str) else ""
if state in (None, "", "已通过", "已查看") or state_normalized in ("undefined", "null"):
state = "已通过"
from User.utils import process_approval_flow
current_approver = approval.personincharge
is_completed, error = process_approval_flow(
approval=approval,
business_record=change_request,
current_approver=current_approver,
state=state,
approval_type="案件变更",
final_state_map={"已通过": "已通过", "未通过": "未通过"},
rejection_reason=rejection_reason
)
if error:
return Response({'status': 'error', 'message': error, 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
if type == "申请用印":
try:
seal_app = SealApplication.objects.get(id=approval.user_id, is_deleted=False)
except SealApplication.DoesNotExist:
return Response({'status': 'error', 'message': '用印申请记录不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
# 检查当前是否已经是财务审核
if is_finance_personincharge(approval.personincharge) and approval.state == "已抄送财务":
# 财务部审核逻辑如果只传了type和id不传state则默认为"已通过"
if not state:
state = "已通过"
if state == "已通过":
approval.state = "已通过"
seal_app.state = "已通过"
else:
approval.state = "未通过"
seal_app.state = "未通过"
# 保存不通过原因
if rejection_reason:
approval.rejection_reason = rejection_reason
approval.save(update_fields=['state', 'rejection_reason'])
seal_app.save(update_fields=['state'])
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
# 使用统一的审核流程处理函数(与付款申请、报销申请逻辑一样)
# 非财务查看时state参数是必填的
if not state:
return Response({'status': 'error', 'message': '缺少参数state审核状态已通过/未通过)', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
from User.utils import process_approval_flow
current_approver = approval.personincharge
is_completed, error = process_approval_flow(
approval=approval,
business_record=seal_app, # SealApplication模型有approvers_order字段
current_approver=current_approver,
state=state,
approval_type="申请用印",
final_state_map={"已通过": "已通过", "未通过": "未通过"},
rejection_reason=rejection_reason
)
if error:
return Response({'status': 'error', 'message': error, 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
if type == "离职财务登记":
try:
user = User.objects.get(id=approval.user_id, is_deleted=False)
except User.DoesNotExist:
return Response({'status': 'error', 'message': '用户记录不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
# 接收离职工资参数(可选,审批人可以填写)
settlement_salary = request.data.get('settlement_salary')
# 如果审批人填写了离职工资更新审批记录的content字段
if settlement_salary:
# 更新审批内容中的结算工资信息
content = approval.content
# 替换或添加结算工资信息
if "结算工资:" in content:
# 使用正则表达式替换结算工资部分
import re
content = re.sub(r'结算工资:[^]*', f'结算工资:{settlement_salary}', content)
else:
# 如果content中没有结算工资信息添加到末尾
content = content + f",结算工资:{settlement_salary}"
approval.content = content
approval.save(update_fields=['content'])
# 检查当前是否已经是财务审核
if is_finance_personincharge(approval.personincharge) and approval.state == "已抄送财务":
# 财务部只需要查看,不需要审批,查看后直接完成
# 如果只传了type和id不传state则默认为"已通过"
if not state:
state = "已通过"
if state == "已通过":
approval.state = "已通过"
# 用户状态已经在离职登记时设置为"已离职",这里不需要再次修改
else:
approval.state = "未通过"
# 如果审批未通过,恢复用户状态为"在职"
user.state = "在职"
user.Dateofdeparture = None
# 保存不通过原因
if rejection_reason:
approval.rejection_reason = rejection_reason
user.save(update_fields=['state', 'Dateofdeparture'])
approval.save(update_fields=['state', 'rejection_reason'])
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
# 使用统一的审核流程处理函数(会同步离职工资信息到下一个审批人)
from User.utils import process_approval_flow
current_approver = approval.personincharge
is_completed, error = process_approval_flow(
approval=approval,
business_record=user, # User模型没有approvers_order字段但可以更新state
current_approver=current_approver,
state=state,
approval_type="离职财务登记",
final_state_map={"已通过": "已离职", "未通过": "在职"},
rejection_reason=rejection_reason
)
if error:
return Response({'status': 'error', 'message': error, 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
# 如果审核不通过,恢复用户状态为"在职"
if state == "未通过":
user.state = "在职"
user.Dateofdeparture = None
user.save(update_fields=['state', 'Dateofdeparture'])
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
if type == "待办":
try:
from business.models import Schedule
schedule = Schedule.objects.get(id=approval.user_id, is_deleted=False)
except Schedule.DoesNotExist:
return Response({'status': 'error', 'message': '待办记录不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
# 使用统一的审核流程处理函数
from User.utils import process_approval_flow
import logging
logger = logging.getLogger(__name__)
current_approver = approval.personincharge
logger.info(f"approvalProcessing-待办: 审批ID={approval.id}, 待办ID={schedule.id}, 当前审核人={current_approver}, 审核状态={state}, 审批内容={approval.content}")
is_completed, error = process_approval_flow(
approval=approval,
business_record=schedule,
current_approver=current_approver,
state=state,
approval_type="待办",
final_state_map={"已通过": "已完成", "未通过": "未通过"},
rejection_reason=rejection_reason
)
# 刷新审批记录,查看更新后的状态
approval.refresh_from_db()
schedule.refresh_from_db()
logger.info(f"approvalProcessing-待办: 审核后 - personincharge={approval.personincharge}, state={approval.state}, 待办状态={schedule.state}")
if error:
return Response({'status': 'error', 'message': error, 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
if type == "结案申请":
from business.models import Schedule, Case
try:
schedule = Schedule.objects.get(id=approval.user_id, is_deleted=False)
except Schedule.DoesNotExist:
return Response({'status': 'error', 'message': '结案申请记录不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
# 使用统一的审核流程处理函数
from User.utils import process_approval_flow
current_approver = approval.personincharge
is_completed, error = process_approval_flow(
approval=approval,
business_record=schedule,
current_approver=current_approver,
state=state,
approval_type="结案申请",
final_state_map={"已通过": "已完成", "未通过": "未通过"},
rejection_reason=rejection_reason
)
if error:
return Response({'status': 'error', 'message': error, 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
# 审批通过后,更新 Case.Closingapplication 字段
# 刷新 schedule 和 approval 以获取最新状态
schedule.refresh_from_db()
approval.refresh_from_db()
if schedule.state == "已完成" or approval.state == "已通过":
try:
import json
import logging
logger = logging.getLogger(__name__)
# 从 schedule.remark 中获取 case_id 和 closing_application_files
remark_data = json.loads(schedule.remark) if schedule.remark else {}
case_id = remark_data.get('case_id')
closing_files = remark_data.get('closing_application_files', [])
if case_id and closing_files:
case = Case.objects.filter(id=case_id, is_deleted=False).first()
if case:
case.Closingapplication = json.dumps(closing_files, ensure_ascii=False)
case.save(update_fields=['Closingapplication'])
logger.info(f"结案申请审批通过,已更新 Case(id={case_id}) 的 Closingapplication 字段")
except Exception as e:
import logging
logger = logging.getLogger(__name__)
logger.error(f"结案申请审批通过后更新 Case.Closingapplication 失败: {str(e)}")
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK)
class personneldisplay(APIView):
def get(self, request, *args, **kwargs):
"""
人员列表
:param request:
:param args:
:param kwargs:
:return:
"""
users = User.objects.filter(is_deleted=False)
data = []
for user in users:
itme = {
'id': user.id,
"username": user.username,
'position': user.position,
}
data.append(itme)
return Response({'message': '展示成功', "data": data, 'code': 0}, status=status.HTTP_200_OK)
class DeleteUser(APIView):
def post(self, request, *args, **kwargs):
"""
删除用户
:param request:
:param args:
:param kwargs:
:return:
"""
id = request.data.get('id')
if not id:
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
try:
user = User.objects.get(id=id, is_deleted=False)
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)
# ========== 团队管理接口 ==========
class TeamView(APIView):
"""团队列表(不分页,用于下拉选择)"""
def post(self, request, *args, **kwargs):
name = request.data.get('name', '')
Q_obj = Q()
if name:
Q_obj &= Q(name__icontains=name)
teams = Team.objects.filter(Q_obj, is_deleted=False).order_by('id')
data = []
for team in teams:
data.append({
'id': team.id,
'name': team.name,
'team_type': team.team_type,
'team_type_display': team.get_team_type_display(),
'description': team.description,
})
return Response({'message': '展示成功', "data": data, 'code': 0}, status=status.HTTP_200_OK)
class TeamListView(APIView):
"""团队列表(分页)"""
def post(self, request, *args, **kwargs):
page = request.data.get('page', 1)
per_page = request.data.get('per_page', 10)
name = request.data.get('name', '')
Q_obj = Q()
if name:
Q_obj &= Q(name__icontains=name)
teams = Team.objects.filter(Q_obj, is_deleted=False).order_by('-id')
total = teams.count()
paginator = Paginator(teams, per_page)
try:
teams_page = paginator.page(page)
except PageNotAnInteger:
teams_page = paginator.page(1)
except EmptyPage:
teams_page = paginator.page(paginator.num_pages)
data = []
for team in teams_page.object_list:
# 统计该团队下的人员数量
user_count = User.objects.filter(team=team.name, is_deleted=False).count()
data.append({
'id': team.id,
'name': team.name,
'team_type': team.team_type,
'team_type_display': team.get_team_type_display(),
'description': team.description,
'user_count': user_count,
})
return Response({
'message': '查询成功',
'total': total,
'data': data,
'code': 0
}, status=status.HTTP_200_OK)
class AddTeam(APIView):
"""新增团队"""
def post(self, request, *args, **kwargs):
name = request.data.get('name')
team_type = request.data.get('team_type', 'team') # 默认为团队类型
description = request.data.get('description', '')
if not name:
return Response({'status': 'error', 'message': '团队名称不能为空', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
# 验证团队类型
if team_type not in ['personal', 'team']:
return Response({'status': 'error', 'message': '团队类型无效必须是personal个人团队或team团队', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
# 检查团队名称是否已存在
existing_team = Team.objects.filter(name=name, is_deleted=False).first()
if existing_team:
return Response({'status': 'error', 'message': '团队名称已存在', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
# 创建团队
team = Team.objects.create(name=name, team_type=team_type, description=description)
# 记录操作日志
new_data = {
'id': team.id,
'name': team.name,
'team_type': team.team_type,
'description': team.description
}
log_operation(
request=request,
operation_type='CREATE',
module='User',
action='新增团队',
target_type='Team',
target_id=team.id,
target_name=team.name,
new_data=new_data,
remark=f'新增团队:{team.name}(类型:{team.get_team_type_display()}'
)
return Response({'message': '添加团队成功', 'code': 0}, status=status.HTTP_200_OK)
class EditTeam(APIView):
"""编辑团队"""
def post(self, request, *args, **kwargs):
id = request.data.get('id')
name = request.data.get('name')
team_type = request.data.get('team_type')
description = request.data.get('description', '')
if not all([id, name]):
return Response({'status': 'error', 'message': '缺少参数', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
try:
team = Team.objects.get(id=id, is_deleted=False)
except Team.DoesNotExist:
return Response({'status': 'error', 'message': '团队不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
# 验证团队类型
if team_type and team_type not in ['personal', 'team']:
return Response({'status': 'error', 'message': '团队类型无效必须是personal个人团队或team团队', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
# 检查新名称是否与其他团队冲突
if name != team.name:
existing_team = Team.objects.filter(name=name, is_deleted=False).exclude(id=id).first()
if existing_team:
return Response({'status': 'error', 'message': '团队名称已存在', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
# 记录操作前的数据
old_data = {
'id': team.id,
'name': team.name,
'team_type': team.team_type,
'description': team.description
}
# 更新团队信息
team.name = name
if team_type:
team.team_type = team_type
team.description = description
team.save()
# 记录操作后的数据
new_data = {
'id': team.id,
'name': team.name,
'team_type': team.team_type,
'description': team.description
}
# 记录操作日志
log_operation(
request=request,
operation_type='UPDATE',
module='User',
action='编辑团队',
target_type='Team',
target_id=team.id,
target_name=team.name,
old_data=old_data,
new_data=new_data,
remark=f'编辑团队:{team.name}(类型:{team.get_team_type_display()}'
)
return Response({'message': '修改成功', 'code': 0}, status=status.HTTP_200_OK)
class DeleteTeam(APIView):
"""删除团队"""
def post(self, request, *args, **kwargs):
id = request.data.get('id')
if not id:
return Response({'status': 'error', 'message': '缺少参数', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
try:
team = Team.objects.get(id=id, is_deleted=False)
except Team.DoesNotExist:
return Response({'status': 'error', 'message': '团队不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
# 检查是否有用户属于该团队
user_count = User.objects.filter(team=team.name, is_deleted=False).count()
if user_count > 0:
return Response({
'status': 'error',
'message': f'删除失败,该团队下还有{user_count}名人员,请先转移人员',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 记录操作前的数据
old_data = {
'id': team.id,
'name': team.name,
'team_type': team.team_type,
'description': team.description
}
# 软删除:更新 is_deleted 字段
team.is_deleted = True
team.save()
# 记录操作日志
log_operation(
request=request,
operation_type='DELETE',
module='User',
action='删除团队',
target_type='Team',
target_id=team.id,
target_name=team.name,
old_data=old_data,
remark=f'删除团队:{team.name}'
)
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
class ApprovalStatusCheck(APIView):
"""查询待办是否已经审核完全通过"""
def post(self, request, *args, **kwargs):
"""
查询审批的审核状态,判断是否已经审核完全通过
对于"待办"类型:审核通过后(状态为"已抄送财务")即为已完成,不需要等待财务查看
对于其他类型:财务部查看时,自动将状态从"已抄送财务"更新为"已通过",并确保业务记录状态也为"已通过"
:param request:
:param args:
:param kwargs:
:return:
"""
type = request.data.get('type')
id = request.data.get('id')
token = request.META.get('token')
if not all([type, id]):
return Response({'status': 'error', 'message': '缺少参数type或id', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
try:
approval = Approval.objects.get(id=id, is_deleted=False)
except Approval.DoesNotExist:
return Response({'status': 'error', 'message': '审批记录不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
# 获取当前用户信息,判断是否是财务部人员或申请人(投标/立项待查看)
is_finance_view = False
is_applicant_view = False
# 对于"待办"类型,审核通过后(已抄送财务)即为已完成,不需要等待财务查看
if token and type != "待办":
try:
current_user = User.objects.prefetch_related('department').get(token=token, is_deleted=False)
user_departments = current_user.department.values_list('username', flat=True)
user_department_names = list(user_departments)
# 判断是否是财务部人员:包含"财务"关键词的部门
is_finance_user = any('财务' in name for name in user_department_names)
# 如果是财务部人员,且当前审批状态是"已抄送财务",则自动标记为已查看(已通过)
if is_finance_user and is_finance_personincharge(approval.personincharge) and approval.state == "已抄送财务":
is_finance_view = True
approval.state = "已通过"
approval.save(update_fields=['state'])
# 投标/立项/案件变更/结案申请:当前为「待查看」且当前用户是申请人时,调用本接口即视为申请人已查看,消除待查看状态
elif type in ("投标登记", "立项登记", "案件变更", "结案申请") and approval.state == "待查看" and getattr(approval, 'applicant', None) and approval.personincharge == approval.applicant and current_user.username == approval.applicant:
is_applicant_view = True
approval.state = "已通过"
update_fields = ['state', 'content']
if type == "立项登记" and not getattr(approval, 'completeTiem', None):
from datetime import date
approval.completeTiem = date.today() # 审批通过时间,用作案件立案时间
update_fields.append('completeTiem')
if "申请人已查看" not in (approval.content or ""):
approval.content = (approval.content or "") + ",申请人已查看"
approval.save(update_fields=update_fields)
except User.DoesNotExist:
pass
# 获取业务记录状态
business_state = None
is_approved = False
try:
if type == "收入确认":
from finance.models import Income
try:
income = Income.objects.get(id=approval.user_id, is_deleted=False)
business_state = income.state
is_approved = (income.state == "已通过")
# 如果财务查看,确保业务记录状态为"已通过"(财务查看即完成)
if is_finance_view and income.state != "已通过":
income.state = "已通过"
income.save(update_fields=['state'])
business_state = "已通过"
is_approved = True
except Income.DoesNotExist:
pass
elif type == "开票":
from finance.models import Invoice
try:
invoice = Invoice.objects.get(id=approval.user_id, is_deleted=False)
business_state = invoice.state
is_approved = (invoice.state == "已通过")
# 如果财务查看,且业务记录状态还不是"已通过",更新业务记录状态
if is_finance_view and invoice.state != "已通过":
invoice.state = "已通过"
invoice.save(update_fields=['state'])
business_state = "已通过"
is_approved = True
except Invoice.DoesNotExist:
pass
elif type == "付款申请":
from finance.models import Payment
try:
payment = Payment.objects.get(id=approval.user_id, is_deleted=False)
business_state = payment.state
is_approved = (payment.state == "已通过")
# 如果财务查看,且业务记录状态还不是"已通过",更新业务记录状态
if is_finance_view and payment.state != "已通过":
payment.state = "已通过"
payment.save(update_fields=['state'])
business_state = "已通过"
is_approved = True
except Payment.DoesNotExist:
pass
elif type == "报销申请":
from finance.models import Reimbursement
try:
reimbursement = Reimbursement.objects.get(id=approval.user_id, is_deleted=False)
business_state = reimbursement.state
is_approved = (reimbursement.state == "已完成")
# 如果财务查看,且业务记录状态还不是"已通过",更新业务记录状态
if is_finance_view and reimbursement.state != "已通过":
reimbursement.state = "已通过"
reimbursement.save(update_fields=['state'])
business_state = "已通过"
is_approved = True
except Reimbursement.DoesNotExist:
pass
elif type == "案件管理":
from business.models import Case
try:
case = Case.objects.get(id=approval.user_id, is_deleted=False)
business_state = case.state
is_approved = (case.state == "已通过")
# 如果财务查看,且业务记录状态还不是"已通过",更新业务记录状态
if is_finance_view and case.state != "已通过":
case.state = "已通过"
case.save(update_fields=['state'])
business_state = "已通过"
is_approved = True
except Case.DoesNotExist:
pass
elif type == "申请用印":
from business.models import SealApplication
try:
seal_app = SealApplication.objects.get(id=approval.user_id, is_deleted=False)
business_state = seal_app.state
is_approved = (seal_app.state == "已通过")
# 如果财务查看,且业务记录状态还不是"已通过",更新业务记录状态
if is_finance_view and seal_app.state != "已通过":
seal_app.state = "已通过"
seal_app.save(update_fields=['state'])
business_state = "已通过"
is_approved = True
except SealApplication.DoesNotExist:
pass
elif type == "投标登记":
try:
bid = Bid.objects.get(id=approval.user_id, is_deleted=False)
business_state = bid.state
is_approved = (bid.state == "已通过")
# 申请人通过本接口查看后,消除待查看并更新业务记录为已通过
if is_applicant_view and bid.state != "已通过":
bid.state = "已通过"
bid.save(update_fields=['state'])
business_state = "已通过"
is_approved = True
except Bid.DoesNotExist:
pass
elif type == "立项登记":
try:
project = ProjectRegistration.objects.get(id=approval.user_id, is_deleted=False)
business_state = project.state
is_approved = (project.state == "已通过")
if is_applicant_view and project.state != "已通过":
project.state = "已通过"
project.save(update_fields=['state'])
business_state = "已通过"
is_approved = True
# 立项审批通过后,自动在案件管理中创建案件
from User.utils import ensure_case_for_approved_project
ensure_case_for_approved_project(project, approval=approval)
except ProjectRegistration.DoesNotExist:
pass
elif type == "案件变更":
try:
change_request = CaseChangeRequest.objects.get(id=approval.user_id, is_deleted=False)
business_state = change_request.state
is_approved = (change_request.state == "已通过")
# 申请人通过本接口查看后,消除待查看并更新变更申请、关联案件状态
if is_applicant_view and change_request.state != "已通过":
change_request.state = "已通过"
change_request.save(update_fields=['state'])
business_state = "已通过"
is_approved = True
if change_request.case_id:
change_request.case.state = "已通过"
change_request.case.save(update_fields=['state'])
except CaseChangeRequest.DoesNotExist:
pass
elif type == "待办":
# 待办类型:审核通过后(已抄送财务或已通过)即为已完成,不需要等待财务查看
from business.models import Schedule
try:
schedule = Schedule.objects.get(id=approval.user_id, is_deleted=False)
business_state = schedule.state
# 对于待办类型,如果审批状态是"已抄送财务"或"已通过",则视为已完成
if approval.state == "已抄送财务" or approval.state == "已通过":
is_approved = True
# 确保业务记录状态也为"已完成"
if schedule.state != "已完成":
schedule.state = "已完成"
schedule.save(update_fields=['state'])
business_state = "已完成"
else:
is_approved = (schedule.state == "已完成")
except Schedule.DoesNotExist:
pass
elif type == "结案申请":
# 结案申请类型:使用 Schedule 作为承载对象
from business.models import Schedule, Case
try:
schedule = Schedule.objects.get(id=approval.user_id, is_deleted=False)
business_state = schedule.state
is_approved = (schedule.state == "已完成" or approval.state == "已通过")
# 申请人通过本接口查看后,消除待查看并更新业务记录为已完成,同时更新 Case.Closingapplication
if is_applicant_view:
if schedule.state != "已完成":
schedule.state = "已完成"
schedule.save(update_fields=['state'])
business_state = "已完成"
is_approved = True
# 更新 Case.Closingapplication 字段
try:
import json
remark_data = json.loads(schedule.remark) if schedule.remark else {}
case_id = remark_data.get('case_id')
closing_files = remark_data.get('closing_application_files', [])
if case_id and closing_files:
case = Case.objects.filter(id=case_id, is_deleted=False).first()
if case:
case.Closingapplication = json.dumps(closing_files, ensure_ascii=False)
case.save(update_fields=['Closingapplication'])
except Exception as e:
import logging
logger = logging.getLogger(__name__)
logger.error(f"结案申请待查看确认后更新 Case.Closingapplication 失败: {str(e)}")
except Schedule.DoesNotExist:
pass
# 可以根据需要添加其他类型
except Exception as e:
import logging
logger = logging.getLogger(__name__)
logger.error(f"查询业务记录状态失败: {str(e)}")
return Response({
'message': '查询成功',
'code': 0,
'data': {
'approval_id': approval.id,
'approval_state': approval.state,
'business_state': business_state,
'is_approved': is_approved, # 是否已经审核完全通过
'type': type,
'auto_marked_as_viewed': is_finance_view or is_applicant_view # 财务查看或申请人查看时已自动标记为已通过
}
}, status=status.HTTP_200_OK)
class ChangePasswordView(APIView):
"""修改密码接口"""
def post(self, request, *args, **kwargs):
"""
修改密码
权限规则:
- 超级管理员admin可以修改任何用户的密码不需要原密码
- 非超级管理员:只能修改自己的密码(需要验证原密码)
"""
token = request.META.get('token')
user_id = request.data.get('user_id') # 要修改密码的用户ID可选超级管理员使用
old_password = request.data.get('old_password') # 原密码(非超级管理员必填)
new_password = request.data.get('new_password') # 新密码
# 参数验证
if not new_password:
return Response({'status': 'error', 'message': '新密码不能为空', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
# 获取当前登录用户
try:
current_user = User.objects.get(token=token, is_deleted=False)
except User.DoesNotExist:
return Response({'status': 'error', 'message': '用户不存在或已被删除', 'code': 1},
status=status.HTTP_401_UNAUTHORIZED)
# 判断是否为超级管理员
is_admin = (current_user.username == 'admin' or current_user.account == 'admin')
# 确定要修改密码的目标用户
if is_admin:
# 超级管理员可以修改任何用户的密码
if user_id:
try:
target_user = User.objects.get(id=user_id, is_deleted=False)
except User.DoesNotExist:
return Response({'status': 'error', 'message': '目标用户不存在或已被删除', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
else:
# 如果没有指定user_id默认修改自己的密码
target_user = current_user
# 超级管理员修改密码不需要原密码
else:
# 非超级管理员只能修改自己的密码
target_user = current_user
# 必须提供原密码
if not old_password:
return Response({'status': 'error', 'message': '原密码不能为空', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
# 验证原密码
if target_user.password != old_password:
return Response({'status': 'error', 'message': '原密码错误', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
# 记录操作前的数据(不包含密码)
old_data = {
'id': target_user.id,
'username': target_user.username,
'account': target_user.account
}
# 更新密码
target_user.password = new_password
target_user.save(update_fields=['password'])
# 记录操作后的数据
new_data = {
'id': target_user.id,
'username': target_user.username,
'account': target_user.account
}
# 记录操作日志
action_desc = '修改密码' if target_user.id == current_user.id else f'修改用户{target_user.username}的密码'
log_operation(
request=request,
operation_type='UPDATE',
module='User',
action=action_desc,
target_type='User',
target_id=target_user.id,
target_name=target_user.username,
old_data=old_data,
new_data=new_data,
remark=f'{current_user.username} {action_desc}'
)
return Response({'message': '密码修改成功', 'code': 0}, status=status.HTTP_200_OK)