Files
jyls_django/finance/views.py
2026-02-01 17:36:37 +08:00

3979 lines
166 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

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

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
import json
import ast
from User.models import User, Approval, Department
from User.utils import log_operation, normalize_approvers_param, build_missing_approvers_message
import datetime
from .models import Invoice, Income, Accounts, Payment, Reimbursement, BonusChange
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 business.models import PreFiling, Case, ProjectRegistration
class UserRegister(APIView):
def post(self, request, *args, **kwargs):
"""
入职财务登记
优化后:只需填写姓名、入职时间、工资三个字段
其他数据(身份证、岗位等)从人事管理同步
:param request:
:param args:
:param kwargs:
:return:
"""
username = request.data.get('username')
Dateofjoining = request.data.get('Dateofjoining')
salary = request.data.get('salary')
# 以下字段改为可选,如果未提供则从人事管理同步
card = request.data.get('card') # 身份证(可选)
position = request.data.get('position') # 岗位(可选)
approvers = request.data.get('approvers') # 审核人列表可选多人团队时需要推荐用户ID数组如[1,2,3],兼容:用户名数组)
# 兼容旧接口:如果传了 personincharge转换为 approvers
personincharge = request.data.get('personincharge')
approvers = normalize_approvers_param(approvers, personincharge)
# 只验证必填字段:姓名、入职时间、工资
if not all([username, Dateofjoining, salary]):
missing_params = []
if not username:
missing_params.append('username(姓名)')
if not Dateofjoining:
missing_params.append('Dateofjoining(入职时间)')
if not salary:
missing_params.append('salary(工资)')
return Response({
'status': 'error',
'message': f'缺少必填参数: {", ".join(missing_params)}',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 验证日期格式
try:
Dateofjoinings = 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)
# 查找用户(必须已存在于人事管理中)
try:
user = User.objects.get(username=username, is_deleted=False)
except User.DoesNotExist:
return Response({
'status': 'error',
'message': '用户不存在,请先在人事管理中创建该用户',
'code': 1
}, status=status.HTTP_404_NOT_FOUND)
# 从人事管理同步其他数据(如果入职登记未提供)
# 如果提供了card或position则使用提供的值否则使用人事管理中的值
# 团队信息始终从人事管理中获取,不在财务登记中修改
update_fields = []
# 更新必填字段
user.Dateofjoining = Dateofjoinings
user.salary = salary
update_fields.extend(['Dateofjoining', 'salary'])
# 处理可选字段:如果提供了则更新,否则保持人事管理中的值
if card:
user.card = card
update_fields.append('card')
# 如果未提供card使用人事管理中的card已存在无需更新
if position:
user.position = position
update_fields.append('position')
# 如果未提供position使用人事管理中的position已存在无需更新
# 更新状态:提交入职登记后,状态设置为"审核中",立即加入到入职列表中
user.state = "审核中"
update_fields.append('state')
# 保存用户信息
user.save(update_fields=update_fields)
# 从人事管理中获取团队信息(团队信息不在财务登记中修改)
team_name = user.team # 用户的团队名称(从人事管理中获取)
team = None
team_type_display = None
if team_name:
try:
from User.models import Team
team = Team.objects.get(name=team_name, is_deleted=False)
team_type_display = team.get_team_type_display()
except Team.DoesNotExist:
# 如果团队不存在,默认按团队类型处理(需要审批)
pass
# 构建审批内容(使用实际的值,包括从人事管理同步的)
actual_card = card if card else (user.card if user.card else '未填写')
actual_position = position if position else (user.position if user.position else '未填写')
actual_team = team_name if team_name else '未分配团队'
today = datetime.datetime.now()
formatted_date = today.strftime("%Y-%m-%d")
# 根据团队类型判断是否需要审批,使用统一的审核流程函数
from User.utils import create_approval_with_team_logic, parse_approvers
# 构建审批内容
content_parts = [
f"{username}{Dateofjoining}办理入职",
f"身份证:{actual_card}",
f"岗位:{actual_position}",
f"团队:{actual_team}"
]
if team_type_display:
content_parts.append(f"团队类型:{team_type_display}")
content_parts.append(f"薪资:{salary}")
content = "".join(content_parts)
# 使用统一的审核流程函数
try:
approval, approvers_order_json, needs_approval = create_approval_with_team_logic(
team_name=team_name,
approvers=approvers,
title=username + "入职财务登记",
content=content,
approval_type="入职财务登记",
user_id=str(user.id),
business_record=user, # User对象没有approvers_order字段但可以更新state
today=formatted_date
)
except Exception as e:
# 记录异常并返回错误信息
import logging
logger = logging.getLogger(__name__)
logger.error(f"创建入职财务登记审批失败: {str(e)}", exc_info=True)
return Response({
'status': 'error',
'message': f'创建审批流程失败: {str(e)},请检查团队配置和审核人信息',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 如果返回None且需要审核说明缺少审核人
if approval is None and needs_approval:
return Response({
'status': 'error',
'message': build_missing_approvers_message(team_name, approvers),
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 记录操作日志(包含从人事管理获取的团队信息)
new_data = {
'user_id': user.id,
'username': user.username,
'card': user.card,
'position': user.position,
'team': team_name, # 从人事管理中获取的团队名称
'team_type': team_type_display if team else None, # 团队类型显示名称
'salary': user.salary,
'Dateofjoining': Dateofjoining,
'state': user.state
}
log_operation(
request=request,
operation_type='CREATE',
module='Finance',
action='新增入职财务登记',
target_type='User',
target_id=user.id,
target_name=user.username,
new_data=new_data,
remark=f'新增入职财务登记:{user.username},入职时间 {Dateofjoining},薪资 {salary},团队 {team_name if team_name else "未分配"}'
)
return Response({
'message': '登记成功',
'code': 0,
'data': {
'username': user.username,
'Dateofjoining': Dateofjoining,
'salary': salary,
'card': user.card, # 返回实际使用的身份证(从人事管理同步)
'position': user.position, # 返回实际使用的岗位(从人事管理同步)
'team': team_name, # 返回团队名称(从人事管理中获取)
'team_type': team_type_display if team else None, # 返回团队类型(从人事管理中获取)
}
}, status=status.HTTP_200_OK)
class RegisteredUserList(APIView):
"""查询已入职登记的用户列表"""
def post(self, request, *args, **kwargs):
"""
查询已完成入职财务登记的用户
:param request:
:param args:
:param kwargs:
:return:
"""
page = request.data.get('page', 1)
per_page = request.data.get('per_page', 10)
username = request.data.get('username', '') # 用户名搜索
department = request.data.get('department', '') # 部门搜索
if not all([page, per_page]):
return Response({
'status': 'error',
'message': '缺少参数:页码和每页数量不能为空',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 获取所有已提交入职登记的用户ID状态为"审核中"、"已抄送财务"或"已通过"的入职财务登记)
# 提交入职登记后,立即加入到入职列表中,状态为"审核中"
registration_user_ids = Approval.objects.filter(
type="入职财务登记",
state__in=["审核中", "已抄送财务", "已通过"],
is_deleted=False
).values_list('user_id', flat=True)
# 将user_id转换为整数列表因为user_id是CharField存储的字符串
# 同时验证用户是否被软删除只保留未删除的用户ID
registered_user_ids = []
for user_id_str in registration_user_ids:
try:
user_id = int(user_id_str)
# 验证用户是否存在且未被软删除
if User.objects.filter(id=user_id, is_deleted=False).exists():
registered_user_ids.append(user_id)
except (ValueError, TypeError):
continue
# 查询条件:已提交入职登记的用户(包括审核中、已抄送财务、已通过)
# 只查询未软删除的用户
Q_obj = Q(is_deleted=False)
# 只查询已提交入职登记的用户
if registered_user_ids:
Q_obj &= Q(id__in=registered_user_ids)
else:
# 如果没有已提交入职登记的用户,返回空列表
return Response({
'message': '查询成功',
'total': 0,
'data': [],
'code': 0
}, status=status.HTTP_200_OK)
# 排除admin用户超级管理员
Q_obj &= ~Q(username='admin')
Q_obj &= ~Q(account='admin')
# 用户名搜索
if username:
Q_obj &= Q(username__icontains=username)
# 部门搜索
if department:
Q_obj &= Q(department__username__icontains=department)
# 查询用户
users = User.objects.prefetch_related('department', 'role').filter(Q_obj).order_by('-id')
total = users.count()
# 分页
paginator = Paginator(users, per_page)
try:
users_page = paginator.page(page)
except PageNotAnInteger:
users_page = paginator.page(1)
except EmptyPage:
users_page = paginator.page(paginator.num_pages)
# 批量获取所有用户的团队信息,优化查询性能
from User.models import Team
team_names = [user.team for user in users_page.object_list if user.team]
teams_dict = {}
if team_names:
teams = Team.objects.prefetch_related('approvers').filter(
name__in=team_names,
is_deleted=False
)
for team in teams:
teams_dict[team.name] = team
data = []
for user in users_page.object_list:
# 获取入职登记的审批记录信息(包括审核中、已抄送财务、已通过)
approval = Approval.objects.filter(
type="入职财务登记",
user_id=str(user.id),
state__in=["审核中", "已抄送财务", "已通过"],
is_deleted=False
).order_by('-id').first()
# 获取完整的团队信息
team_info = None
if user.team:
team = teams_dict.get(user.team)
if team:
team_info = {
'id': team.id,
'name': team.name,
'team_type': team.team_type,
'team_type_display': team.get_team_type_display(), # 个人团队/团队
'description': team.description,
'approvers': list(team.approvers.filter(is_deleted=False).values('id', 'username'))
}
else:
# 如果团队不存在,只返回团队名称(兼容旧数据)
team_info = {
'id': None,
'name': user.team,
'team_type': None,
'team_type_display': None,
'description': None,
'approvers': []
}
data.append({
'id': user.id,
'username': user.username,
'account': user.account,
'card': user.card,
'position': user.position,
'mobilePhone': user.mobilePhone,
'department': list(user.department.values('id', 'username')),
'role': list(user.role.values('id', 'RoleName', 'permissionId')),
'team': team_info, # 返回完整的团队信息
'Dateofjoining': user.Dateofjoining.strftime("%Y-%m-%d") if user.Dateofjoining else None,
'salary': user.salary,
'state': user.state, # 用户状态
'approval_state': approval.state if approval else None, # 审批状态:审核中、已抄送财务、已通过
'registration_date': approval.times.strftime("%Y-%m-%d") if approval and approval.times else None,
# 入职登记日期
'approval_id': approval.id if approval else None
})
return Response({
'message': '查询成功',
'total': total,
'data': data,
'code': 0
}, status=status.HTTP_200_OK)
class UnregisteredUserList(APIView):
"""查询未入职登记的用户列表"""
def post(self, request, *args, **kwargs):
"""
查询在人事管理中已创建,但还没有完成入职财务登记的用户
:param request:
:param args:
:param kwargs:
:return:
"""
page = request.data.get('page', 1)
per_page = request.data.get('per_page', 10)
username = request.data.get('username', '') # 用户名搜索
department = request.data.get('department', '') # 部门搜索
if not all([page, per_page]):
return Response({
'status': 'error',
'message': '缺少参数:页码和每页数量不能为空',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 获取所有已完成入职登记的用户ID状态为"已通过"的入职财务登记)
completed_registration_user_ids = Approval.objects.filter(
type="入职财务登记",
state="已通过",
is_deleted=False
).values_list('user_id', flat=True)
# 将user_id转换为整数列表因为user_id是CharField存储的字符串
# 同时验证用户是否被软删除只保留未删除的用户ID
completed_user_ids = []
for user_id_str in completed_registration_user_ids:
try:
user_id = int(user_id_str)
# 验证用户是否存在且未被软删除
if User.objects.filter(id=user_id, is_deleted=False).exists():
completed_user_ids.append(user_id)
except (ValueError, TypeError):
continue
# 查询条件:在人事管理中,但不在已完成入职登记的用户列表中
# 只查询未软删除的用户
Q_obj = Q(is_deleted=False)
# 排除已完成入职登记的用户
if completed_user_ids:
Q_obj &= ~Q(id__in=completed_user_ids)
# 排除admin用户超级管理员
Q_obj &= ~Q(username='admin')
Q_obj &= ~Q(account='admin')
# 用户名搜索
if username:
Q_obj &= Q(username__icontains=username)
# 部门搜索
if department:
Q_obj &= Q(department__username__icontains=department)
# 查询用户
users = User.objects.prefetch_related('department', 'role').filter(Q_obj).order_by('-id')
total = users.count()
# 分页
paginator = Paginator(users, per_page)
try:
users_page = paginator.page(page)
except PageNotAnInteger:
users_page = paginator.page(1)
except EmptyPage:
users_page = paginator.page(paginator.num_pages)
# 批量获取所有用户的团队信息,优化查询性能
from User.models import Team
team_names = [user.team for user in users_page.object_list if user.team]
teams_dict = {}
if team_names:
teams = Team.objects.prefetch_related('approvers').filter(
name__in=team_names,
is_deleted=False
)
for team in teams:
teams_dict[team.name] = team
# 批量获取所有用户的待审核入职登记,优化查询性能
user_ids = [user.id for user in users_page.object_list]
pending_approvals_dict = {}
if user_ids:
pending_approvals = Approval.objects.filter(
type="入职财务登记",
user_id__in=[str(uid) for uid in user_ids],
state="审核中",
is_deleted=False
)
for approval in pending_approvals:
try:
user_id = int(approval.user_id)
if user_id not in pending_approvals_dict:
pending_approvals_dict[user_id] = approval
except (ValueError, TypeError):
continue
data = []
for user in users_page.object_list:
# 检查是否有待审核的入职登记(状态为"审核中"
pending_approval = pending_approvals_dict.get(user.id)
# 获取完整的团队信息
team_info = None
if user.team:
team = teams_dict.get(user.team)
if team:
team_info = {
'id': team.id,
'name': team.name,
'team_type': team.team_type,
'team_type_display': team.get_team_type_display(), # 个人团队/团队
'description': team.description,
'approvers': list(team.approvers.filter(is_deleted=False).values('id', 'username'))
}
else:
# 如果团队不存在,只返回团队名称
team_info = {
'name': user.team,
'team_type': None,
'team_type_display': None,
'description': None,
'approvers': []
}
data.append({
'id': user.id,
'username': user.username,
'account': user.account,
'card': user.card,
'position': user.position,
'mobilePhone': user.mobilePhone,
'department': list(user.department.values('id', 'username')),
'role': list(user.role.values('id', 'RoleName', 'permissionId')),
'team': team_info, # 返回完整的团队信息对象
'Dateofjoining': user.Dateofjoining.strftime("%Y-%m-%d") if user.Dateofjoining else None,
'state': user.state,
'has_pending_approval': pending_approval is not None, # 是否有待审核的入职登记
'pending_approval_id': pending_approval.id if pending_approval else None
})
return Response({
'message': '查询成功',
'total': total,
'data': data,
'code': 0
}, status=status.HTTP_200_OK)
class GetCaseListForInvoice(APIView):
"""获取案件列表(用于开票申请选择)"""
def post(self, request, *args, **kwargs):
"""
获取案件列表,用于开票申请时选择合同号
返回案件ID、合同号、负责人等信息
"""
from business.models import ProjectRegistration
# 获取所有未删除的案件
cases = ProjectRegistration.objects.filter(is_deleted=False).order_by('-id')
data = []
for case in cases:
data.append({
'id': case.id,
'ContractNo': case.ContractNo, # 合同号
'responsiblefor': case.responsiblefor, # 负责人
'type': case.type, # 项目类型
'times': case.times, # 立项时间
})
return Response({
'message': '获取成功',
'data': data,
'code': 0
}, status=status.HTTP_200_OK)
class issueAnInvoice(APIView):
def post(self, request, *args, **kwargs):
"""
财务开票
优化后合同号从案件表选择负责人自动同步username从token自动获取
:param request:
:param args:
:param kwargs:
:return:
"""
token = request.META.get('token')
# 从token获取用户信息
try:
user = User.objects.get(token=token, is_deleted=False)
username = user.username
except User.DoesNotExist:
return Response({'status': 'error', 'message': '用户不存在或已被删除', 'code': 1},
status=status.HTTP_401_UNAUTHORIZED)
case_id = request.data.get('case_id') # 案件ID从案件表选择
# 以下字段改为可选如果提供了case_id则从案件表同步
ContractNo = request.data.get('ContractNo') # 合同号可选如果提供了case_id则自动同步
personincharge = request.data.get('personincharge') # 负责人可选如果提供了case_id则自动同步
amount = request.data.get('amount')
type = request.data.get('type')
unit = request.data.get('unit')
number = request.data.get('number')
address_telephone = request.data.get('address_telephone')
bank = request.data.get('bank')
# 必填字段验证username已从token获取不再需要验证
if not all([token, amount, type, unit, number, address_telephone, bank]):
missing_params = []
if not token:
missing_params.append('token')
if not amount:
missing_params.append('amount(开票金额)')
if not type:
missing_params.append('type(开票类型)')
if not unit:
missing_params.append('unit(开票单位)')
if not number:
missing_params.append('number(纳税人识别号)')
if not address_telephone:
missing_params.append('address_telephone(地址/电话)')
if not bank:
missing_params.append('bank(银行卡)')
return Response({
'status': 'error',
'message': f'缺少必填参数: {", ".join(missing_params)}',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 处理合同号和负责人:优先从案件表同步
from business.models import ProjectRegistration, Case
import json
if case_id:
# 如果提供了案件ID优先从Case案件管理表查找如果找不到再从ProjectRegistration立项登记表查找
ContractNo = None
personincharge = None
responsiblefor = None
# 转换case_id为整数如果可能
try:
case_id_int = int(case_id)
except (ValueError, TypeError):
case_id_int = case_id
# 优先从Case案件管理中查找
try:
case = Case.objects.select_related('project').get(id=case_id_int, is_deleted=False)
ContractNo = case.contract_no # 自动同步合同号
responsiblefor = case.responsiblefor # 获取负责人信息
# 如果案件信息不完整从关联的ProjectRegistration获取
if case.project:
project = case.project
ContractNo = ContractNo or project.ContractNo
responsiblefor = responsiblefor or project.responsiblefor
except Case.DoesNotExist:
# 如果Case中找不到尝试从ProjectRegistration立项登记中查找
try:
project = ProjectRegistration.objects.get(id=case_id_int, is_deleted=False)
ContractNo = project.ContractNo # 自动同步合同号
responsiblefor = project.responsiblefor # 获取负责人信息
except ProjectRegistration.DoesNotExist:
# 提供更详细的错误信息
return Response({
'status': 'error',
'message': f'案件不存在或已被删除ID: {case_id}已尝试在Case表和ProjectRegistration表中查找',
'code': 1
}, status=status.HTTP_404_NOT_FOUND)
# 解析负责人信息提取负责人姓名Invoice模型的personincharge字段是CharField需要字符串
if responsiblefor:
try:
if isinstance(responsiblefor, str):
responsiblefor_dict = json.loads(responsiblefor)
else:
responsiblefor_dict = responsiblefor
personincharge = responsiblefor_dict.get('responsible_person', '')
if not personincharge:
personincharge = str(responsiblefor) if responsiblefor else ''
except (json.JSONDecodeError, TypeError, AttributeError):
personincharge = str(responsiblefor) if responsiblefor else ''
# 确保不超过100字符Invoice模型限制
if personincharge and len(str(personincharge)) > 100:
personincharge = str(personincharge)[:100]
# 验证是否成功获取到合同号和负责人
if not ContractNo:
return Response({
'status': 'error',
'message': '无法从案件中获取合同号,请检查案件信息',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
if not personincharge:
return Response({
'status': 'error',
'message': '无法从案件中获取负责人信息,请检查案件信息或手动填写负责人',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
else:
# 如果没有提供案件ID则ContractNo和personincharge必须手动提供
if not ContractNo:
return Response({
'status': 'error',
'message': '请选择案件或手动填写合同号',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
if not personincharge:
return Response({
'status': 'error',
'message': '请选择案件或手动填写负责人',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
today = datetime.datetime.now()
formatted_date = today.strftime("%Y-%m-%d")
# 获取提交人的团队信息(用于审核流程)
from User.models import Team
team_name = user.team
team = None
if team_name:
try:
team = Team.objects.get(name=team_name, is_deleted=False)
except Team.DoesNotExist:
pass
# 审核人列表可选多人团队时需要推荐用户ID数组如[1,2,3],兼容:用户名数组)
approvers = request.data.get('approvers')
# 兼容旧接口:如果传了 personincharge转换为 approvers
personincharge_param = request.data.get('personincharge')
approvers = normalize_approvers_param(approvers, personincharge_param)
# 根据团队类型决定初始状态
# 如果是团队类型且需要审核,状态为"审核中";否则为"待财务处理"
initial_state = "审核中" if (team and team.team_type == 'team') else "待财务处理"
invoice = Invoice.objects.create(
ContractNo=ContractNo,
personincharge=personincharge,
amount=amount,
type=type,
unit=unit,
number=number,
address_telephone=address_telephone,
bank=bank,
state=initial_state,
username=username,
times=formatted_date,
)
# 创建开票待办事项
from User.utils import create_approval_with_team_logic, build_missing_approvers_message
# 构建审批内容
content_parts = [
f"{username}{formatted_date}提交了开票申请",
f"合同编号:{ContractNo}",
f"负责人:{personincharge}",
f"开票金额:{amount}",
f"开票类型:{type}",
f"开票单位:{unit}"
]
content = "".join(content_parts)
# 使用统一的审核流程函数
try:
approval, approvers_order_json, needs_approval = create_approval_with_team_logic(
team_name=team_name,
approvers=approvers,
title=username + "提交开票申请",
content=content,
approval_type="开票",
user_id=str(invoice.id),
business_record=invoice,
today=formatted_date
)
except Exception as e:
# 记录异常并返回错误信息
import logging
logger = logging.getLogger(__name__)
logger.error(f"创建开票审批失败: {str(e)}", exc_info=True)
return Response({
'status': 'error',
'message': f'创建审批流程失败: {str(e)},请检查团队配置和审核人信息',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 如果返回None且需要审核说明缺少审核人
if approval is None and needs_approval:
return Response({
'status': 'error',
'message': build_missing_approvers_message(team_name, approvers),
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 记录操作日志
new_data = {
'id': invoice.id,
'contract_no': invoice.ContractNo,
'personincharge': invoice.personincharge,
'amount': invoice.amount,
'type': invoice.type,
'unit': invoice.unit,
'case_id': case_id if case_id else None
}
log_operation(
request=request,
operation_type='CREATE',
module='Finance',
action='新增开票申请',
target_type='Invoice',
target_id=invoice.id,
target_name=invoice.ContractNo,
new_data=new_data,
remark=f'新增开票申请:合同号 {invoice.ContractNo},负责人 {invoice.personincharge},金额 {invoice.amount}'
)
return Response({
'message': '提交成功',
'code': 0,
'data': {
'id': invoice.id,
'ContractNo': invoice.ContractNo,
'personincharge': invoice.personincharge,
'state': invoice.state,
'approval_id': approval.id if approval else None,
'needs_approval': team is None or team.team_type != 'personal'
}
}, status=status.HTTP_200_OK)
class issueAnInvoiceDetail(APIView):
def post(self, request, *args, **kwargs):
"""
开票记录详情
"""
page = request.data.get('page')
per_page = request.data.get('per_page')
times = request.data.get('times')
end_time = request.data.get('end_time')
unit = request.data.get('unit')
ContractNo = request.data.get('ContractNo')
if not all([page, per_page]):
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
Q_obj = Q()
if times and end_time:
Q_obj &= Q(times__gte=times) & Q(times__lte=end_time)
if unit:
Q_obj &= Q(unit__icontains=unit)
if ContractNo:
Q_obj &= Q(ContractNo__icontains=ContractNo)
invos = Invoice.objects.filter(Q_obj, is_deleted=False).order_by('-id')
total = len(invos)
paginator = Paginator(invos, 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)
# 导入案件模型用于查找案件id
from business.models import Case, ProjectRegistration
data = []
for info in user_agents_page.object_list:
# 通过ContractNo查找对应的案件id
case_id = None
project_id = None
# 优先从Case案件管理中查找
try:
case = Case.objects.get(contract_no=info.ContractNo, is_deleted=False)
case_id = case.id
# 如果Case有关联的ProjectRegistration也返回project_id
if case.project:
project_id = case.project.id
except Case.DoesNotExist:
# 如果Case中找不到尝试从ProjectRegistration立项登记中查找
try:
project = ProjectRegistration.objects.get(ContractNo=info.ContractNo, is_deleted=False)
project_id = project.id
except ProjectRegistration.DoesNotExist:
pass # 如果都找不到case_id和project_id保持为None
itme = {
'id': info.id,
"ContractNo": info.ContractNo,
"personincharge": info.personincharge,
"amount": info.amount,
"type": info.type,
"unit": info.unit,
"number": info.number,
"address_telephone": info.address_telephone,
"bank": info.bank,
"state": info.state,
"username": info.username,
"times": info.times,
"case_id": case_id, # 案件管理IDCase表
"project_id": project_id, # 立项登记IDProjectRegistration表
}
data.append(itme)
return Response({'message': '展示成功', "total": total, 'data': data, 'code': 0}, status=status.HTTP_200_OK)
class EditInvoice(APIView):
def post(self, request, *args, **kwargs):
"""
编辑开票申请
:param request:
:param args:
:param kwargs:
:return:
"""
id = request.data.get('id')
ContractNo = request.data.get('ContractNo')
personincharge = request.data.get('personincharge')
amount = request.data.get('amount')
type = request.data.get('type')
unit = request.data.get('unit')
number = request.data.get('number')
address_telephone = request.data.get('address_telephone')
bank = request.data.get('bank')
if not id:
return Response({'status': 'error', 'message': '缺少参数id', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
try:
invoice = Invoice.objects.get(id=id, is_deleted=False)
except Invoice.DoesNotExist:
return Response({'status': 'error', 'message': '开票申请不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
update_fields_list = []
if ContractNo:
invoice.ContractNo = ContractNo
update_fields_list.append('ContractNo')
if personincharge:
invoice.personincharge = personincharge
update_fields_list.append('personincharge')
if amount:
invoice.amount = amount
update_fields_list.append('amount')
if type:
invoice.type = type
update_fields_list.append('type')
if unit:
invoice.unit = unit
update_fields_list.append('unit')
if number:
invoice.number = number
update_fields_list.append('number')
if address_telephone:
invoice.address_telephone = address_telephone
update_fields_list.append('address_telephone')
if bank:
invoice.bank = bank
update_fields_list.append('bank')
# 记录操作前的数据
old_data = {
'id': invoice.id,
'contract_no': invoice.ContractNo,
'amount': invoice.amount,
'type': invoice.type,
'unit': invoice.unit
}
if update_fields_list:
invoice.save(update_fields=update_fields_list)
# 记录操作后的数据
invoice.refresh_from_db()
new_data = {
'id': invoice.id,
'contract_no': invoice.ContractNo,
'amount': invoice.amount,
'type': invoice.type,
'unit': invoice.unit
}
# 记录操作日志
log_operation(
request=request,
operation_type='UPDATE',
module='Finance',
action='编辑开票申请',
target_type='Invoice',
target_id=invoice.id,
target_name=invoice.ContractNo,
old_data=old_data,
new_data=new_data,
remark=f'编辑开票申请:合同号 {invoice.ContractNo}'
)
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
class DeleteInvoice(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:
invoice = Invoice.objects.get(id=id, is_deleted=False)
except Invoice.DoesNotExist:
return Response({'status': 'error', 'message': '开票申请不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
# 记录操作前的数据
old_data = {
'id': invoice.id,
'contract_no': invoice.ContractNo,
'amount': invoice.amount,
'unit': invoice.unit
}
# 软删除:更新 is_deleted 字段
invoice.is_deleted = True
invoice.save()
# 记录操作日志
log_operation(
request=request,
operation_type='DELETE',
module='Finance',
action='删除开票申请',
target_type='Invoice',
target_id=invoice.id,
target_name=invoice.ContractNo,
old_data=old_data,
remark=f'删除开票申请:合同号 {invoice.ContractNo},金额 {invoice.amount}'
)
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
class confirm(APIView):
def post(self, request, *args, **kwargs):
"""
收入确认
优化后:
1. 支持通过案件IDcase_id或project_id或合同号ContractNo关联案件
2. 从案件管理中同步:合同号、客户名称、负责人
3. 手动填写:收款日期、收款金额
4. 按照团队审核逻辑处理审核流程(与其他审核待办一致)
:param request:
:param args:
:param kwargs:
:return:
"""
# 案件选择三选一case_id、project_id、ContractNo
case_id = request.data.get('case_id') # 案件管理IDCase表
project_id = request.data.get('project_id') # 立项登记IDProjectRegistration表
ContractNo = request.data.get('ContractNo') # 合同号可选如果提供了case_id或project_id则自动同步
# 手动填写信息
times = request.data.get('times') # 收款日期
amount = request.data.get('amount') # 收款金额
# 可选字段如果通过案件ID或合同号找到了案件将从案件管理中同步
CustomerID = request.data.get('CustomerID') # 客户名称可选如果通过案件ID或合同号找到案件则自动同步
# 审核人列表可选多人团队时需要推荐用户ID数组如[1,2,3],兼容:用户名数组)
approvers = request.data.get('approvers')
# 兼容旧接口:如果传了 personincharge转换为 approvers
personincharge = request.data.get('personincharge')
approvers = normalize_approvers_param(approvers, personincharge)
# 收入分配由审批人(负责人)在审批时填写,新增时不接收此参数
token = request.META.get('token')
try:
user = User.objects.get(token=token, is_deleted=False)
except User.DoesNotExist:
return Response({'status': 'error', 'message': '用户不存在或已被删除', 'code': 1},
status=status.HTTP_401_UNAUTHORIZED)
# 必填字段验证
missing_params = []
if not times:
missing_params.append('times(收款日期)')
if not amount:
missing_params.append('amount(收款金额)')
# 必须提供case_id、project_id或ContractNo之一
if not case_id and not project_id and not ContractNo:
missing_params.append('case_id或project_id或ContractNo(案件ID或合同号)')
if missing_params:
return Response({
'status': 'error',
'message': f'缺少必填参数: {", ".join(missing_params)}',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 获取提交人的团队信息(用于审核流程)
from User.models import Team
team_name = user.team
team = None
if team_name:
try:
team = Team.objects.get(name=team_name, is_deleted=False)
except Team.DoesNotExist:
pass
# 通过案件ID或合同号从案件管理中提取信息
from business.models import Case, ProjectRegistration
import json
responsible_person = None # 负责人姓名(用于抄送)
responsible_person_username = None # 负责人用户名(用于抄送)
case_info = None
responsiblefor = None
# 优先通过案件ID查找
if case_id:
# 从Case案件管理中查找
try:
# 转换case_id为整数如果可能
try:
case_id_int = int(case_id)
except (ValueError, TypeError):
case_id_int = case_id
case = Case.objects.select_related('project').get(id=case_id_int, is_deleted=False)
# 获取合同号
ContractNo = ContractNo or case.contract_no
# 获取客户名称
CustomerID = CustomerID or case.client_name
# 获取负责人信息
responsiblefor = case.responsiblefor
# 如果案件信息不完整从关联的ProjectRegistration获取
if case.project:
project = case.project
ContractNo = ContractNo or project.ContractNo
CustomerID = CustomerID or project.client_info
responsiblefor = responsiblefor or project.responsiblefor
case_info = {
'id': case.id,
'type': 'case',
'contract_no': ContractNo,
'customer_name': CustomerID,
}
except Case.DoesNotExist:
return Response({
'status': 'error',
'message': f'案件不存在或已被删除case_id: {case_id}',
'code': 1
}, status=status.HTTP_404_NOT_FOUND)
elif project_id:
# 从ProjectRegistration立项登记中查找
try:
# 转换project_id为整数如果可能
try:
project_id_int = int(project_id)
except (ValueError, TypeError):
project_id_int = project_id
project = ProjectRegistration.objects.get(id=project_id_int, is_deleted=False)
# 获取合同号
ContractNo = ContractNo or project.ContractNo
# 获取客户名称
CustomerID = CustomerID or project.client_info
# 获取负责人信息
responsiblefor = project.responsiblefor
case_info = {
'id': project.id,
'type': 'project',
'contract_no': ContractNo,
'customer_name': CustomerID,
}
except ProjectRegistration.DoesNotExist:
return Response({
'status': 'error',
'message': f'立项登记不存在或已被删除project_id: {project_id}',
'code': 1
}, status=status.HTTP_404_NOT_FOUND)
else:
# 通过合同号查找(保持向后兼容)
# 优先从Case案件管理中查找
try:
case = Case.objects.select_related('project').get(contract_no=ContractNo, is_deleted=False)
# 获取客户名称
CustomerID = CustomerID or case.client_name
# 获取负责人信息
responsiblefor = case.responsiblefor
# 如果案件信息不完整从关联的ProjectRegistration获取
if case.project:
project = case.project
CustomerID = CustomerID or project.client_info
responsiblefor = responsiblefor or project.responsiblefor
case_info = {
'id': case.id,
'type': 'case',
'contract_no': ContractNo,
'customer_name': CustomerID,
}
except Case.DoesNotExist:
# 如果Case中找不到尝试从ProjectRegistration立项登记中查找
try:
project = ProjectRegistration.objects.get(ContractNo=ContractNo, is_deleted=False)
CustomerID = CustomerID or project.client_info
responsiblefor = project.responsiblefor
case_info = {
'id': project.id,
'type': 'project',
'contract_no': ContractNo,
'customer_name': CustomerID,
}
except ProjectRegistration.DoesNotExist:
return Response({
'status': 'error',
'message': f'未找到合同号为"{ContractNo}"的案件或立项登记,请检查合同号是否正确',
'code': 1
}, status=status.HTTP_404_NOT_FOUND)
# 解析负责人信息
if responsiblefor:
try:
if isinstance(responsiblefor, str):
responsiblefor_dict = json.loads(responsiblefor)
else:
responsiblefor_dict = responsiblefor
responsible_person = responsiblefor_dict.get('responsible_person', '')
# 查找负责人用户名
if responsible_person:
try:
responsible_user = User.objects.get(username=responsible_person, is_deleted=False)
responsible_person_username = responsible_user.username
except User.DoesNotExist:
pass
except (json.JSONDecodeError, TypeError, AttributeError):
responsible_person = str(responsiblefor) if responsiblefor else ''
# 更新case_info中的负责人信息
if case_info:
case_info['responsible_person'] = responsible_person
# 验证合同号、客户名称是否获取成功
if not ContractNo:
return Response({
'status': 'error',
'message': '无法从案件管理中获取合同号,请检查案件信息',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
if not CustomerID:
return Response({
'status': 'error',
'message': '无法从案件管理中获取客户名称,请检查案件信息',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
from datetime import datetime
now = datetime.now()
date_string = now.strftime("%Y-%m-%d")
# 根据团队类型决定初始状态
# 如果是团队类型且需要审核,状态为"审核中";否则为"待财务处理"
initial_state = "审核中" if (team and team.team_type == 'team') else "待财务处理"
# 创建收入确认记录
# 收入分配由审批人(负责人)在审批时填写
income = Income.objects.create(
times=times,
ContractNo=ContractNo,
CustomerID=CustomerID,
amount=amount,
allocate="待负责人指定", # 收入分配由审批人在审批时填写
submit=user.username,
submit_tiem=date_string,
state=initial_state
)
# 使用统一的审核流程函数(与其他审核待办一致)
from User.utils import create_approval_with_team_logic
# 构建审批内容
content_parts = [
f"{user.username}{times}提交了收入确认",
f"合同编号:{ContractNo}",
f"客户名称:{CustomerID}",
f"收款金额:{amount}",
f"收入分配:待负责人指定"
]
if responsible_person:
content_parts.append(f"案件负责人:{responsible_person}")
content = "".join(content_parts)
# 使用统一的审核流程函数
try:
approval, approvers_order_json, needs_approval = create_approval_with_team_logic(
team_name=team_name,
approvers=approvers,
title=user.username + "提交收入确认",
content=content,
approval_type="收入确认",
user_id=str(income.id),
business_record=income,
today=date_string
)
except Exception as e:
# 记录异常并返回错误信息
import logging
logger = logging.getLogger(__name__)
logger.error(f"创建收入确认审批失败: {str(e)}", exc_info=True)
return Response({
'status': 'error',
'message': f'创建审批流程失败: {str(e)},请检查团队配置和审核人信息',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 如果返回None且需要审核说明缺少审核人
if approval is None and needs_approval:
return Response({
'status': 'error',
'message': build_missing_approvers_message(team_name, approvers),
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 记录操作日志
new_data = {
'id': income.id,
'ContractNo': income.ContractNo,
'CustomerID': income.CustomerID,
'amount': income.amount,
'allocate': income.allocate,
'case_info': case_info,
'responsible_person': responsible_person,
'team_type': team.team_type if team else 'unknown',
'approvers': approvers if (team and team.team_type == 'team') else None
}
log_operation(
request=request,
operation_type='CREATE',
module='Finance',
action='新增收入确认',
target_type='Income',
target_id=income.id,
target_name=f'{income.ContractNo} - {income.CustomerID}',
new_data=new_data,
remark=f'新增收入确认:合同号 {income.ContractNo},金额 {income.amount},团队类型 {team.team_type if team else "未知"}'
)
return Response({
'message': '提交成功',
'code': 0,
'data': {
'id': income.id,
'ContractNo': income.ContractNo,
'CustomerID': income.CustomerID,
'responsible_person': responsible_person,
'state': income.state,
'approval_id': approval.id if approval else None,
'needs_approval': team is None or team.team_type != 'personal'
}
}, status=status.HTTP_200_OK)
class GetApproversListForIncomeConfirm(APIView):
"""获取收入确认的审核人列表接口"""
def post(self, request, *args, **kwargs):
"""
获取收入确认时可选的审核人列表
返回所有未删除的用户列表,格式简洁,适合下拉选择
:param request:
:param args:
:param kwargs:
:return:
"""
# 可选参数:用户名搜索
username = request.data.get('username', '') # 用户名搜索(可选)
# 查询条件:只查询未软删除的用户
Q_obj = Q(is_deleted=False)
# 排除admin用户超级管理员
Q_obj &= ~Q(username='admin')
Q_obj &= ~Q(account='admin')
# 用户名搜索(模糊匹配)
if username:
Q_obj &= Q(username__icontains=username)
# 查询用户,按用户名排序
users = User.objects.filter(Q_obj).order_by('username')
# 构建返回数据(格式简洁,适合下拉选择)
data = []
for user in users:
data.append({
'id': user.id,
'username': user.username, # 用户名(用于显示和选择)
'account': user.account, # 账号(辅助信息)
'position': user.position if user.position else '', # 岗位(辅助信息)
})
return Response({
'message': '获取审核人列表成功',
'code': 0,
'total': len(data),
'data': data
}, status=status.HTTP_200_OK)
class confirmdisplay(APIView):
def post(self, request, *args, **kwargs):
"""
收入确认展示
权限控制:律师只能看自己的数据,管委会和财务部能查看所有
"""
page = request.data.get('page')
per_page = request.data.get('per_page')
times = request.data.get('times')
end_time = request.data.get('end_time')
CustomerID = request.data.get('CustomerID')
token = request.META.get('token')
if not all([page, per_page]):
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
# 获取当前用户信息
try:
user = User.objects.get(token=token, is_deleted=False)
except User.DoesNotExist:
return Response({'status': 'error', 'message': '用户不存在或已被删除', 'code': 1},
status=status.HTTP_401_UNAUTHORIZED)
# 检查用户是否有权限查看所有数据
# 获取用户的角色名称列表
user_roles = user.role.values_list('RoleName', flat=True)
user_role_names = list(user_roles)
# 获取用户的部门名称列表
user_departments = user.department.values_list('username', flat=True)
user_department_names = list(user_departments)
# 判断是否有权限查看所有数据:管委会角色或财务部角色或财务部部门
# 使用模糊匹配"财务"关键词,与 get_finance_personincharge_candidates() 保持一致
has_all_permission = (
'管委会' in user_role_names or
'财务部' in user_role_names or
any('财务' in name for name in user_department_names)
)
# 构建查询条件
Q_obj = Q()
if times and end_time:
Q_obj &= Q(times__gte=times) & Q(times__lte=end_time)
if CustomerID:
Q_obj &= Q(CustomerID__icontains=CustomerID)
# 如果没有查看所有数据的权限,只显示自己提交的数据
if not has_all_permission:
Q_obj &= Q(submit=user.username)
income = Income.objects.filter(Q_obj, is_deleted=False).order_by('-id')
total = len(income)
paginator = Paginator(income, 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:
itme = {
'id': info.id,
"times": info.times,
"ContractNo": info.ContractNo,
"CustomerID": info.CustomerID,
"amount": info.amount,
"allocate": info.allocate,
"state": info.state,
"submit": info.submit, # 添加提交人信息
"submit_tiem": info.submit_tiem, # 添加提交时间
}
data.append(itme)
return Response({'message': '展示成功', "total": total, 'data': data, 'code': 0}, status=status.HTTP_200_OK)
class EditIncome(APIView):
def post(self, request, *args, **kwargs):
"""
编辑收入确认
:param request:
:param args:
:param kwargs:
:return:
"""
id = request.data.get('id')
times = request.data.get('times')
ContractNo = request.data.get('ContractNo')
CustomerID = request.data.get('CustomerID')
amount = request.data.get('amount')
allocate = request.data.get('allocate')
if not id:
return Response({'status': 'error', 'message': '缺少参数id', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
try:
income = Income.objects.get(id=id, is_deleted=False)
except Income.DoesNotExist:
return Response({'status': 'error', 'message': '收入确认不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
update_fields_list = []
if times:
income.times = times
update_fields_list.append('times')
if ContractNo:
income.ContractNo = ContractNo
update_fields_list.append('ContractNo')
if CustomerID:
income.CustomerID = CustomerID
update_fields_list.append('CustomerID')
if amount:
income.amount = amount
update_fields_list.append('amount')
if allocate:
income.allocate = allocate
update_fields_list.append('allocate')
if update_fields_list:
income.save(update_fields=update_fields_list)
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
class DeleteIncome(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:
income = Income.objects.get(id=id, is_deleted=False)
except Income.DoesNotExist:
return Response({'status': 'error', 'message': '收入确认不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
# 软删除:更新 is_deleted 字段
income.is_deleted = True
income.save()
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
class loan(APIView):
def post(self, request, *args, **kwargs):
"""
调账申请
主要原因是收款确认收入时未确认调账,后期决定要调账时的新增处理
合同号搜索、客户名称同步,其他信息由申请律师填写
调账信息由申请律师填写后直接抄送财务负责人储存、办理,不需要审核流程
:param request:
:param args:
:param kwargs:
:return:
"""
times = request.data.get('times')
ContractNo = request.data.get('ContractNo') # 合同号(可通过搜索获取)
CustomerID = request.data.get('CustomerID') # 客户名称(自动同步)
amount = request.data.get('amount') # 调账金额
situation = request.data.get('situation') # 调账信息(由申请律师填写)
case_id = request.data.get('case_id') # 案件ID可选用于搜索和同步
token = request.META.get('token')
# 必填字段验证
if not all([times, amount, situation]):
missing_params = []
if not times:
missing_params.append('times(调账日期)')
if not amount:
missing_params.append('amount(调账金额)')
if not situation:
missing_params.append('situation(调账信息)')
return Response({
'status': 'error',
'message': f'缺少必填参数: {", ".join(missing_params)}',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
from datetime import datetime
now = datetime.now()
date_string = now.strftime("%Y-%m-%d")
try:
user = User.objects.get(token=token, is_deleted=False)
except User.DoesNotExist:
return Response({'status': 'error', 'message': '用户不存在或已被删除', 'code': 1},
status=status.HTTP_401_UNAUTHORIZED)
# 处理合同号和客户名称:优先从案件表同步
from business.models import ProjectRegistration, Case
case_info = None
# 如果提供了案件ID优先从Case案件管理表查找如果找不到再从ProjectRegistration立项登记表查找
if case_id:
try:
case_id_int = int(case_id)
except (ValueError, TypeError):
case_id_int = case_id
# 优先从Case案件管理中查找
try:
case = Case.objects.select_related('project').get(id=case_id_int, is_deleted=False)
if not ContractNo:
ContractNo = case.contract_no # 自动同步合同号
if not CustomerID:
CustomerID = case.client_name or "" # 自动同步客户名称
if not CustomerID and case.project:
CustomerID = case.project.client_info or ""
case_info = {
'id': case.id,
'ContractNo': ContractNo or case.contract_no,
'CustomerID': CustomerID,
'type': '案件管理'
}
except Case.DoesNotExist:
# 如果Case中找不到尝试从ProjectRegistration立项登记中查找
try:
project = ProjectRegistration.objects.get(id=case_id_int, is_deleted=False)
if not ContractNo:
ContractNo = project.ContractNo # 自动同步合同号
if not CustomerID:
CustomerID = project.client_info or "" # 自动同步客户名称
case_info = {
'id': project.id,
'ContractNo': ContractNo or project.ContractNo,
'CustomerID': CustomerID,
'type': project.type
}
except ProjectRegistration.DoesNotExist:
return Response({
'status': 'error',
'message': '案件不存在或已被删除',
'code': 1
}, status=status.HTTP_404_NOT_FOUND)
# 如果提供了合同号但未提供客户名称,尝试通过合同号查找
if ContractNo and not CustomerID:
# 优先从Case案件管理中查找
try:
case = Case.objects.select_related('project').filter(contract_no=ContractNo, is_deleted=False).first()
if case:
CustomerID = case.client_name or ""
if not CustomerID and case.project:
CustomerID = case.project.client_info or ""
except:
pass
# 如果Case中找不到尝试从ProjectRegistration立项登记中查找
if not CustomerID:
try:
project = ProjectRegistration.objects.filter(ContractNo=ContractNo, is_deleted=False).first()
if project:
CustomerID = project.client_info or ""
except:
pass
# 验证必填字段
if not ContractNo:
return Response({
'status': 'error',
'message': '请提供合同号或选择案件',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
if not CustomerID:
return Response({
'status': 'error',
'message': '无法通过合同号找到客户名称,请手动填写客户名称',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 创建调账申请记录
acc = Accounts.objects.create(
times=times,
ContractNo=ContractNo,
CustomerID=CustomerID,
amount=amount,
situation=situation, # 调账信息(由申请律师填写)
submit=user.username,
submit_tiem=date_string,
state="已通过" # 直接设为已通过,因为不需要审核
)
# 构建审批内容
content_parts = [
f"{user.username}{times}提交了调账申请",
f"合同编号:{ContractNo}",
f"客户名称:{CustomerID}",
f"调账金额:{amount}",
f"调账信息:{situation}"
]
content = "".join(content_parts)
# 直接抄送财务,不需要审核流程
from User.utils import get_finance_personincharge_value
from User.models import Approval
finance_personincharge = get_finance_personincharge_value()
approval = Approval.objects.create(
title=user.username + "提交调账申请",
content=content,
times=date_string,
personincharge=finance_personincharge,
state="已抄送财务",
type="调账申请",
user_id=str(acc.id)
)
# 调账申请不需要审核,直接设为已通过状态
acc.state = "已通过"
acc.save(update_fields=['state'])
# 记录操作日志
new_data = {
'id': acc.id,
'ContractNo': acc.ContractNo,
'CustomerID': acc.CustomerID,
'amount': acc.amount,
'case_id': case_id if case_id else None
}
log_operation(
request=request,
operation_type='CREATE',
module='Finance',
action='新增调账申请',
target_type='Accounts',
target_id=acc.id,
target_name=f'{acc.ContractNo} - {acc.CustomerID}',
new_data=new_data,
remark=f'新增调账申请:合同号 {acc.ContractNo},金额 {acc.amount},已直接抄送财务'
)
return Response({
'message': '提交成功',
'code': 0,
'data': {
'id': acc.id,
'ContractNo': acc.ContractNo,
'state': acc.state
}
}, status=status.HTTP_200_OK)
class loandisplay(APIView):
def post(self, request, *args, **kwargs):
"""
调账申请列表展示
优化CustomerID字段直接返回客户名字如果有多个使用逗号隔开
"""
page = request.data.get('page')
per_page = request.data.get('per_page')
times = request.data.get('times')
end_time = request.data.get('end_time')
CustomerID = request.data.get('CustomerID')
if not all([page, per_page]):
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
Q_obj = Q()
if times and end_time:
Q_obj &= Q(times__gte=times) & Q(times__lte=end_time)
if CustomerID:
Q_obj &= Q(CustomerID__icontains=CustomerID)
acc = Accounts.objects.filter(Q_obj, is_deleted=False).order_by('-id')
total = len(acc)
paginator = Paginator(acc, 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 = []
import re
import json
from business.models import Case, ProjectRegistration
for info in user_agents_page.object_list:
# 通过合同号查找关联的案件,提取客户名字(只返回纯名字)
customer_names = []
if info.ContractNo:
# 优先从Case案件管理中查找
try:
case = Case.objects.select_related('project').filter(
contract_no=info.ContractNo,
is_deleted=False
).first()
if case:
# 提取委托人名字(只返回纯名字)
if case.client_name:
client_name_str = str(case.client_name).strip()
# 先尝试 JSON/ast 解析
parsed_client = None
try:
parsed_client = json.loads(client_name_str)
except Exception:
try:
import ast
parsed_client = ast.literal_eval(client_name_str)
except Exception:
parsed_client = None
# 从解析结果中提取 name 字段
if isinstance(parsed_client, list):
for item in parsed_client:
if isinstance(item, dict):
n = item.get("name")
if n:
customer_names.append(str(n).strip())
elif isinstance(parsed_client, dict):
n = parsed_client.get("name")
if n:
customer_names.append(str(n).strip())
else:
# 如果不是 JSON/结构体,按字符串处理
client_name = client_name_str.split('')[0].split(',')[0].strip()
client_name = re.sub(r'[(].*?[)]', '', client_name) # 去除括号内容
client_name = re.sub(r'\d{15,18}', '', client_name) # 去除身份证号
client_name = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9]', '', client_name).strip() # 只保留中文、英文、数字
if client_name and len(client_name) >= 2:
customer_names.append(client_name)
# 提取相对方名字(只返回纯名字)
if case.party_name:
party_name_str = str(case.party_name).strip()
# 先尝试 JSON/ast 解析
parsed_party = None
try:
parsed_party = json.loads(party_name_str)
except Exception:
try:
import ast
parsed_party = ast.literal_eval(party_name_str)
except Exception:
parsed_party = None
# 从解析结果中提取 name 字段
if isinstance(parsed_party, list):
for item in parsed_party:
if isinstance(item, dict):
n = item.get("name")
if n:
customer_names.append(str(n).strip())
elif isinstance(parsed_party, dict):
n = parsed_party.get("name")
if n:
customer_names.append(str(n).strip())
else:
# 如果不是 JSON/结构体,按字符串处理
party_name = party_name_str.split('')[0].split(',')[0].strip()
party_name = re.sub(r'[(].*?[)]', '', party_name) # 去除括号内容
party_name = re.sub(r'\d{15,18}', '', party_name) # 去除身份证号
party_name = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9]', '', party_name).strip() # 只保留中文、英文、数字
if party_name and len(party_name) >= 2:
customer_names.append(party_name)
# 如果Case中没有从关联的project获取
if not customer_names and case.project:
if case.project.client_info:
client_info_str = str(case.project.client_info).strip()
# 先尝试 JSON/ast 解析
parsed_client_info = None
try:
parsed_client_info = json.loads(client_info_str)
except Exception:
try:
import ast
parsed_client_info = ast.literal_eval(client_info_str)
except Exception:
parsed_client_info = None
# 从解析结果中提取 name 字段
if isinstance(parsed_client_info, list):
for item in parsed_client_info:
if isinstance(item, dict):
n = item.get("name")
if n:
customer_names.append(str(n).strip())
elif isinstance(parsed_client_info, dict):
n = parsed_client_info.get("name")
if n:
customer_names.append(str(n).strip())
else:
# 如果不是 JSON/结构体,按字符串处理
client_info = client_info_str.split('')[0].split(',')[0].strip()
client_info = re.sub(r'[(].*?[)]', '', client_info) # 去除括号内容
client_info = re.sub(r'\d{15,18}', '', client_info) # 去除身份证号
client_info = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9]', '', client_info).strip() # 只保留中文、英文、数字
if client_info and len(client_info) >= 2:
customer_names.append(client_info)
if case.project.party_info:
party_info_str = str(case.project.party_info).strip()
# 先尝试 JSON/ast 解析
parsed_party_info = None
try:
parsed_party_info = json.loads(party_info_str)
except Exception:
try:
import ast
parsed_party_info = ast.literal_eval(party_info_str)
except Exception:
parsed_party_info = None
# 从解析结果中提取 name 字段
if isinstance(parsed_party_info, list):
for item in parsed_party_info:
if isinstance(item, dict):
n = item.get("name")
if n:
customer_names.append(str(n).strip())
elif isinstance(parsed_party_info, dict):
n = parsed_party_info.get("name")
if n:
customer_names.append(str(n).strip())
else:
# 如果不是 JSON/结构体,按字符串处理
party_info = party_info_str.split('')[0].split(',')[0].strip()
party_info = re.sub(r'[(].*?[)]', '', party_info) # 去除括号内容
party_info = re.sub(r'\d{15,18}', '', party_info) # 去除身份证号
party_info = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9]', '', party_info).strip() # 只保留中文、英文、数字
if party_info and len(party_info) >= 2:
customer_names.append(party_info)
except:
pass
# 如果Case中找不到尝试从ProjectRegistration立项登记中查找
if not customer_names:
try:
project = ProjectRegistration.objects.filter(
ContractNo=info.ContractNo,
is_deleted=False
).first()
if project:
# 提取委托人名字(只返回纯名字)
if project.client_info:
client_info_str = str(project.client_info).strip()
# 先尝试 JSON/ast 解析
parsed_client_info = None
try:
parsed_client_info = json.loads(client_info_str)
except Exception:
try:
import ast
parsed_client_info = ast.literal_eval(client_info_str)
except Exception:
parsed_client_info = None
# 从解析结果中提取 name 字段
if isinstance(parsed_client_info, list):
for item in parsed_client_info:
if isinstance(item, dict):
n = item.get("name")
if n:
customer_names.append(str(n).strip())
elif isinstance(parsed_client_info, dict):
n = parsed_client_info.get("name")
if n:
customer_names.append(str(n).strip())
else:
# 如果不是 JSON/结构体,按字符串处理
client_info = client_info_str.split('')[0].split(',')[0].strip()
client_info = re.sub(r'[(].*?[)]', '', client_info) # 去除括号内容
client_info = re.sub(r'\d{15,18}', '', client_info) # 去除身份证号
client_info = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9]', '', client_info).strip() # 只保留中文、英文、数字
if client_info and len(client_info) >= 2:
customer_names.append(client_info)
# 提取相对方名字(只返回纯名字)
if project.party_info:
party_info_str = str(project.party_info).strip()
# 先尝试 JSON/ast 解析
parsed_party_info = None
try:
parsed_party_info = json.loads(party_info_str)
except Exception:
try:
import ast
parsed_party_info = ast.literal_eval(party_info_str)
except Exception:
parsed_party_info = None
# 从解析结果中提取 name 字段
if isinstance(parsed_party_info, list):
for item in parsed_party_info:
if isinstance(item, dict):
n = item.get("name")
if n:
customer_names.append(str(n).strip())
elif isinstance(parsed_party_info, dict):
n = parsed_party_info.get("name")
if n:
customer_names.append(str(n).strip())
else:
# 如果不是 JSON/结构体,按字符串处理
party_info = party_info_str.split('')[0].split(',')[0].strip()
party_info = re.sub(r'[(].*?[)]', '', party_info) # 去除括号内容
party_info = re.sub(r'\d{15,18}', '', party_info) # 去除身份证号
party_info = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9]', '', party_info).strip() # 只保留中文、英文、数字
if party_info and len(party_info) >= 2:
customer_names.append(party_info)
except:
pass
# 如果找不到案件信息从CustomerID字段中提取名字
if not customer_names and info.CustomerID:
customer_id_str = str(info.CustomerID).strip()
if customer_id_str:
# 先尝试按 JSON 解析(例如:[{"index":1,"name":"测试111","idNumber":"111"}]
import json
parsed = None
try:
parsed = json.loads(customer_id_str)
except Exception:
parsed = None
# 如果 JSON 解析失败,再尝试按 Python 字面量解析(例如:[{'index': 1, 'name': '测试111'}]
if parsed is None:
import ast
try:
parsed = ast.literal_eval(customer_id_str)
except Exception:
parsed = None
names_from_struct = []
if isinstance(parsed, list):
for item in parsed:
if isinstance(item, dict):
n = item.get("name")
if n:
names_from_struct.append(str(n).strip())
elif isinstance(parsed, dict):
n = parsed.get("name")
if n:
names_from_struct.append(str(n).strip())
if names_from_struct:
for n in names_from_struct:
if n and n not in customer_names:
customer_names.append(n)
else:
# 如果既不是 JSON 也不是 Python 结构,再按原有字符串方式解析(逗号/分号分隔)
parts = re.split(r'[,;\s]+', customer_id_str)
for part in parts:
part = part.strip()
if part:
# 提取纯名字部分:去除身份证号、括号内容、特殊字符等
# 只保留中文字符、英文字母、数字(用于名字)
name = re.sub(r'[(].*?[)]', '', part) # 去除括号内容
name = re.sub(r'\d{15,18}', '', name) # 去除身份证号
name = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9]', '', name) # 只保留中文、英文、数字
name = name.strip()
if name and len(name) >= 2 and name not in customer_names: # 名字至少2个字符
customer_names.append(name)
# 去重并连接,只返回客户名字
customer_names = list(dict.fromkeys(customer_names)) # 保持顺序去重
customer_name_str = ''.join(customer_names) if customer_names else ''
itme = {
'id': info.id,
"times": info.times,
"ContractNo": info.ContractNo,
"CustomerID": customer_name_str, # 返回客户名字,多个用逗号隔开
"amount": info.amount,
"situation": info.situation,
"state": info.state,
}
data.append(itme)
return Response({'message': '展示成功', "total": total, 'data': data, 'code': 0}, status=status.HTTP_200_OK)
class EditAccounts(APIView):
def post(self, request, *args, **kwargs):
"""
编辑调账申请
:param request:
:param args:
:param kwargs:
:return:
"""
id = request.data.get('id')
times = request.data.get('times')
ContractNo = request.data.get('ContractNo')
CustomerID = request.data.get('CustomerID')
amount = request.data.get('amount')
situation = request.data.get('situation')
if not id:
return Response({'status': 'error', 'message': '缺少参数id', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
try:
account = Accounts.objects.get(id=id, is_deleted=False)
except Accounts.DoesNotExist:
return Response({'status': 'error', 'message': '调账申请不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
update_fields_list = []
if times:
account.times = times
update_fields_list.append('times')
if ContractNo:
account.ContractNo = ContractNo
update_fields_list.append('ContractNo')
if CustomerID:
account.CustomerID = CustomerID
update_fields_list.append('CustomerID')
if amount:
account.amount = amount
update_fields_list.append('amount')
if situation:
account.situation = situation
update_fields_list.append('situation')
if update_fields_list:
account.save(update_fields=update_fields_list)
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
class DeleteAccounts(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:
account = Accounts.objects.get(id=id, is_deleted=False)
except Accounts.DoesNotExist:
return Response({'status': 'error', 'message': '调账申请不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
# 软删除:更新 is_deleted 字段
account.is_deleted = True
account.save()
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
class PaymentRequest(APIView):
def post(self, request, *args, **kwargs):
"""
付款申请
新增付款申请单包括:
1、付款事由
2、付款金额
3、付款日期可选
4、收款方名称
5、收款方银行账号
6、收款方开户行
7、申请人通过token自动获取
8、支付说明
9、审批人可选多人团队时需要
审批逻辑和离职逻辑一样:
- 个人团队personal/独立律师):不触发审批,直接抄送财务
- 团队team/团队律师):需要审批,按顺序依次审批,最后抄送财务
:param request:
:param args:
:param kwargs:
:return:
"""
payment_reason = request.data.get('payment_reason') # 付款事由
amount = request.data.get('amount') # 付款金额
times = request.data.get('times') # 付款日期(可选)
payee_name = request.data.get('payee_name') # 收款方名称
payee_account = request.data.get('payee_account') # 收款方银行账号
payee_bank = request.data.get('payee_bank') # 收款方开户行
payment_description = request.data.get('payment_description') # 支付说明
payment_type = request.data.get('payment_type', '律所支付') # 支付方式(可选,保留字段,但不影响审批流程)
applicant = request.data.get('applicant') # 申请人可选如果前端传递则使用否则从token获取
approvers = request.data.get('approvers') # 审核人列表可选多人团队时需要推荐用户ID数组如[1,2,3],兼容:用户名数组)
# 兼容旧接口:如果传了 personincharge转换为 approvers
personincharge = request.data.get('personincharge')
approvers = normalize_approvers_param(approvers, personincharge)
# 兼容旧字段
reason = request.data.get('reason') or payment_reason
payee = request.data.get('payee') or payee_name
bankcard = request.data.get('bankcard') or payee_account
BankName = request.data.get('BankName') or payee_bank
# 必填字段验证
if not all([payment_reason or reason, amount, payee_name or payee, payee_account or bankcard, payee_bank or BankName, payment_description]):
missing_params = []
if not (payment_reason or reason):
missing_params.append('payment_reason(付款事由)')
if not amount:
missing_params.append('amount(付款金额)')
if not (payee_name or payee):
missing_params.append('payee_name(收款方名称)')
if not (payee_account or bankcard):
missing_params.append('payee_account(收款方银行账号)')
if not (payee_bank or BankName):
missing_params.append('payee_bank(收款方开户行)')
if not payment_description:
missing_params.append('payment_description(支付说明)')
return Response({
'status': 'error',
'message': f'缺少必填参数: {", ".join(missing_params)}',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 获取申请人优先使用前端传递的applicant如果没有则从token获取
applicant_user = None
token = request.META.get('token')
if applicant:
# 如果前端传递了申请人,验证申请人是否存在
try:
applicant_user = User.objects.get(username=applicant, is_deleted=False)
except User.DoesNotExist:
return Response({'status': 'error', 'message': '申请人不存在或已被删除', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
else:
# 如果前端没有传递申请人从token获取
if not token:
return Response({'status': 'error', 'message': '缺少参数请提供applicant或token', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
try:
applicant_user = User.objects.get(token=token, is_deleted=False)
applicant = applicant_user.username
except User.DoesNotExist:
return Response({'status': 'error', 'message': '用户不存在或已被删除', 'code': 1},
status=status.HTTP_401_UNAUTHORIZED)
from datetime import datetime
now = datetime.now()
date_string = now.strftime("%Y-%m-%d")
# 获取申请人的团队信息
team_name = applicant_user.team
team = None
if team_name:
from User.models import Team
try:
team = Team.objects.get(name=team_name, is_deleted=False)
except Team.DoesNotExist:
# 如果团队不存在,默认按团队类型处理(需要审批)
pass
# 付款申请统一都需要审批(无论团队类型)
initial_state = "审核中"
# 创建付款申请记录
pay = Payment.objects.create(
payment_reason=payment_reason or reason,
amount=amount,
times=times, # 可选
payee_name=payee_name or payee,
payee_account=payee_account or bankcard,
payee_bank=payee_bank or BankName,
applicant=applicant,
payment_description=payment_description,
payment_type=payment_type,
submit_tiem=date_string,
state=initial_state,
# 兼容旧字段
reason=reason or payment_reason,
payee=payee or payee_name,
bankcard=bankcard or payee_account,
BankName=BankName or payee_bank
)
# 构建审批内容
content_parts = [
f"{applicant}提交了付款申请",
f"付款事由:{payment_reason or reason}",
f"付款金额:{amount}",
f"收款方名称:{payee_name or payee}",
f"收款方银行账号:{payee_account or bankcard}",
f"收款方开户行:{payee_bank or BankName}",
f"支付说明:{payment_description}"
]
if times:
content_parts.insert(3, f"付款日期:{times}")
content = "".join(content_parts)
# 使用统一的审核流程函数付款申请统一需要审批force_approval=True
from User.utils import create_approval_with_team_logic
approval, approvers_order_json, needs_approval = create_approval_with_team_logic(
team_name=team_name,
approvers=approvers,
title=applicant + "提交付款申请",
content=content,
approval_type="付款申请",
user_id=str(pay.id),
business_record=pay,
today=date_string,
force_approval=True # 付款申请统一需要审批
)
# 如果返回None且需要审核说明缺少审核人
if approval is None and needs_approval:
return Response({
'status': 'error',
'message': build_missing_approvers_message(team_name, approvers),
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 记录操作日志
new_data = {
'id': pay.id,
'payment_reason': pay.payment_reason,
'amount': pay.amount,
'payee_name': pay.payee_name,
'applicant': pay.applicant,
'payment_description': pay.payment_description,
'payment_type': pay.payment_type
}
log_operation(
request=request,
operation_type='CREATE',
module='Finance',
action='新增付款申请',
target_type='Payment',
target_id=pay.id,
target_name=f'{pay.applicant} - {pay.amount}',
new_data=new_data,
remark=f'新增付款申请:申请人 {pay.applicant},金额 {pay.amount},支付方式 {pay.payment_type}'
)
return Response({
'message': '提交成功',
'code': 0,
'data': {
'id': pay.id,
'state': pay.state,
'approval_id': approval.id if approval else None,
'needs_approval': True, # 付款申请统一都需要审批
'team_type': team.team_type if team else None, # 团队类型personal/team前端用这个字段判断
'team_name': team_name # 团队名称
}
}, status=status.HTTP_200_OK)
class PaymentDisplay(APIView):
def post(self, request, *args, **kwargs):
"""
付款申请列表展示接口
必填参数page页码、per_page每页数量
可选参数times开始时间、end_time结束时间、payee_name收款方名称搜索、applicant申请人搜索
返回:申请人、收款单位、付款金额、支付说明
"""
# 必填参数:页数和每页数量
page = request.data.get('page')
per_page = request.data.get('per_page')
# 可选参数:搜索条件
times = request.data.get('times') # 开始时间(可选)
end_time = request.data.get('end_time') # 结束时间(可选)
payee_name = request.data.get('payee_name') # 收款方名称搜索(可选)
applicant = request.data.get('applicant') # 申请人搜索(可选)
# 验证必填参数
if not page:
return Response({
'status': 'error',
'message': '缺少必填参数: page(页码)',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
if not per_page:
return Response({
'status': 'error',
'message': '缺少必填参数: per_page(每页数量)',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 转换为整数
try:
page = int(page)
per_page = int(per_page)
except (ValueError, TypeError):
return Response({
'status': 'error',
'message': '参数类型错误: page和per_page必须是数字',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 构建查询条件
Q_obj = Q(is_deleted=False)
# 时间范围筛选(可选)
if times and end_time:
Q_obj &= Q(submit_tiem__gte=times) & Q(submit_tiem__lte=end_time)
# 收款方名称搜索(可选,兼容新旧字段)
if payee_name:
Q_obj &= Q(payee__icontains=payee_name)
# 申请人搜索(可选)
if applicant:
Q_obj &= Q(applicant__icontains=applicant)
# 查询数据(只查询存在的字段,避免访问不存在的字段)
pay = Payment.objects.filter(Q_obj).only(
# 这里仅选择“数据库一定存在的旧字段”,避免线上库未迁移时查询新列报错
'id', 'applicant', 'amount', 'state', 'submit_tiem',
'reason', 'times', 'payee', 'bankcard', 'BankName',
'approvers_order'
).order_by('-id')
total = pay.count()
# 分页处理
paginator = Paginator(pay, 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:
# 安全地获取字段值:
# 线上数据库如果还没迁移新字段payee_name/payment_reason/...会导致“Unknown column”。
# 所以这里访问新字段时必须捕获数据库异常,并回退到旧字段。
from django.db.utils import OperationalError
def safe_get(obj, field_name, default=None):
try:
return getattr(obj, field_name)
except OperationalError:
return default
except Exception:
return default
# 新旧字段兼容:优先新字段,取不到则回退旧字段
payment_reason_value = safe_get(info, 'payment_reason') or safe_get(info, 'reason') or ''
payee_name_value = safe_get(info, 'payee_name') or safe_get(info, 'payee') or ''
payee_account_value = safe_get(info, 'payee_account') or safe_get(info, 'bankcard') or ''
payee_bank_value = safe_get(info, 'payee_bank') or safe_get(info, 'BankName') or ''
payment_description_value = safe_get(info, 'payment_description') or ''
payment_type_value = safe_get(info, 'payment_type') or ''
itme = {
'id': info.id,
"applicant": info.applicant, # 申请人
"payment_reason": payment_reason_value, # 付款事由
"payee_name": payee_name_value, # 收款单位(收款方名称)
"payee_account": payee_account_value, # 收款方银行账号
"payee_bank": payee_bank_value, # 收款方开户行
"amount": info.amount, # 付款金额
"times": safe_get(info, 'times') or "", # 付款日期(可选)
"payment_description": payment_description_value, # 支付说明
"payment_type": payment_type_value, # 支付方式
"state": info.state, # 状态
"submit_tiem": info.submit_tiem, # 提交时间
"approvers_order": safe_get(info, 'approvers_order') or None, # 审核人顺序JSON字符串
# 兼容旧字段(如前端仍在使用)
"reason": safe_get(info, 'reason') or "", # 旧:付款理由
"payee": safe_get(info, 'payee') or "", # 旧:收款人
"bankcard": safe_get(info, 'bankcard') or "", # 旧:银行卡
"BankName": safe_get(info, 'BankName') or "", # 旧:开户行
}
data.append(itme)
return Response({
'message': '展示成功',
"total": total,
'data': data,
'code': 0
}, status=status.HTTP_200_OK)
class EditPayment(APIView):
def post(self, request, *args, **kwargs):
"""
编辑付款申请
:param request:
:param args:
:param kwargs:
:return:
"""
id = request.data.get('id')
reason = request.data.get('reason')
amount = request.data.get('amount')
times = request.data.get('times')
payee = request.data.get('payee')
bankcard = request.data.get('bankcard')
BankName = request.data.get('BankName')
applicant = request.data.get('applicant')
if not id:
return Response({'status': 'error', 'message': '缺少参数id', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
try:
payment = Payment.objects.get(id=id, is_deleted=False)
except Payment.DoesNotExist:
return Response({'status': 'error', 'message': '付款申请不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
update_fields_list = []
if reason:
payment.reason = reason
update_fields_list.append('reason')
if amount:
payment.amount = amount
update_fields_list.append('amount')
if times:
payment.times = times
update_fields_list.append('times')
if payee:
payment.payee = payee
update_fields_list.append('payee')
if bankcard:
payment.bankcard = bankcard
update_fields_list.append('bankcard')
if BankName:
payment.BankName = BankName
update_fields_list.append('BankName')
if applicant:
payment.applicant = applicant
update_fields_list.append('applicant')
if update_fields_list:
payment.save(update_fields=update_fields_list)
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
class DeletePayment(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:
payment = Payment.objects.get(id=id, is_deleted=False)
except Payment.DoesNotExist:
return Response({'status': 'error', 'message': '付款申请不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
# 软删除:更新 is_deleted 字段
payment.is_deleted = True
payment.save()
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
class reimbursement(APIView):
def post(self, request, *args, **kwargs):
"""
报销申请
优化后:根据团队类型判断是否需要审批
- 个人团队personal/独立律师):不触发审批,直接抄送财务
- 团队team/团队律师):需要多级审批,按顺序依次审批,最后抄送财务
:param request:
:param args:
:param kwargs:
:return:
"""
person = request.data.get('person')
times = request.data.get('times')
reason = request.data.get('reason')
amount = request.data.get('amount')
FeeDescription = request.data.get('FeeDescription')
approvers = request.data.get('approvers') # 审批人列表数组仅团队类型需要推荐用户ID数组如[1,2,3],兼容:用户名数组)
# 兼容旧接口:如果传了 personincharge转换为 approvers
personincharge = request.data.get('personincharge')
approvers = normalize_approvers_param(approvers, personincharge)
token = request.META.get('token')
if not all([person, times, reason, amount, FeeDescription]):
missing_params = []
if not person:
missing_params.append('person(报销人)')
if not times:
missing_params.append('times(报销日期)')
if not reason:
missing_params.append('reason(报销理由)')
if not amount:
missing_params.append('amount(报销金额)')
if not FeeDescription:
missing_params.append('FeeDescription(费用说明)')
return Response({
'status': 'error',
'message': f'缺少必填参数: {", ".join(missing_params)}',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 获取报销人的团队信息
try:
submitter_user = User.objects.get(username=person, is_deleted=False)
except User.DoesNotExist:
return Response({
'status': 'error',
'message': f'报销人"{person}"不存在或已被删除',
'code': 1
}, status=status.HTTP_404_NOT_FOUND)
from User.models import Team
team_name = submitter_user.team
team = None
if team_name:
try:
team = Team.objects.get(name=team_name, is_deleted=False)
except Team.DoesNotExist:
pass
from datetime import datetime
now = datetime.now()
date_string = now.strftime("%Y-%m-%d")
# 报销统一都需要审批(无论团队类型)
initial_state = "审核中"
reim = Reimbursement.objects.create(
person=person,
times=times,
reason=reason,
amount=amount,
FeeDescription=FeeDescription,
submit_tiem=date_string,
state=initial_state
)
# 使用统一的审核流程函数报销统一需要审批force_approval=True
from User.utils import create_approval_with_team_logic
content = f"{person}{times}提交了报销申请,报销理由:{reason},报销金额:{amount},报销日期:{times},费用说明:{FeeDescription}"
approval, approvers_order_json, needs_approval = create_approval_with_team_logic(
team_name=team_name,
approvers=approvers,
title=person + "报销申请",
content=content,
approval_type="报销申请",
user_id=str(reim.id),
business_record=reim,
today=date_string,
force_approval=True # 报销统一需要审批
)
# 如果返回None且需要审核说明缺少审核人
if approval is None and needs_approval:
return Response({
'status': 'error',
'message': build_missing_approvers_message(team_name, approvers),
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 记录操作日志
new_data = {
'id': reim.id,
'person': reim.person,
'amount': reim.amount,
'team_type': team.team_type if team else 'unknown',
'approvers': approvers if (team and team.team_type == 'team') else None
}
log_operation(
request=request,
operation_type='CREATE',
module='Finance',
action='新增报销申请',
target_type='Reimbursement',
target_id=reim.id,
target_name=f'{reim.person} - {reim.amount}',
new_data=new_data,
remark=f'新增报销申请:{reim.person},金额 {reim.amount},团队类型 {team.team_type if team else "未知"}'
)
return Response({
'message': '提交成功',
'code': 0,
'data': {
'id': reim.id,
'state': reim.state,
'approval_id': approval.id if approval else None,
'needs_approval': True, # 报销统一都需要审批
'team_type': team.team_type if team else None, # 团队类型personal/team前端用这个字段判断
'team_name': team_name # 团队名称
}
}, status=status.HTTP_200_OK)
class reimbursementdetail(APIView):
def post(self, request, *args, **kwargs):
"""
报销申请列表展示接口
必填参数page页码、per_page每页数量
可选参数times开始时间、end_time结束时间、person报销人搜索
"""
page = request.data.get('page')
per_page = request.data.get('per_page')
times = request.data.get('times')
end_time = request.data.get('end_time')
person = request.data.get('person')
# 验证必填参数
if not page:
return Response({
'status': 'error',
'message': '缺少必填参数: page(页码)',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
if not per_page:
return Response({
'status': 'error',
'message': '缺少必填参数: per_page(每页数量)',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 转换为整数
try:
page = int(page)
per_page = int(per_page)
except (ValueError, TypeError):
return Response({
'status': 'error',
'message': '参数类型错误: page和per_page必须是数字',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
Q_obj = Q()
if times and end_time:
Q_obj &= Q(times__gte=times) & Q(times__lte=end_time)
if person:
Q_obj &= Q(person__icontains=person)
rei = Reimbursement.objects.filter(Q_obj, is_deleted=False).order_by('-id')
total = len(rei)
paginator = Paginator(rei, 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:
itme = {
"id": info.id,
"times": info.times,
"person": info.person,
"reason": info.reason,
"amount": info.amount,
"FeeDescription": info.FeeDescription,
"state": info.state,
}
data.append(itme)
return Response({'message': '展示成功', "total": total, 'data': data, 'code': 0}, status=status.HTTP_200_OK)
class EditReimbursement(APIView):
def post(self, request, *args, **kwargs):
"""
编辑报销申请
:param request:
:param args:
:param kwargs:
:return:
"""
id = request.data.get('id')
person = request.data.get('person')
times = request.data.get('times')
reason = request.data.get('reason')
amount = request.data.get('amount')
FeeDescription = request.data.get('FeeDescription')
if not id:
return Response({'status': 'error', 'message': '缺少参数id', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
try:
reimbursement = Reimbursement.objects.get(id=id, is_deleted=False)
except Reimbursement.DoesNotExist:
return Response({'status': 'error', 'message': '报销申请不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
update_fields_list = []
if person:
reimbursement.person = person
update_fields_list.append('person')
if times:
reimbursement.times = times
update_fields_list.append('times')
if reason:
reimbursement.reason = reason
update_fields_list.append('reason')
if amount:
reimbursement.amount = amount
update_fields_list.append('amount')
if FeeDescription:
reimbursement.FeeDescription = FeeDescription
update_fields_list.append('FeeDescription')
if update_fields_list:
reimbursement.save(update_fields=update_fields_list)
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
class DeleteReimbursement(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:
reimbursement = Reimbursement.objects.get(id=id, is_deleted=False)
except Reimbursement.DoesNotExist:
return Response({'status': 'error', 'message': '报销申请不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
# 软删除:更新 is_deleted 字段
reimbursement.is_deleted = True
reimbursement.save()
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
class Change(APIView):
def post(self, request, *args, **kwargs):
"""
工资/奖金变更
优化后:增加提交人字段,明确是谁提交的
:param request:
:param args:
:param kwargs:
:return:
"""
username = request.data.get('username')
type = request.data.get('type')
Instructions = request.data.get('Instructions')
approvers = request.data.get('approvers') # 审核人列表可选多人团队时需要推荐用户ID数组如[1,2,3],兼容:用户名数组)
# 兼容旧接口:如果传了 personincharge转换为 approvers
personincharge = request.data.get('personincharge')
approvers = normalize_approvers_param(approvers, personincharge)
token = request.META.get('token')
if not all([username, type, Instructions]):
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
# 获取提交人信息优先从token获取确保明确记录是谁提交的
submitter = None
user = None
if token:
try:
user = User.objects.get(token=token, is_deleted=False)
submitter = user.username
except User.DoesNotExist:
pass
# 如果token无效或未提供使用username作为提交人兼容旧逻辑
if not submitter:
submitter = username
from datetime import datetime
now = datetime.now()
date_string = now.strftime("%Y-%m-%d")
# 获取用户的团队信息
team_name = user.team if user else None
team = None
if team_name:
from User.models import Team
try:
team = Team.objects.get(name=team_name, is_deleted=False)
except Team.DoesNotExist:
pass
# 工资/奖金变更统一都需要审批(无论团队类型)
initial_state = "审核中"
bonus = BonusChange.objects.create(
username=username,
type=type,
Instructions=Instructions,
times=date_string,
state=initial_state,
submitter=submitter # 记录提交人
)
from User.utils import create_approval_with_team_logic
content = f"{submitter}{date_string}提交了工资/奖金变更,类型:{type},调整说明:{Instructions}"
# 使用统一的审核流程函数(工资/奖金变更统一需要审批force_approval=True
approval, approvers_order_json, needs_approval = create_approval_with_team_logic(
team_name=team_name,
approvers=approvers,
title=username + "工资/奖金变更",
content=content,
approval_type="工资/奖金变更",
user_id=str(bonus.id),
business_record=bonus,
today=date_string,
force_approval=True # 工资/奖金变更统一需要审批
)
# 如果返回None且需要审核说明缺少审核人
if approval is None and needs_approval:
return Response({
'status': 'error',
'message': build_missing_approvers_message(team_name, approvers),
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 记录操作日志
new_data = {
'id': bonus.id,
'username': bonus.username,
'type': bonus.type,
'submitter': bonus.submitter
}
log_operation(
request=request,
operation_type='CREATE',
module='Finance',
action='新增工资/奖金变更',
target_type='BonusChange',
target_id=bonus.id,
target_name=f'{bonus.username} - {bonus.type}',
new_data=new_data,
remark=f'新增工资/奖金变更:{bonus.username},类型 {bonus.type},提交人 {bonus.submitter}'
)
return Response({
'message': '插入成功',
'code': 0,
'data': {
'id': bonus.id,
'username': bonus.username, # 变更对象(被变更工资/奖金的人员)
'submitter': bonus.submitter, # 提交人(明确是谁提交的申请)
'state': bonus.state,
'approval_id': approval.id if approval else None,
'needs_approval': True, # 工资/奖金变更统一都需要审批
'team_type': team.team_type if team else None, # 团队类型personal/team前端用这个字段判断
'team_name': team_name # 团队名称
}
}, status=status.HTTP_200_OK)
class ChangeDetail(APIView):
def post(self, request, *args, **kwargs):
"""
工资/奖金变更列表展示接口
必填参数page页码、per_page每页数量
可选参数times开始时间、end_time结束时间、username用户名搜索
"""
page = request.data.get('page')
per_page = request.data.get('per_page')
times = request.data.get('times')
end_time = request.data.get('end_time')
username = request.data.get('username')
# 验证必填参数
if not page:
return Response({
'status': 'error',
'message': '缺少必填参数: page(页码)',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
if not per_page:
return Response({
'status': 'error',
'message': '缺少必填参数: per_page(每页数量)',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 转换为整数
try:
page = int(page)
per_page = int(per_page)
except (ValueError, TypeError):
return Response({
'status': 'error',
'message': '参数类型错误: page和per_page必须是数字',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
Q_obj = Q()
if times and end_time:
Q_obj &= Q(times__gte=times) & Q(times__lte=end_time)
if username:
Q_obj &= Q(username__icontains=username)
bon = BonusChange.objects.filter(Q_obj, is_deleted=False).order_by('-id')
total = len(bon)
paginator = Paginator(bon, 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:
# 明确展示提交人优先使用submitter字段如果没有则使用username作为备选
submitter_name = info.submitter if info.submitter else info.username
itme = {
"id": info.id,
"times": info.times,
"type": info.type,
"Instructions": info.Instructions,
"username": info.username, # 变更对象(被变更工资/奖金的人员)
"submitter": submitter_name, # 提交人(明确是谁提交的申请)
"state": info.state, # 状态
}
data.append(itme)
return Response({'message': '展示成功', "total": total, 'data': data, 'code': 0}, status=status.HTTP_200_OK)
class EditBonusChange(APIView):
def post(self, request, *args, **kwargs):
"""
编辑工资/奖金变更
:param request:
:param args:
:param kwargs:
:return:
"""
id = request.data.get('id')
username = request.data.get('username')
type = request.data.get('type')
Instructions = request.data.get('Instructions')
if not id:
return Response({'status': 'error', 'message': '缺少参数id', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
try:
bonus = BonusChange.objects.get(id=id, is_deleted=False)
except BonusChange.DoesNotExist:
return Response({'status': 'error', 'message': '工资/奖金变更不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
update_fields_list = []
if username:
bonus.username = username
update_fields_list.append('username')
if type:
bonus.type = type
update_fields_list.append('type')
if Instructions:
bonus.Instructions = Instructions
update_fields_list.append('Instructions')
if update_fields_list:
bonus.save(update_fields=update_fields_list)
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
class DeleteBonusChange(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:
bonus = BonusChange.objects.get(id=id, is_deleted=False)
except BonusChange.DoesNotExist:
return Response({'status': 'error', 'message': '工资/奖金变更不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
# 软删除:更新 is_deleted 字段
bonus.is_deleted = True
bonus.save()
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
class UserDeparture(APIView):
def post(self, request, *args, **kwargs):
"""
离职财务登记
优化后:只需填写姓名和离职时间,其余字段从人事管理中同步
离职工资由审批人填写
如果用户有案件(作为承办人员),需要先转移案件才能完成离职登记
:param request:
:param args:
:param kwargs:
:return:
"""
username = request.data.get('username')
Dateofdeparture = request.data.get('Dateofdeparture') # 离职时间
approvers = request.data.get('approvers') # 审核人列表可选多人团队时需要推荐用户ID数组如[1,2,3],兼容:用户名数组)
# 兼容旧接口:如果传了 personincharge转换为 approvers
personincharge = request.data.get('personincharge')
approvers = normalize_approvers_param(approvers, personincharge)
# 只验证必填字段:姓名和离职时间
if not all([username, Dateofdeparture]):
return Response({'status': 'error', 'message': '缺少参数:姓名和离职时间不能为空', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
try:
user = User.objects.get(username=username, is_deleted=False)
except User.DoesNotExist:
return Response({'status': 'error', 'message': '用户不存在或已被删除', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
# 检查用户是否有案件(预立案 + 正式案件)
prefiling_cases = PreFiling.objects.filter(Undertaker=username, is_deleted=False)
prefiling_count = prefiling_cases.count()
def _user_in_responsible(responsiblefor_data, target_username):
if isinstance(responsiblefor_data, dict):
return target_username in responsiblefor_data.values()
return False
formal_case_list = []
formal_project_list = []
# 正式案件Case 表(责任人信息来自立项同步)
case_qs = Case.objects.filter(is_deleted=False, responsiblefor__icontains=username)
for case in case_qs:
try:
responsible_data = json.loads(case.responsiblefor) if case.responsiblefor else {}
except (json.JSONDecodeError, TypeError):
responsible_data = case.responsiblefor if case.responsiblefor else {}
if _user_in_responsible(responsible_data, username):
formal_case_list.append({
'id': case.id,
'project_id': case.project_id,
'contract_no': case.contract_no,
'project_type': case.project_type,
'times': case.times
})
# 立项登记:尚未生成案件但已绑定负责人
project_qs = ProjectRegistration.objects.filter(is_deleted=False, responsiblefor__icontains=username)
for project in project_qs:
try:
responsible_data = json.loads(project.responsiblefor) if project.responsiblefor else {}
except (json.JSONDecodeError, TypeError):
responsible_data = project.responsiblefor if project.responsiblefor else {}
if _user_in_responsible(responsible_data, username):
formal_project_list.append({
'id': project.id,
'contract_no': project.ContractNo,
'type': project.type,
'times': project.times
})
total_case_count = prefiling_count + len(formal_case_list) + len(formal_project_list)
if total_case_count > 0:
# 用户有案件,需要先转移案件才能离职
prefiling_list = []
for case in prefiling_cases:
prefiling_list.append({
'id': case.id,
'times': case.times,
'client_username': case.client_username,
'party_username': case.party_username,
'description': case.description
})
return Response({
'status': 'error',
'message': f'该用户还有{total_case_count}个案件未转移,请先转移案件后再进行离职登记',
'code': 1,
'data': {
'has_cases': True,
'case_count': total_case_count,
'prefiling_cases': prefiling_list,
'formal_cases': formal_case_list,
'formal_projects': formal_project_list
}
}, status=status.HTTP_400_BAD_REQUEST)
# 用户没有案件,可以正常离职登记
try:
Dateofdepartures = datetime.datetime.strptime(Dateofdeparture, "%Y-%m-%d")
except ValueError:
return Response({'status': 'error', 'message': '离职时间格式错误请使用YYYY-MM-DD格式', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
# 更新用户离职时间和状态
user.Dateofdeparture = Dateofdepartures
user.state = "已离职"
user.save(update_fields=['Dateofdeparture', 'state'])
# 根据团队类型判断是否需要审批
# 规则:
# - 个人团队personal/独立律师):不触发审批
# - 团队team/团队律师):需要审批,审批人需要指定结算工资
from User.models import Team
# 获取用户的团队信息
team_name = user.team # 用户的团队名称CharField
team = None
if team_name:
try:
team = Team.objects.get(name=team_name, is_deleted=False)
except Team.DoesNotExist:
# 如果团队不存在,默认按团队类型处理(需要审批)
pass
# 使用统一的审核流程函数
from User.utils import create_approval_with_team_logic
today = datetime.datetime.now()
formatted_date = today.strftime("%Y-%m-%d")
# 从人事管理同步用户信息,构建审批内容
# 包含用户的基本信息:姓名、岗位、部门、团队等
content_parts = [f"{user.username}{Dateofdeparture}办理离职登记"]
# 同步岗位信息
if user.position:
content_parts.append(f"岗位:{user.position}")
# 同步部门信息
departments = user.department.filter(is_deleted=False)
if departments.exists():
dept_names = [dept.username for dept in departments]
content_parts.append(f"部门:{', '.join(dept_names)}")
# 同步团队信息
if user.team:
content_parts.append(f"团队:{user.team}")
# 离职工资由审批人填写,初始状态为"待审批人指定"
content_parts.append("结算工资:待审批人指定")
content = "".join(content_parts)
approval, approvers_order_json, needs_approval = create_approval_with_team_logic(
team_name=team_name,
approvers=approvers,
title=user.username + "离职财务登记",
content=content,
approval_type="离职财务登记",
user_id=str(user.id),
business_record=user, # User对象没有approvers_order字段但可以更新state
today=formatted_date
)
# 如果返回None且需要审核说明缺少审核人
if approval is None and needs_approval:
return Response({
'status': 'error',
'message': build_missing_approvers_message(team_name, approvers),
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 如果是个人团队,不创建审批记录,但记录日志
if team and team.team_type == 'personal' and approval is None:
new_data = {
'user_id': user.id,
'username': user.username,
'Dateofdeparture': Dateofdeparture,
'state': user.state,
'team_type': 'personal',
'note': '个人团队,无需审批'
}
log_operation(
request=request,
operation_type='CREATE',
module='Finance',
action='新增离职财务登记(个人团队)',
target_type='User',
target_id=user.id,
target_name=user.username,
new_data=new_data,
remark=f'新增离职财务登记:{user.username},离职时间 {Dateofdeparture}(个人团队,无需审批)'
)
else:
# 如果创建了审批记录,记录操作日志
if approval:
new_data = {
'user_id': user.id,
'username': user.username,
'Dateofdeparture': Dateofdeparture,
'state': user.state,
'position': user.position,
'team': user.team,
'settlement_salary': '待审批人指定'
}
log_operation(
request=request,
operation_type='CREATE',
module='Finance',
action='新增离职财务登记',
target_type='User',
target_id=user.id,
target_name=user.username,
new_data=new_data,
remark=f'新增离职财务登记:{user.username},离职时间 {Dateofdeparture}'
)
return Response({
'message': '离职登记成功',
'code': 0,
'data': {
'has_cases': False,
'case_count': 0,
'needs_approval': team is None or team.team_type != 'personal' # 是否需要审批
}
}, status=status.HTTP_200_OK)
class UserDepartureDetail(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') # 用户名搜索
times = request.data.get('times') # 开始时间
end_time = request.data.get('end_time') # 结束时间
state = request.data.get('state') # 审批状态
if not all([page, per_page]):
return Response({'status': 'error', 'message': '缺少参数:页码和每页数量不能为空', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
# 查询离职财务登记的审批记录
Q_obj = Q(type="离职财务登记", is_deleted=False)
if username:
Q_obj &= Q(title__icontains=username) | Q(content__icontains=username)
if times and end_time:
Q_obj &= Q(times__gte=times) & Q(times__lte=end_time)
if state:
Q_obj &= Q(state=state)
approvals = Approval.objects.filter(Q_obj).order_by('-id')
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 approval in user_agents_page.object_list:
# 获取用户信息
try:
user = User.objects.get(id=int(approval.user_id), is_deleted=False)
user_info = {
'id': user.id,
'username': user.username,
'account': user.account,
'position': user.position,
'Dateofdeparture': user.Dateofdeparture.strftime("%Y-%m-%d") if user.Dateofdeparture else None,
'state': user.state
}
except (User.DoesNotExist, ValueError):
user_info = {
'id': None,
'username': '用户不存在',
'account': '',
'position': '',
'Dateofdeparture': None,
'state': ''
}
# 从content中解析结算工资
settlement_salary = None
if approval.content and "结算工资:" in approval.content:
import re
# 匹配格式结算工资xxx元 或 结算工资:待审批人指定
match = re.search(r'结算工资:([^]*)', approval.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)
item = {
'id': approval.id,
'title': approval.title,
'content': approval.content,
'times': approval.times.strftime("%Y-%m-%d") if approval.times else None,
'completeTiem': approval.completeTiem.strftime("%Y-%m-%d") if approval.completeTiem else None,
'personincharge': approval.personincharge,
'state': approval.state,
'type': approval.type,
'user_id': approval.user_id,
'user_info': user_info,
'settlement_salary': settlement_salary # 单独返回离职工资字段
}
data.append(item)
return Response({
'message': '查询成功',
'total': total,
'data': data,
'code': 0
}, status=status.HTTP_200_OK)
class EditUserDeparture(APIView):
def post(self, request, *args, **kwargs):
"""
编辑离职登记
:param request:
:param args:
:param kwargs:
:return:
"""
id = request.data.get('id') # 审批记录ID
Dateofdeparture = request.data.get('Dateofdeparture') # 离职时间
personincharge = request.data.get('personincharge') # 负责人
if not id:
return Response({'status': 'error', 'message': '缺少参数id', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
try:
approval = Approval.objects.get(id=id, type="离职财务登记", is_deleted=False)
except Approval.DoesNotExist:
return Response({'status': 'error', 'message': '离职登记记录不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
# 获取用户信息
try:
user = User.objects.get(id=int(approval.user_id), is_deleted=False)
except (User.DoesNotExist, ValueError):
return Response({'status': 'error', 'message': '用户不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
update_fields_list = []
approval_update_fields = []
# 更新离职时间
if Dateofdeparture:
try:
Dateofdepartures = datetime.datetime.strptime(Dateofdeparture, "%Y-%m-%d")
user.Dateofdeparture = Dateofdepartures
update_fields_list.append('Dateofdeparture')
# 更新审批记录内容
approval.content = user.username + "" + Dateofdeparture + "办理离职登记"
approval_update_fields.append('content')
except ValueError:
return Response({'status': 'error', 'message': '离职时间格式错误请使用YYYY-MM-DD格式', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
# 更新负责人
if personincharge:
approval.personincharge = personincharge
approval_update_fields.append('personincharge')
# 保存更新
if update_fields_list:
user.save(update_fields=update_fields_list)
if approval_update_fields:
approval.save(update_fields=approval_update_fields)
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
class DeleteUserDeparture(APIView):
def post(self, request, *args, **kwargs):
"""
删除离职登记
删除离职登记记录,并恢复用户状态为"在职",清空离职时间
:param request:
:param args:
:param kwargs:
:return:
"""
id = request.data.get('id') # 审批记录ID
if not id:
return Response({'status': 'error', 'message': '缺少参数id', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
try:
approval = Approval.objects.get(id=id, type="离职财务登记", is_deleted=False)
except Approval.DoesNotExist:
return Response({'status': 'error', 'message': '离职登记记录不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
# 获取用户信息
try:
user = User.objects.get(id=int(approval.user_id), is_deleted=False)
except (User.DoesNotExist, ValueError):
return Response({'status': 'error', 'message': '用户不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
# 记录操作前的数据
old_data = {
'user_id': user.id,
'username': user.username,
'state': user.state,
'dateofdeparture': str(user.Dateofdeparture) if user.Dateofdeparture else None,
'approval_id': approval.id
}
# 恢复用户状态
user.state = "在职"
user.Dateofdeparture = None
user.save(update_fields=['state', 'Dateofdeparture'])
# 软删除:更新 is_deleted 字段
approval.is_deleted = True
approval.save()
# 记录操作日志
log_operation(
request=request,
operation_type='DELETE',
module='Finance',
action='删除离职登记',
target_type='Approval',
target_id=approval.id,
target_name=f'离职登记-{user.username}',
old_data=old_data,
new_data={'user_id': user.id, 'username': user.username, 'state': '在职'},
remark=f'删除离职登记,恢复用户 {user.username} 状态为"在职"'
)
return Response({'message': '删除成功,用户状态已恢复为"在职"', 'code': 0}, status=status.HTTP_200_OK)
class SearchCaseByContractNo(APIView):
"""通过合同号搜索案件(用于增开票功能)"""
def post(self, request, *args, **kwargs):
"""
通过合同号搜索案件支持从Case和ProjectRegistration中搜索
返回案件信息,包括合同号、负责人、项目类型等
"""
contract_no = request.data.get('contract_no', '').strip()
if not contract_no:
return Response({
'status': 'error',
'message': '缺少参数contract_no合同号',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
from business.models import Case, ProjectRegistration
import json
results = []
# 1. 从Case案件管理中搜索
cases = Case.objects.filter(
contract_no__icontains=contract_no,
is_deleted=False
).select_related('project').order_by('-id')[:20] # 限制返回数量
for case in cases:
# 获取案件信息
case_contract_no = case.contract_no
case_responsiblefor = case.responsiblefor
case_project_type = case.project_type
case_client_name = case.client_name
case_party_name = case.party_name
case_project_description = case.project_description
case_charge = case.charge
# 如果案件信息不完整尝试从关联的ProjectRegistration获取
if case.project:
project = case.project
case_contract_no = case_contract_no or project.ContractNo
case_responsiblefor = case_responsiblefor or project.responsiblefor
case_project_type = case_project_type or project.type
case_client_name = case_client_name or project.client_info
case_party_name = case_party_name or project.party_info
case_project_description = case_project_description or project.description
case_charge = case_charge or project.charge
# 解析负责人信息
responsiblefor_dict = {}
responsible_person = ''
try:
if case_responsiblefor:
if isinstance(case_responsiblefor, str):
responsiblefor_dict = json.loads(case_responsiblefor)
else:
responsiblefor_dict = case_responsiblefor
responsible_person = responsiblefor_dict.get('responsible_person', '')
except (json.JSONDecodeError, TypeError, AttributeError):
responsible_person = str(case_responsiblefor) if case_responsiblefor else ''
results.append({
'id': case.id,
'type': 'case', # 标识这是案件管理中的案件
'contract_no': case_contract_no or '',
'responsiblefor': responsiblefor_dict if responsiblefor_dict else case_responsiblefor,
'responsible_person': responsible_person, # 负责人姓名(用于显示)
'project_type': case_project_type or '',
'client_name': case_client_name or '',
'party_name': case_party_name or '',
'project_description': case_project_description or '',
'charge': case_charge or '',
'times': case.times,
'project_id': case.project_id
})
# 2. 从ProjectRegistration立项登记中搜索如果还没有生成案件
projects = ProjectRegistration.objects.filter(
ContractNo__icontains=contract_no,
is_deleted=False
).order_by('-id')[:20]
# 过滤掉已经生成案件的立项登记
existing_project_ids = [case.project_id for case in cases if case.project_id]
projects = projects.exclude(id__in=existing_project_ids)
for project in projects:
# 解析负责人信息
responsiblefor_dict = {}
responsible_person = ''
try:
if project.responsiblefor:
if isinstance(project.responsiblefor, str):
responsiblefor_dict = json.loads(project.responsiblefor)
else:
responsiblefor_dict = project.responsiblefor
responsible_person = responsiblefor_dict.get('responsible_person', '')
except (json.JSONDecodeError, TypeError, AttributeError):
responsible_person = str(project.responsiblefor) if project.responsiblefor else ''
results.append({
'id': project.id,
'type': 'project', # 标识这是立项登记
'contract_no': project.ContractNo,
'responsiblefor': responsiblefor_dict if responsiblefor_dict else project.responsiblefor,
'responsible_person': responsible_person, # 负责人姓名(用于显示)
'project_type': project.type or '',
'client_name': project.client_info or '',
'party_name': project.party_info or '',
'project_description': project.description or '',
'charge': project.charge or '',
'times': project.times,
'project_id': None
})
return Response({
'message': '搜索成功',
'code': 0,
'data': results,
'total': len(results)
}, status=status.HTTP_200_OK)
class AddInvoice(APIView):
"""增开票功能:通过合同号搜索,提取案件管理板块内容,负责人直接同步,其余信息手填,填好后直接抄送财务"""
def post(self, request, *args, **kwargs):
"""
增开票申请
- 通过合同号搜索选择案件case_id或project_id
- 从案件管理中提取信息(合同号、负责人等)
- 负责人自动同步
- 其余信息手填
- 提交后直接抄送财务(不需要审批流程)
"""
# 案件选择(二选一)
case_id = request.data.get('case_id') # 案件管理ID
project_id = request.data.get('project_id') # 立项登记ID
contract_no = request.data.get('contract_no') # 合同号(用于搜索,可选)
# 手填信息
amount = request.data.get('amount') # 开票金额
type = request.data.get('type') # 开票类型
unit = request.data.get('unit') # 开票单位全称
number = request.data.get('number') # 纳税人识别号
address_telephone = request.data.get('address_telephone') # 地址/电话
bank = request.data.get('bank') # 银行卡
username = request.data.get('username') # 提交人
# 必填字段验证
missing_params = []
if not amount:
missing_params.append('amount(开票金额)')
if not type:
missing_params.append('type(开票类型)')
if not unit:
missing_params.append('unit(开票单位)')
if not number:
missing_params.append('number(纳税人识别号)')
if not address_telephone:
missing_params.append('address_telephone(地址/电话)')
if not bank:
missing_params.append('bank(银行卡)')
if not username:
missing_params.append('username(提交人)')
if missing_params:
return Response({
'status': 'error',
'message': f'缺少必填参数: {", ".join(missing_params)}',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 必须提供case_id或project_id之一
if not case_id and not project_id:
return Response({
'status': 'error',
'message': '请选择案件提供case_id案件管理ID或project_id立项登记ID',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
from business.models import Case, ProjectRegistration
import json
# 从案件管理中提取信息
ContractNo = None
personincharge = None
case_info = None
if case_id:
# 从Case案件管理中获取信息
try:
case = Case.objects.select_related('project').get(id=case_id, is_deleted=False)
# 获取合同号
ContractNo = case.contract_no
# 获取负责人信息
personincharge = case.responsiblefor
# 如果案件信息不完整从关联的ProjectRegistration获取
if case.project:
project = case.project
ContractNo = ContractNo or project.ContractNo
personincharge = personincharge or project.responsiblefor
# 解析负责人信息,提取负责人姓名用于显示
responsible_person = ''
try:
if personincharge:
if isinstance(personincharge, str):
responsiblefor_dict = json.loads(personincharge)
else:
responsiblefor_dict = personincharge
responsible_person = responsiblefor_dict.get('responsible_person', '')
except (json.JSONDecodeError, TypeError, AttributeError):
responsible_person = str(personincharge) if personincharge else ''
case_info = {
'id': case.id,
'type': 'case',
'contract_no': ContractNo,
'responsible_person': responsible_person
}
except Case.DoesNotExist:
return Response({
'status': 'error',
'message': '案件不存在或已被删除',
'code': 1
}, status=status.HTTP_404_NOT_FOUND)
elif project_id:
# 从ProjectRegistration立项登记中获取信息
try:
project = ProjectRegistration.objects.get(id=project_id, is_deleted=False)
ContractNo = project.ContractNo
personincharge = project.responsiblefor
# 解析负责人信息,提取负责人姓名用于显示
responsible_person = ''
try:
if personincharge:
if isinstance(personincharge, str):
responsiblefor_dict = json.loads(personincharge)
else:
responsiblefor_dict = personincharge
responsible_person = responsiblefor_dict.get('responsible_person', '')
except (json.JSONDecodeError, TypeError, AttributeError):
responsible_person = str(personincharge) if personincharge else ''
case_info = {
'id': project.id,
'type': 'project',
'contract_no': ContractNo,
'responsible_person': responsible_person
}
except ProjectRegistration.DoesNotExist:
return Response({
'status': 'error',
'message': '立项登记不存在或已被删除',
'code': 1
}, status=status.HTTP_404_NOT_FOUND)
# 验证合同号和负责人是否获取成功
if not ContractNo:
return Response({
'status': 'error',
'message': '无法从案件管理中获取合同号,请检查案件信息',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
if not personincharge:
return Response({
'status': 'error',
'message': '无法从案件管理中获取负责人信息,请检查案件信息',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 处理负责人信息提取负责人姓名确保不超过100字符限制
personincharge_display = ''
try:
if isinstance(personincharge, str):
# 尝试解析JSON
try:
responsiblefor_dict = json.loads(personincharge)
personincharge_display = responsiblefor_dict.get('responsible_person', '')
except (json.JSONDecodeError, TypeError):
# 如果不是JSON直接使用字符串
personincharge_display = personincharge
else:
# 如果是字典,直接提取
personincharge_display = personincharge.get('responsible_person', '') if isinstance(personincharge, dict) else str(personincharge)
except (AttributeError, TypeError):
personincharge_display = str(personincharge) if personincharge else ''
# 确保不超过100字符Invoice.personincharge字段限制
if len(personincharge_display) > 100:
personincharge_display = personincharge_display[:100]
# 如果提取失败使用原始值但需要确保是字符串且不超过100字符
if not personincharge_display:
personincharge_str = str(personincharge) if personincharge else ''
personincharge_display = personincharge_str[:100] if len(personincharge_str) > 100 else personincharge_str
# 创建开票记录
today = datetime.datetime.now()
formatted_date = today.strftime("%Y-%m-%d")
invoice = Invoice.objects.create(
ContractNo=ContractNo,
personincharge=personincharge_display, # 负责人姓名字符串不超过100字符
amount=amount,
type=type,
unit=unit,
number=number,
address_telephone=address_telephone,
bank=bank,
state="待财务处理", # 直接设为待财务处理,不需要审批
username=username,
times=formatted_date,
)
# 直接抄送财务,不需要审批流程
from User.utils import create_approval_with_team_logic
# 构建审批内容
responsible_person_display = personincharge_display # 使用处理后的负责人姓名
content_parts = [
f"{username}{formatted_date}提交了增开票申请",
f"合同编号:{ContractNo}",
f"负责人:{responsible_person_display}",
f"开票金额:{amount}",
f"开票类型:{type}",
f"开票单位:{unit}"
]
content = "".join(content_parts)
# 直接抄送财务team_name=None, approvers=None
approval, approvers_order_json, needs_approval = create_approval_with_team_logic(
team_name=None,
approvers=None,
title=username + "提交增开票申请",
content=content,
approval_type="增开票申请",
user_id=invoice.id,
business_record=invoice,
today=formatted_date
)
# 记录操作日志
new_data = {
'id': invoice.id,
'contract_no': invoice.ContractNo,
'personincharge': invoice.personincharge,
'amount': invoice.amount,
'type': invoice.type,
'unit': invoice.unit,
'case_id': case_id if case_id else None,
'project_id': project_id if project_id else None
}
log_operation(
request=request,
operation_type='CREATE',
module='Finance',
action='新增增开票申请',
target_type='Invoice',
target_id=invoice.id,
target_name=invoice.ContractNo,
new_data=new_data,
remark=f'新增增开票申请:合同号 {invoice.ContractNo},负责人 {responsible_person_display},金额 {invoice.amount},已直接抄送财务'
)
return Response({
'message': '提交成功,已直接抄送财务',
'code': 0,
'data': {
'id': invoice.id,
'ContractNo': invoice.ContractNo,
'personincharge': invoice.personincharge,
'responsible_person': responsible_person_display,
'state': invoice.state,
'approval_id': approval.id if approval else None
}
}, status=status.HTTP_200_OK)