3136 lines
158 KiB
Python
3136 lines
158 KiB
Python
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)
|