3979 lines
166 KiB
Python
3979 lines
166 KiB
Python
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, # 案件管理ID(Case表)
|
||
"project_id": project_id, # 立项登记ID(ProjectRegistration表)
|
||
}
|
||
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. 支持通过案件ID(case_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') # 案件管理ID(Case表)
|
||
project_id = request.data.get('project_id') # 立项登记ID(ProjectRegistration表)
|
||
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)
|