Files
jyls_django/User/utils.py
ddrwode 78e9987123 haha
2026-01-12 23:17:39 +08:00

574 lines
21 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

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

"""
审批相关的工具函数
"""
import json
from datetime import datetime
from .models import OperationLog, User, Team, Approval
def is_department_id(value):
"""
判断personincharge字段的值是部门ID还是审批员用户名
统一规则:
- 如果是纯数字字符串(如 "1", "2", "123"表示部门ID
- 如果包含非数字字符(如 "张三", "李四"),表示审批员用户名
Args:
value: personincharge字段的值字符串
Returns:
bool: True表示是部门IDFalse表示是审批员用户名
示例:
>>> is_department_id("1")
True
>>> is_department_id("123")
True
>>> is_department_id("张三")
False
>>> is_department_id("dept:1")
False
"""
if not value:
return False
# 判断是否为纯数字字符串(去除首尾空格)
return str(value).strip().isdigit()
def format_personincharge(value, is_department=False):
"""
格式化personincharge字段的值
统一规则:
- 如果是部门ID确保是纯数字字符串
- 如果是审批员用户名,保持原样
Args:
value: 部门ID整数或字符串或审批员用户名字符串
is_department: 是否为部门ID默认False审批员用户名
Returns:
str: 格式化后的personincharge值
"""
if not value:
return ''
if is_department:
# 部门ID转换为字符串确保是纯数字
try:
return str(int(value))
except (ValueError, TypeError):
raise ValueError(f"部门ID必须是数字当前值: {value}")
else:
# 审批员用户名:保持原样,但确保是字符串
return str(value).strip()
def log_operation(request, operation_type, module, action, target_type, target_id=None,
target_name=None, old_data=None, new_data=None, remark=None):
"""
记录操作日志
Args:
request: Django request对象
operation_type: 操作类型DELETE, CREATE, UPDATE, APPROVE等
module: 模块名称User, Business, Finance等
action: 操作描述(如"删除用户""创建立项"等)
target_type: 目标类型如User, ProjectRegistration等
target_id: 目标ID
target_name: 目标名称(如用户名、项目名等)
old_data: 操作前的数据字典会自动转换为JSON
new_data: 操作后的数据字典会自动转换为JSON
remark: 备注信息
Returns:
OperationLog对象
"""
try:
# 获取操作人信息
token = request.META.get('token') or request.META.get('HTTP_AUTHORIZATION', '').replace('Bearer ', '')
operator = '未知用户'
operator_id = None
if token:
try:
user = User.objects.get(token=token, is_deleted=False)
operator = user.username
operator_id = user.id
except User.DoesNotExist:
pass
# 获取IP地址
ip_address = request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')[0].strip()
if not ip_address:
ip_address = request.META.get('REMOTE_ADDR', '')
# 获取用户代理
user_agent = request.META.get('HTTP_USER_AGENT', '')
# 转换数据为JSON字符串
old_data_str = json.dumps(old_data, ensure_ascii=False) if old_data else None
new_data_str = json.dumps(new_data, ensure_ascii=False) if new_data else None
# 创建日志记录
log = OperationLog.objects.create(
operator=operator,
operator_id=operator_id,
operation_type=operation_type,
module=module,
action=action,
target_type=target_type,
target_id=str(target_id) if target_id else None,
target_name=target_name,
old_data=old_data_str,
new_data=new_data_str,
ip_address=ip_address,
user_agent=user_agent,
request_path=request.path,
remark=remark
)
return log
except Exception as e:
# 日志记录失败不应该影响主业务流程
import logging
logger = logging.getLogger(__name__)
logger.error(f"记录操作日志失败: {str(e)}")
return None
def create_team_approval(team_name, title, content, approval_type, user_id, request=None):
"""
根据团队类型创建审批流程
规则:
- 个人团队personal不需要审核人直接抄送财务personincharge设为"财务"
- 团队team需要指定审核人可以多个审核通过后才抄送财务
Args:
team_name: 团队名称
title: 审批标题
content: 审批内容
approval_type: 审批类型(如"立项登记""付款申请"等)
user_id: 关联的业务ID字符串
request: Django request对象可选用于记录日志
Returns:
Approval对象或None
"""
try:
# 查找团队
team = Team.objects.prefetch_related('approvers').filter(name=team_name, is_deleted=False).first()
if not team:
# 如果找不到团队,使用默认流程(需要审核人)
return None
today = datetime.now().strftime("%Y-%m-%d")
if team.team_type == 'personal':
# 个人团队:直接抄送财务,不需要审核人
approval = Approval.objects.create(
title=title,
content=content,
times=today,
personincharge='财务', # 直接抄送财务
state='已抄送财务', # 直接标记为已抄送财务
type=approval_type,
user_id=str(user_id)
)
return approval
elif team.team_type == 'team':
# 团队:需要审核人审核
approvers = team.approvers.filter(is_deleted=False)
if not approvers.exists():
# 如果没有审核人返回None应该在前端验证
return None
# 将审核人用户名用逗号连接(多个审核人)
approver_names = ','.join([approver.username for approver in approvers])
approval = Approval.objects.create(
title=title,
content=content,
times=today,
personincharge=approver_names, # 多个审核人用逗号分隔
state='审核中',
type=approval_type,
user_id=str(user_id)
)
return approval
return None
except Exception as e:
import logging
logger = logging.getLogger(__name__)
logger.error(f"创建团队审批失败: {str(e)}")
return None
def get_team_approval_info(team_name):
"""
获取团队的审批信息
Args:
team_name: 团队名称
Returns:
dict: {
'team_type': 'personal''team',
'needs_approval': True/False, # 是否需要审核
'approvers': [{'id': 1, 'username': '张三'}, ...], # 审核人列表
'direct_to_finance': True/False # 是否直接抄送财务
}
"""
try:
team = Team.objects.prefetch_related('approvers').filter(name=team_name, is_deleted=False).first()
if not team:
return {
'team_type': None,
'needs_approval': True, # 默认需要审核
'approvers': [],
'direct_to_finance': False
}
approvers = list(team.approvers.filter(is_deleted=False).values('id', 'username'))
return {
'team_type': team.team_type,
'needs_approval': team.team_type == 'team',
'approvers': approvers,
'direct_to_finance': team.team_type == 'personal'
}
except Exception as e:
import logging
logger = logging.getLogger(__name__)
logger.error(f"获取团队审批信息失败: {str(e)}")
return {
'team_type': None,
'needs_approval': True,
'approvers': [],
'direct_to_finance': False
}
def parse_approvers(approvers):
"""
解析审核人列表,统一处理数组和字符串格式
支持用户ID列表推荐和用户名列表兼容旧接口
Args:
approvers: 审核人列表,可以是:
- 数组格式ID列表推荐[1, 2, 3] 或 ["1", "2", "3"]
- 数组格式(用户名列表,兼容):["张三", "李四", "王五"]
- 字符串格式逗号分隔的ID"1,2,3"
- 字符串格式(逗号分隔的用户名,兼容):"张三,李四,王五"
- None: 返回空列表
Returns:
list: 审核人用户名列表,如 ["张三", "李四", "王五"]
"""
if not approvers:
return []
approvers_list = []
if isinstance(approvers, str):
# 字符串格式:逗号分隔
approvers_list = [a.strip() for a in approvers.split(',') if a.strip()]
elif isinstance(approvers, list):
# 数组格式(推荐)
approvers_list = [str(a).strip() for a in approvers if a]
else:
return []
if not approvers_list:
return []
# 判断是ID还是用户名如果第一个元素是纯数字则认为是ID列表
# 否则认为是用户名列表(兼容旧接口)
first_item = approvers_list[0]
is_id_list = str(first_item).strip().isdigit()
if is_id_list:
# ID列表转换为用户名列表
try:
user_ids = [int(uid) for uid in approvers_list]
users = User.objects.filter(id__in=user_ids, is_deleted=False)
# 保持原有顺序
user_dict = {user.id: user.username for user in users}
username_list = [user_dict.get(uid, None) for uid in user_ids]
# 过滤掉不存在的用户
username_list = [name for name in username_list if name is not None]
return username_list
except (ValueError, TypeError):
# 如果转换失败,返回空列表
return []
else:
# 用户名列表(兼容旧接口):直接返回
return approvers_list
def get_approvers_from_record(business_record):
"""
从业务记录中获取审核人列表(统一方法)
优先从 approvers_order 字段读取JSON格式
如果没有则从 Approval.content 字段解析(兼容旧数据)
Args:
business_record: 业务记录对象(如 Schedule, Reimbursement, Income 等)
Returns:
list: 审核人列表,如 ["张三", "李四", "王五"],如果没有则返回空列表
"""
import json
# 优先从 approvers_order 字段读取
if hasattr(business_record, 'approvers_order') and business_record.approvers_order:
try:
approvers_list = json.loads(business_record.approvers_order)
if isinstance(approvers_list, list):
return approvers_list
except (json.JSONDecodeError, TypeError):
pass
# 兼容旧数据:从 Approval.content 字段解析
# 格式:审批流程:张三 → 李四 → 王五 → 财务(按顺序审批),当前审批人:张三
return []
def process_approval_flow(approval, business_record, current_approver, state,
approval_type, final_state_map=None):
"""
统一的审核流程处理函数
规则:
- 如果 business_record 有 approvers_order 字段,从该字段读取审核人列表
- 如果没有审核人列表(个人团队),直接抄送财务
- 如果有审核人列表(多人团队),按顺序流转
- 最后一个审核人通过后,抄送财务
- 财务审核通过后,完成审批
Args:
approval: Approval对象
business_record: 业务记录对象(如 Schedule, Reimbursement, Income 等)
current_approver: 当前审核人(从 approval.personincharge 获取)
state: 审核状态("已通过""未通过"
approval_type: 审批类型(如"待办""报销申请"等)
final_state_map: 状态映射字典,格式:{"已通过": "已完成", "未通过": "未通过"}
Returns:
tuple: (是否完成, 错误信息)
"""
import json
from .models import Approval
if final_state_map is None:
final_state_map = {"已通过": "已完成", "未通过": "未通过"}
# 如果审核不通过,直接结束
if state == "未通过":
approval.state = "未通过"
approval.save(update_fields=['state'])
if business_record and hasattr(business_record, 'state'):
business_record.state = final_state_map.get("未通过", "未通过")
business_record.save(update_fields=['state'])
return True, None
# 检查当前是否已经是财务审核
if approval.personincharge == "财务" and approval.state == "已抄送财务":
# 财务部审核逻辑:财务部只需要一个人审核完即可完成
if state == "已通过":
approval.state = "已通过"
approval.save(update_fields=['state'])
if business_record and hasattr(business_record, 'state'):
business_record.state = final_state_map.get("已通过", "已完成")
business_record.save(update_fields=['state'])
return True, None
# 获取审核人列表
approvers_list = get_approvers_from_record(business_record)
if not approvers_list:
# 没有审核人列表(个人团队或直接到财务)
# 如果当前不是财务,则抄送财务
if approval.personincharge != "财务":
approval.personincharge = "财务"
approval.state = "已抄送财务"
if "已抄送财务" not in approval.content:
approval.content = approval.content + ",已抄送财务"
approval.save(update_fields=['state', 'personincharge', 'content'])
if business_record and hasattr(business_record, 'state'):
business_record.state = "待财务处理"
business_record.save(update_fields=['state'])
return False, None
# 有审核人列表(多人团队),按顺序流转
try:
current_index = approvers_list.index(current_approver)
except ValueError:
# 当前审核人不在列表中,可能是旧数据,直接抄送财务
approval.personincharge = "财务"
approval.state = "已抄送财务"
approval.save(update_fields=['state', 'personincharge'])
return False, None
# 检查是否还有下一个审核人
if current_index < len(approvers_list) - 1:
# 不是最后一个审核人,流转到下一个
next_approver = approvers_list[current_index + 1]
approval.personincharge = next_approver
approval.state = "审核中"
# 更新审批内容,显示当前审批人
if "当前审批人:" in approval.content:
approval.content = approval.content.replace(
f"当前审批人:{current_approver}",
f"当前审批人:{next_approver}"
)
approval.save(update_fields=['state', 'personincharge', 'content'])
return False, None
else:
# 最后一个审核人,抄送财务
approval.personincharge = "财务"
approval.state = "已抄送财务"
if "已抄送财务" not in approval.content:
approval.content = approval.content + ",已抄送财务"
approval.save(update_fields=['state', 'personincharge', 'content'])
if business_record and hasattr(business_record, 'state'):
business_record.state = "待财务处理"
business_record.save(update_fields=['state'])
return False, None
def create_approval_with_team_logic(team_name, approvers, title, content, approval_type, user_id,
business_record=None, today=None):
"""
根据团队类型创建审批记录(统一逻辑)
规则:
- 个人团队personal直接抄送财务personincharge="财务"state="已抄送财务"
- 团队team需要审核人按顺序审核最后抄送财务
- 无团队:直接抄送财务
Args:
team_name: 团队名称
approvers: 审核人列表(可以是数组或字符串,多人团队时需要)
- 推荐格式用户ID列表如 [1, 2, 3] 或 ["1", "2", "3"]
- 兼容格式:用户名列表,如 ["张三", "李四", "王五"]
- 字符串格式逗号分隔的ID或用户名"1,2,3""张三,李四,王五"
title: 审批标题
content: 审批内容
approval_type: 审批类型(如"立项登记""付款申请"等)
user_id: 关联的业务ID字符串
business_record: 业务记录对象可选用于更新状态和存储approvers_order
today: 日期字符串可选格式YYYY-MM-DD
Returns:
tuple: (approval对象, approvers_order_json, 是否需要审核)
"""
import json
from datetime import datetime
from .models import Team, Approval, User
if today is None:
today = datetime.now().strftime("%Y-%m-%d")
# 查找团队
team = None
if team_name:
try:
team = Team.objects.prefetch_related('approvers').filter(name=team_name, is_deleted=False).first()
except:
team = None
# 判断团队类型
if not team_name or not team or (team and team.team_type == 'personal'):
# 个人团队或无团队:直接到财务团队审核
approval = Approval.objects.create(
title=title,
content=content,
times=today,
personincharge="财务",
state="已抄送财务",
type=approval_type,
user_id=str(user_id)
)
# 更新业务记录状态
if business_record:
business_record.state = "待财务处理"
business_record.save(update_fields=['state'])
return approval, None, False
elif team and team.team_type == 'team':
# 团队类型:需要审核人审核(按顺序)
approvers_list = parse_approvers(approvers)
# 如果没有传入审核人,使用团队的审核人
if not approvers_list:
approvers_list = [approver.username for approver in team.approvers.filter(is_deleted=False).order_by('id')]
if not approvers_list:
return None, None, True # 需要审核但没有审核人
# 验证审核人是否存在
for approver_name in approvers_list:
if not User.objects.filter(username=approver_name, is_deleted=False).exists():
return None, None, True # 审核人不存在
# 将审核人顺序存储为JSON格式
approvers_order_json = json.dumps(approvers_list, ensure_ascii=False)
# 存储到业务记录
if business_record and hasattr(business_record, 'approvers_order'):
business_record.approvers_order = approvers_order_json
business_record.state = "审核中"
business_record.save(update_fields=['approvers_order', 'state'])
# 创建审批记录,第一个审核人
first_approver = approvers_list[0]
approvers_str = ''.join(approvers_list) # 使用箭头表示顺序
content_with_flow = f"{content},审批流程:{approvers_str} → 财务(按顺序审批),当前审批人:{first_approver}"
approval = Approval.objects.create(
title=title,
content=content_with_flow,
times=today,
personincharge=first_approver,
state="审核中",
type=approval_type,
user_id=str(user_id)
)
return approval, approvers_order_json, True
else:
# 找不到团队或团队类型未知,直接抄送财务
approval = Approval.objects.create(
title=title,
content=content,
times=today,
personincharge="财务",
state="已抄送财务",
type=approval_type,
user_id=str(user_id)
)
# 更新业务记录状态
if business_record:
business_record.state = "待财务处理"
business_record.save(update_fields=['state'])
return approval, None, False