Files
jyls_django/business/views.py

5457 lines
230 KiB
Python
Raw Normal View History

2025-12-24 13:51:20 +08:00
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
import json
import ast
2026-01-15 16:41:19 +08:00
import re
2026-01-15 16:47:13 +08:00
from decimal import Decimal, InvalidOperation
2026-01-15 14:34:22 +08:00
from User.models import User, Approval
2026-01-15 11:41:49 +08:00
from User.utils import log_operation, normalize_approvers_param, build_missing_approvers_message
2025-12-25 13:09:20 +08:00
from .models import PreFiling, ProjectRegistration, Bid, Case, Invoice, Caselog, SealApplication, Warehousing, \
2026-02-03 20:27:50 +08:00
RegisterPlatform, Announcement, LawyerFlie, Schedule, permission, role, CaseChangeRequest, CaseTag,Propaganda,System, ContractCounter
2026-02-04 14:03:15 +08:00
from .contract_no import generate_next_contract_no, get_next_contract_no_preview
2025-12-24 13:51:20 +08:00
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from utility.utility import flies
from datetime import datetime
from django.db.models import Count, Q
2026-01-04 17:49:16 +08:00
from django.db import transaction
import os
2025-12-25 13:09:20 +08:00
2026-01-15 11:41:49 +08:00
def get_user_team_name(username):
if not username:
return None
try:
user = User.objects.get(username=username, is_deleted=False)
return user.team
except User.DoesNotExist:
return None
def get_team_name_from_responsiblefor(responsiblefor):
if not responsiblefor:
return None
try:
data = json.loads(responsiblefor) if isinstance(responsiblefor, str) else responsiblefor
except (json.JSONDecodeError, TypeError):
return None
if isinstance(data, dict):
2026-01-15 17:40:56 +08:00
responsible_person = data.get('responsible_person')
if responsible_person is None:
return None
# 兼容负责人人员ID或用户名
if str(responsible_person).strip().isdigit():
try:
user = User.objects.get(id=int(responsible_person), is_deleted=False)
return user.team
except User.DoesNotExist:
return None
return get_user_team_name(responsible_person)
2026-01-15 11:41:49 +08:00
return None
2026-01-15 11:54:16 +08:00
2026-01-16 13:13:14 +08:00
def search_related_records(client_info, party_info, exclude_project_id=None):
"""
2026-01-16 13:25:11 +08:00
根据委托人和相对方信息检索预立案投标立项三个表的冲突记录
只返回委托人和相对方都匹配的记录冲突数据
2026-01-16 13:13:14 +08:00
Args:
client_info: 委托人身份信息字符串
party_info: 相对方身份信息字符串
exclude_project_id: 要排除的立项ID可选用于排除当前正在创建的记录
Returns:
2026-01-16 15:09:49 +08:00
dict: 包含三个表的冲突记录信息
{
'prefiling_conflicts': [...], # 预立案冲突记录列表
'project_conflicts': [...], # 立项冲突记录列表
'bid_conflicts': [...] # 投标冲突记录列表
}
2026-01-16 13:13:14 +08:00
"""
2026-01-16 15:09:49 +08:00
result = {
'prefiling_conflicts': [],
'project_conflicts': [],
'bid_conflicts': []
}
2026-01-16 13:13:14 +08:00
if not client_info and not party_info:
2026-01-16 15:09:49 +08:00
return result
2026-01-16 13:13:14 +08:00
2026-01-20 19:30:36 +08:00
# 解析委托人信息和相对方信息
client_parsed = parse_person_info(client_info) if client_info else {'name': '', 'id_number': '', 'original': ''}
party_parsed = parse_person_info(party_info) if party_info else {'name': '', 'id_number': '', 'original': ''}
2026-01-16 13:13:14 +08:00
2026-01-20 19:30:36 +08:00
# 提取姓名/名称(用于匹配)
client_name = client_parsed['name']
client_id_number = client_parsed['id_number']
client_original = client_parsed['original']
2026-01-16 13:13:14 +08:00
2026-01-20 19:30:36 +08:00
party_name = party_parsed['name']
party_id_number = party_parsed['id_number']
party_original = party_parsed['original']
2026-01-16 13:13:14 +08:00
2026-01-16 15:55:14 +08:00
# 检索预立案表 - 查找委托人和相对方都匹配的记录(冲突数据)
# 注意:预立案和立项是完全独立的,没有关联关系,只根据委托人和相对方信息匹配
2026-01-16 16:07:06 +08:00
# 预立案表字段client_username/party_usernameJSON数组格式字符串[{"index":1,"name":"张三","idNumber":"110101199001011234"}]
2026-01-16 15:55:14 +08:00
# 立项表字段client_info/party_info完整身份信息如"张三身份证号110101199001011234"
#
# 冲突判断逻辑:
# 1. 委托人和相对方都匹配(顺序相同或相反)
# 2. 预立案中委托人和相对方是同一个人,且这个人在立项的委托人或相对方中
# 3. 立项中委托人和相对方是同一个人,且这个人在预立案的委托人或相对方中
2026-01-16 15:44:55 +08:00
if (client_info or client_name) and (party_info or party_name):
matched_prefilings = []
2026-01-16 13:13:14 +08:00
2026-01-16 15:55:14 +08:00
# 检查立项中委托人和相对方是否是同一个人
is_same_person_in_project = False
if client_name and party_name and client_name.strip() == party_name.strip():
is_same_person_in_project = True
2026-01-20 19:30:36 +08:00
elif client_id_number and party_id_number and client_id_number == party_id_number:
# 通过身份证号判断是否为同一人
is_same_person_in_project = True
2026-01-16 15:55:14 +08:00
2026-01-16 16:07:06 +08:00
# 遍历预立案记录解析JSON格式并匹配
prefiling_all = PreFiling.objects.filter(is_deleted=False)
for pf in prefiling_all[:100]: # 限制查询数量以提高性能
if len(matched_prefilings) >= 10:
break
2026-01-16 15:55:14 +08:00
2026-01-16 16:07:06 +08:00
if not pf.client_username or not pf.party_username:
continue
2026-01-16 15:55:14 +08:00
2026-01-16 16:07:06 +08:00
# 解析预立案的client_usernameJSON数组格式
pf_client_names = []
try:
client_data = json.loads(pf.client_username) if isinstance(pf.client_username, str) else pf.client_username
if isinstance(client_data, list):
for item in client_data:
if isinstance(item, dict) and 'name' in item:
pf_client_names.append(item['name'].strip())
elif isinstance(client_data, dict) and 'name' in client_data:
pf_client_names.append(client_data['name'].strip())
except (json.JSONDecodeError, TypeError, AttributeError):
# 如果不是JSON格式尝试直接使用字符串兼容旧数据
pf_client_names = [pf.client_username.strip()]
2026-01-16 15:55:14 +08:00
2026-01-16 16:07:06 +08:00
# 解析预立案的party_usernameJSON数组格式
pf_party_names = []
try:
party_data = json.loads(pf.party_username) if isinstance(pf.party_username, str) else pf.party_username
if isinstance(party_data, list):
for item in party_data:
if isinstance(item, dict) and 'name' in item:
pf_party_names.append(item['name'].strip())
elif isinstance(party_data, dict) and 'name' in party_data:
pf_party_names.append(party_data['name'].strip())
except (json.JSONDecodeError, TypeError, AttributeError):
# 如果不是JSON格式尝试直接使用字符串兼容旧数据
pf_party_names = [pf.party_username.strip()]
2026-01-16 15:44:55 +08:00
2026-01-16 16:07:06 +08:00
if not pf_client_names or not pf_party_names:
continue
# 检查预立案中委托人和相对方是否是同一个人(所有委托人和所有相对方中有相同的名字)
is_same_person_in_prefiling = bool(set(pf_client_names) & set(pf_party_names))
# 对每个委托人名字和相对方名字进行匹配
client_match = False
party_match = False
reverse_client_match = False
reverse_party_match = False
# 检查正向匹配:预立案的委托人是否在立项的委托人信息中,预立案的相对方是否在立项的相对方信息中
for pf_client in pf_client_names:
2026-01-20 19:30:36 +08:00
if client_name and pf_client in client_name:
2026-01-16 15:55:14 +08:00
client_match = True
2026-01-16 16:07:06 +08:00
break
2026-01-20 19:30:36 +08:00
elif client_original and pf_client in client_original:
2026-01-16 15:55:14 +08:00
client_match = True
2026-01-16 16:07:06 +08:00
break
for pf_party in pf_party_names:
2026-01-20 19:30:36 +08:00
if party_name and pf_party in party_name:
2026-01-16 15:55:14 +08:00
party_match = True
2026-01-16 16:07:06 +08:00
break
2026-01-20 19:30:36 +08:00
elif party_original and pf_party in party_original:
2026-01-16 15:55:14 +08:00
party_match = True
2026-01-16 16:07:06 +08:00
break
# 检查反向匹配:预立案的委托人是否在立项的相对方信息中,预立案的相对方是否在立项的委托人信息中
for pf_client in pf_client_names:
2026-01-20 19:30:36 +08:00
if party_name and pf_client in party_name:
2026-01-16 15:55:14 +08:00
reverse_client_match = True
2026-01-16 16:07:06 +08:00
break
2026-01-20 19:30:36 +08:00
elif party_original and pf_client in party_original:
2026-01-16 15:55:14 +08:00
reverse_client_match = True
2026-01-16 16:07:06 +08:00
break
for pf_party in pf_party_names:
2026-01-20 19:30:36 +08:00
if client_name and pf_party in client_name:
2026-01-16 15:55:14 +08:00
reverse_party_match = True
2026-01-16 16:07:06 +08:00
break
2026-01-20 19:30:36 +08:00
elif client_original and pf_party in client_original:
2026-01-16 15:55:14 +08:00
reverse_party_match = True
2026-01-16 16:07:06 +08:00
break
# 判断是否冲突:
# 1. 正向匹配或反向匹配都成功
# 2. 预立案中委托人和相对方是同一个人,且这个人在立项的委托人或相对方信息中
# 3. 立项中委托人和相对方是同一个人,且这个人在预立案的委托人或相对方信息中
is_conflict = False
if (client_match and party_match) or (reverse_client_match and reverse_party_match):
is_conflict = True
elif is_same_person_in_prefiling:
# 预立案中委托人和相对方是同一个人,检查这个共同的名字是否在立项信息中
common_names = set(pf_client_names) & set(pf_party_names)
for common_name in common_names:
2026-01-20 19:30:36 +08:00
if (client_name and common_name in client_name) or (party_name and common_name in party_name) or \
(client_original and common_name in client_original) or (party_original and common_name in party_original):
2026-01-16 16:07:06 +08:00
is_conflict = True
break
elif is_same_person_in_project:
# 立项中委托人和相对方是同一个人,检查这个共同的名字是否在预立案信息中
for pf_client in pf_client_names:
2026-01-20 19:30:36 +08:00
if (client_name and pf_client in client_name) or (party_name and pf_client in party_name) or \
(client_original and pf_client in client_original) or (party_original and pf_client in party_original):
2026-01-16 15:55:14 +08:00
is_conflict = True
2026-01-16 16:07:06 +08:00
break
if not is_conflict:
for pf_party in pf_party_names:
2026-01-20 19:30:36 +08:00
if (client_name and pf_party in client_name) or (party_name and pf_party in party_name) or \
(client_original and pf_party in client_original) or (party_original and pf_party in party_original):
2026-01-16 16:07:06 +08:00
is_conflict = True
break
if is_conflict:
matched_prefilings.append({
'id': pf.id,
'times': pf.times,
'client_username': pf.client_username,
'party_username': pf.party_username,
'description': pf.description
})
2026-01-16 15:44:55 +08:00
result['prefiling_conflicts'] = matched_prefilings
2026-01-16 13:13:14 +08:00
2026-01-16 13:25:11 +08:00
# 检索立项表(排除当前正在创建的记录)- 只查找委托人和相对方都匹配的记录(冲突数据)
if client_info and party_info:
2026-01-16 13:13:14 +08:00
project_records = ProjectRegistration.objects.filter(is_deleted=False)
if exclude_project_id:
project_records = project_records.exclude(id=exclude_project_id)
2026-01-16 13:25:11 +08:00
# 委托人和相对方都匹配,或者相对方和委托人都匹配(可能是顺序相反)
2026-01-20 19:30:36 +08:00
# 支持姓名和完整信息匹配
project_q = Q()
if client_name and party_name:
project_q |= (Q(client_info__icontains=client_name) & Q(party_info__icontains=party_name))
project_q |= (Q(client_info__icontains=party_name) & Q(party_info__icontains=client_name))
if client_original and party_original:
project_q |= (Q(client_info__icontains=client_original[:50]) & Q(party_info__icontains=party_original[:50]))
project_q |= (Q(client_info__icontains=party_original[:50]) & Q(party_info__icontains=client_original[:50]))
if client_id_number and party_id_number:
project_q |= (Q(client_info__icontains=client_id_number) & Q(party_info__icontains=party_id_number))
project_q |= (Q(client_info__icontains=party_id_number) & Q(party_info__icontains=client_id_number))
if project_q:
project_records = project_records.filter(project_q)
2026-01-16 13:13:14 +08:00
# 获取项目列表最多10条
2026-01-16 13:25:11 +08:00
project_list = list(project_records[:10].values('id', 'ContractNo', 'times', 'type', 'client_info', 'party_info'))
2026-01-16 15:09:49 +08:00
result['project_conflicts'] = project_list
2026-01-16 13:13:14 +08:00
2026-01-16 15:19:05 +08:00
# 检索投标表 - 投标表只有BiddingUnit字段需要检查是否包含委托人或相对方信息
# BiddingUnit可能同时包含委托人和相对方或者只包含其中一方
bid_records = Bid.objects.filter(is_deleted=False)
bid_q = Q()
# 如果有提取出的名称,优先使用名称匹配
if client_name:
bid_q |= Q(BiddingUnit__icontains=client_name)
if party_name:
bid_q |= Q(BiddingUnit__icontains=party_name)
2026-01-20 19:30:36 +08:00
# 如果有完整信息,也尝试匹配
if client_original:
bid_q |= Q(BiddingUnit__icontains=client_original[:50])
if party_original:
bid_q |= Q(BiddingUnit__icontains=party_original[:50])
2026-01-16 15:19:05 +08:00
2026-01-20 19:30:36 +08:00
# 如果有身份证号,也尝试匹配
if client_id_number:
bid_q |= Q(BiddingUnit__icontains=client_id_number)
if party_id_number:
bid_q |= Q(BiddingUnit__icontains=party_id_number)
2026-01-16 15:19:05 +08:00
# 如果构建了查询条件,执行查询
if bid_q:
bid_records = bid_records.filter(bid_q)
2026-01-16 13:25:11 +08:00
bid_list = list(bid_records[:10].values('id', 'ProjectName', 'times', 'BiddingUnit'))
2026-01-16 15:09:49 +08:00
result['bid_conflicts'] = bid_list
2026-01-16 13:13:14 +08:00
2026-01-16 15:09:49 +08:00
return result
2026-01-16 13:13:14 +08:00
2026-01-15 14:34:22 +08:00
def get_change_approvers():
"""获取变更审批人(管委会或行政部)"""
try:
approvers = (
User.objects.filter(is_deleted=False)
.filter(
Q(role__RoleName__in=["管委会", "行政部"])
| Q(department__username="行政部")
)
.distinct()
.order_by("id")
)
return [user.username for user in approvers if user.username]
except Exception:
return []
def build_change_approval_content(applicant_name, case, change_item, change_reason, agreement_urls):
agreement_display = "".join(agreement_urls) if agreement_urls else ""
return (
f"{applicant_name}提交案件变更申请,"
f"案件ID{case.id}"
f"变更事项:{change_item}"
f"变更原因:{change_reason}"
f"变更协议:{agreement_display}"
)
2026-01-15 11:54:16 +08:00
def build_case_approval_content(project_registration, times, change_request=False):
times_str = times or "未填写日期"
content_parts = [f"{times_str}提交了一份案件信息"]
if change_request:
content_parts[0] = f"{times_str}提交了一份案件信息,更改了变更申请"
if project_registration:
if project_registration.ContractNo:
content_parts.append(f"合同编号:{project_registration.ContractNo}")
if project_registration.type:
content_parts.append(f"项目类型:{project_registration.type}")
if project_registration.client_info:
2026-01-21 16:10:36 +08:00
# 解析客户名称如果是JSON格式只提取name字段
client_info_str = project_registration.client_info
try:
# 尝试解析为JSON数组
client_info_list = json.loads(client_info_str)
if isinstance(client_info_list, list) and len(client_info_list) > 0:
# 提取所有name字段用逗号连接
client_names = [item.get('name', '') for item in client_info_list if isinstance(item, dict) and item.get('name')]
client_info_str = ''.join(client_names) if client_names else client_info_str
except (json.JSONDecodeError, TypeError, AttributeError):
# 如果不是JSON格式保持原样
pass
content_parts.append(f"客户名称:{client_info_str}")
2026-01-15 11:54:16 +08:00
if project_registration.party_info:
2026-01-21 16:10:36 +08:00
# 解析相对方名称如果是JSON格式只提取name字段
party_info_str = project_registration.party_info
try:
# 尝试解析为JSON数组
party_info_list = json.loads(party_info_str)
if isinstance(party_info_list, list) and len(party_info_list) > 0:
# 提取所有name字段用逗号连接
party_names = [item.get('name', '') for item in party_info_list if isinstance(item, dict) and item.get('name')]
party_info_str = ''.join(party_names) if party_names else party_info_str
except (json.JSONDecodeError, TypeError, AttributeError):
# 如果不是JSON格式保持原样
pass
content_parts.append(f"相对方名称:{party_info_str}")
2026-01-15 11:54:16 +08:00
if project_registration.description:
content_parts.append(f"项目简述:{project_registration.description}")
try:
responsiblefor_dict = json.loads(project_registration.responsiblefor) if project_registration.responsiblefor else {}
except (json.JSONDecodeError, TypeError):
responsiblefor_dict = project_registration.responsiblefor if project_registration.responsiblefor else {}
if isinstance(responsiblefor_dict, dict) and responsiblefor_dict.get("responsible_person"):
2026-01-30 18:05:41 +08:00
content_parts.append(f"承办人:{responsiblefor_dict.get('responsible_person')}")
2026-01-15 11:54:16 +08:00
if project_registration.charge:
content_parts.append(f"收费情况:{project_registration.charge}")
if change_request:
content_parts.append("请审核变更申请")
else:
content_parts.append("请审核")
return "".join(content_parts)
2026-01-15 16:47:13 +08:00
def normalize_amount_value(value):
if value is None:
return "0"
if isinstance(value, (int, float, Decimal)):
return str(value)
value_str = str(value).strip()
if value_str in ["", "未开票", "None", "null"]:
return "0"
numbers = re.findall(r"\d+\.?\d*", value_str)
return numbers[0] if numbers else "0"
2025-12-24 13:51:20 +08:00
class registration(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
预立案登记
:param request:
:param args:
:param kwargs:
:return:
"""
token = request.META.get('token')
times = request.data.get('times')
client_username = request.data.get('client_username')
party_username = request.data.get('party_username')
description = request.data.get('description')
Undertaker = request.data.get('Undertaker')
2026-01-12 17:32:15 +08:00
user = User.objects.get(token=token, is_deleted=False).username
2025-12-25 13:09:20 +08:00
if not all([times, description, Undertaker]):
2025-12-24 13:51:20 +08:00
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2025-12-31 12:28:10 +08:00
prefiling = PreFiling.objects.create(
2025-12-24 13:51:20 +08:00
times=times,
client_username=client_username,
party_username=party_username,
description=description,
Undertaker=Undertaker,
submit=user
)
2026-01-04 15:44:07 +08:00
2025-12-31 12:28:10 +08:00
# 记录操作日志
new_data = {
'id': prefiling.id,
'client_username': prefiling.client_username,
'party_username': prefiling.party_username,
'undertaker': prefiling.Undertaker
}
log_operation(
request=request,
operation_type='CREATE',
module='Business',
action='新增预立案登记',
target_type='PreFiling',
target_id=prefiling.id,
target_name=f'{prefiling.client_username} vs {prefiling.party_username}',
new_data=new_data,
remark=f'新增预立案登记:委托人 {prefiling.client_username},相对方 {prefiling.party_username}'
)
2026-01-04 15:44:07 +08:00
2025-12-24 13:51:20 +08:00
return Response({'message': '登记成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-24 13:51:20 +08:00
class registrationList(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
预立案登记列表接口
2026-01-14 15:04:44 +08:00
注意由于预立案和立项登记投标登记已经拆分现在返回所有未删除的预立案
2025-12-24 13:51:20 +08:00
:param request:
:param args:
:param kwargs:
:return:
"""
2026-01-14 15:04:44 +08:00
# 由于预立案和立项登记、投标登记已经拆分,不再需要检查关联
# 直接返回所有未删除的预立案
prefiling_list = PreFiling.objects.filter(is_deleted=False)
2025-12-24 13:51:20 +08:00
data = []
2026-01-14 15:04:44 +08:00
for prefiling in prefiling_list:
2025-12-24 13:51:20 +08:00
data.append({
"id": prefiling.id,
"client_username": prefiling.client_username,
"party_username": prefiling.party_username,
})
2025-12-25 13:09:20 +08:00
return Response({'message': '展示成功', 'data': data, 'code': 0}, status=status.HTTP_200_OK)
2025-12-24 13:51:20 +08:00
class registrationDetail(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
预立案登记展示
:param request:
:param args:
:param kwargs:
:return:
"""
page = request.data.get('page')
per_page = request.data.get('per_page')
times = request.data.get('times')
end_time = request.data.get('end_time')
Undertaker = request.data.get('Undertaker')
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 Undertaker:
Q_obj &= Q(Undertaker__icontains=Undertaker)
2025-12-31 11:42:28 +08:00
pre = PreFiling.objects.filter(Q_obj, is_deleted=False).order_by('-id')
2025-12-24 13:51:20 +08:00
total = len(pre)
paginator = Paginator(pre, per_page)
try:
user_agents_page = paginator.page(page)
except PageNotAnInteger:
user_agents_page = paginator.page(1)
except EmptyPage:
user_agents_page = paginator.page(paginator.num_pages)
data = []
for info in user_agents_page.object_list:
data.append({
"id": info.id,
'times': info.times,
"client_username": info.client_username,
"party_username": info.party_username,
"description": info.description,
"Undertaker": info.Undertaker,
})
2025-12-25 13:09:20 +08:00
return Response({'message': '展示成功', "total": total, 'data': data, 'code': 0}, status=status.HTTP_200_OK)
2025-12-24 13:51:20 +08:00
class EditRegistration(APIView):
def post(self, request, *args, **kwargs):
"""
编辑预立案登记
:param request:
:param args:
:param kwargs:
:return:
"""
id = request.data.get('id')
times = request.data.get('times')
client_username = request.data.get('client_username')
party_username = request.data.get('party_username')
description = request.data.get('description')
Undertaker = request.data.get('Undertaker')
2026-01-04 15:44:07 +08:00
if not id:
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2026-01-04 15:44:07 +08:00
try:
2025-12-31 14:17:33 +08:00
pre = PreFiling.objects.get(id=id, is_deleted=False)
except PreFiling.DoesNotExist:
2026-01-04 15:44:07 +08:00
return Response({'status': 'error', 'message': '预立案登记不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
# 更新字段(如果提供了值)
if times:
pre.times = times
pre.save(update_fields=['times'])
if client_username:
pre.client_username = client_username
pre.save(update_fields=['client_username'])
if party_username:
pre.party_username = party_username
pre.save(update_fields=['party_username'])
if description:
pre.description = description
pre.save(update_fields=['description'])
if Undertaker:
pre.Undertaker = Undertaker
pre.save(update_fields=['Undertaker'])
2026-01-04 15:44:07 +08:00
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-24 13:51:20 +08:00
class Project(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
2026-01-30 15:36:57 +08:00
立项登记 - 独立创建不再需要预立案
2026-02-04 14:03:15 +08:00
必填项目类型立项日期项目简述收费情况承办人信息合同
合同编号(ContractNo)可选不传或传空时由后端按规则自动生成校准()年份第n号
2026-01-30 15:36:57 +08:00
相对方(party_info)为非必填项委托人(client_info)可为空
2025-12-24 13:51:20 +08:00
:param request:
:param args:
:param kwargs:
:return:
"""
2026-01-15 17:34:04 +08:00
project_type = request.data.get('type')
2025-12-24 13:51:20 +08:00
ContractNo = request.data.get('ContractNo')
2026-02-04 14:03:15 +08:00
if ContractNo is not None and not isinstance(ContractNo, str):
ContractNo = str(ContractNo)
2025-12-24 13:51:20 +08:00
times = request.data.get('times')
2026-01-30 15:36:57 +08:00
client_info = request.data.get('client_info') # 委托人身份信息(可选)
party_info = request.data.get('party_info') # 相对方身份信息(非必填)
2026-01-14 15:04:44 +08:00
description = request.data.get('description') # 项目简述
2026-01-30 18:05:41 +08:00
responsiblefor = request.data.get('responsiblefor') # 承办人信息(字典格式)
2025-12-24 13:51:20 +08:00
charge = request.data.get('charge')
contract = request.FILES.getlist('contract')
2026-01-12 23:17:39 +08:00
approvers = request.data.get('approvers') # 审核人列表可选多人团队时需要推荐用户ID数组如[1,2,3],兼容:用户名数组)
2026-01-15 11:41:49 +08:00
# 兼容旧接口:如果传了 personincharge转换为 approvers
personincharge = request.data.get('personincharge')
2026-01-15 17:29:38 +08:00
2026-01-15 11:41:49 +08:00
approvers = normalize_approvers_param(approvers, personincharge)
2025-12-24 13:51:20 +08:00
import datetime
2026-01-14 15:40:39 +08:00
import json
2025-12-24 13:51:20 +08:00
2026-01-14 16:11:26 +08:00
# 明确拒绝 user_id 参数(此接口不再需要预立案,不需要 user_id
if 'user_id' in request.data:
return Response({'status': 'error', 'message': '此接口不需要 user_id 参数,立项登记已独立创建', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2026-02-04 14:03:15 +08:00
# 验证必填字段(合同编号可选,不传则后端自动生成)
if not all([project_type, times, description, charge]):
return Response({'status': 'error', 'message': '缺少必填参数(项目类型、立项日期、项目简述、收费情况)', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2026-01-04 17:35:51 +08:00
2026-01-30 18:05:41 +08:00
# 验证承办人信息(字典格式)
2026-01-14 15:40:39 +08:00
if not responsiblefor:
2026-01-30 18:05:41 +08:00
return Response({'status': 'error', 'message': '承办人信息不能为空', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2026-01-14 15:40:39 +08:00
2026-01-30 18:05:41 +08:00
# 解析承办人信息支持字符串JSON或字典
2026-01-14 15:40:39 +08:00
try:
if isinstance(responsiblefor, str):
responsiblefor_dict = json.loads(responsiblefor)
else:
responsiblefor_dict = responsiblefor
2026-01-30 18:05:41 +08:00
# 验证承办人responsible_person必填
2026-01-14 15:53:41 +08:00
if not responsiblefor_dict.get('responsible_person'):
2026-01-30 18:05:41 +08:00
return Response({'status': 'error', 'message': '承办人不能为空', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2026-01-14 15:40:39 +08:00
# 将字典转换为JSON字符串存储
responsiblefor_str = json.dumps(responsiblefor_dict, ensure_ascii=False)
except (json.JSONDecodeError, TypeError, AttributeError):
2026-01-30 18:05:41 +08:00
return Response({'status': 'error', 'message': '承办人信息格式错误,应为字典格式', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2026-01-14 15:40:39 +08:00
2025-12-24 13:51:20 +08:00
if contract:
contract = flies(contract)
2025-12-25 13:09:20 +08:00
else:
contract = []
# 将文件URL列表转换为字符串存储如果有多个URL用逗号分隔
if contract:
# 如果只有一个URL直接存储如果有多个用逗号分隔
contract_str = contract[0] if len(contract) == 1 else ','.join(contract)
else:
contract_str = ""
2025-12-24 13:51:20 +08:00
2026-02-04 14:03:15 +08:00
need_auto_contract_no = not (ContractNo and ContractNo.strip())
if not need_auto_contract_no:
# 用户传入合同编号:检查是否已存在
existing_project = ProjectRegistration.objects.filter(ContractNo=ContractNo.strip(), is_deleted=False).first()
if existing_project:
return Response({'status': 'error', 'message': '该合同编号已存在,不能重复创建', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
contract_year = int(times[:4]) if times and len(times) >= 4 else datetime.now().year
2025-12-24 13:51:20 +08:00
2026-02-03 20:27:50 +08:00
# 使用事务确保创建立项和更新计数器是原子操作
with transaction.atomic():
2026-02-04 14:03:15 +08:00
if need_auto_contract_no:
ContractNo = generate_next_contract_no(project_type, contract_year)
if not ContractNo:
return Response({
'status': 'error',
'message': f'项目类型「{project_type}」未配置合同编号规则,无法自动生成。请手动填写合同编号,或选择已配置的类型(如:法律顾问、专项服务、民事案件代理等)',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
2026-02-03 20:27:50 +08:00
# 创建立项登记(相对方可为空,空字符串统一为 None
pro = ProjectRegistration.objects.create(
type=project_type,
2026-02-04 14:03:15 +08:00
ContractNo=ContractNo.strip() if isinstance(ContractNo, str) else ContractNo,
2026-02-03 20:27:50 +08:00
times=times,
client_info=client_info or None,
party_info=party_info if party_info not in (None, '') else None,
description=description,
responsiblefor=responsiblefor_str,
charge=charge,
contract=contract_str,
state="审核中",
)
2026-02-04 14:03:15 +08:00
# 仅当用户手动传入合同编号时,更新计数器(与自动生成逻辑一致,保证序号连续)
if not need_auto_contract_no:
try:
counter, created = ContractCounter.objects.select_for_update().get_or_create(
project_type=project_type,
year=contract_year,
defaults={'current_number': 1}
)
if not created:
counter.current_number += 1
counter.save(update_fields=['current_number', 'updated_at'])
except Exception as e:
import logging
logging.warning(f"更新合同编号计数器失败: {e}")
2026-01-14 15:04:44 +08:00
2025-12-24 13:51:20 +08:00
today = datetime.datetime.now()
formatted_date = today.strftime("%Y-%m-%d")
2026-01-12 22:20:36 +08:00
2026-01-30 18:05:41 +08:00
# 获取团队信息:优先承办人,其次当前登录用户
2026-01-15 17:40:56 +08:00
team_name = get_team_name_from_responsiblefor(responsiblefor_dict)
2026-01-30 15:47:25 +08:00
request_user = None
token = request.META.get('token') or request.META.get('HTTP_AUTHORIZATION', '').replace('Bearer ', '')
if token:
try:
request_user = User.objects.get(token=token, is_deleted=False)
if not team_name:
2026-01-15 17:40:56 +08:00
team_name = request_user.team
2026-01-30 15:47:25 +08:00
except User.DoesNotExist:
pass
2026-01-12 22:20:36 +08:00
# 使用统一的审核流程函数
from User.utils import create_approval_with_team_logic
2026-01-30 18:05:41 +08:00
# 构建承办人信息描述
2026-01-14 15:53:41 +08:00
responsible_desc = responsiblefor_dict.get('responsible_person', '')
if responsiblefor_dict.get('main_lawyer'):
responsible_desc += f",主办律师:{responsiblefor_dict.get('main_lawyer')}"
if responsiblefor_dict.get('assistant_lawyer'):
2026-01-30 18:05:41 +08:00
responsible_desc += f",协办律师:{responsiblefor_dict.get('assistant_lawyer')}"
2026-01-14 15:53:41 +08:00
if responsiblefor_dict.get('case_manager_lawyer'):
responsible_desc += f",案管律师:{responsiblefor_dict.get('case_manager_lawyer')}"
2026-01-14 15:40:39 +08:00
2026-02-02 16:58:33 +08:00
# 检索相关记录(预立案、投标、立项)- 使用 conflict_search 函数,支持更灵活的参数组合
related_records_info = conflict_search(client_info=client_info, party_info=party_info, exclude_project_id=pro.id)
2026-01-16 13:13:14 +08:00
2026-01-16 15:09:49 +08:00
# 构建冲突信息文本用于content字段保持向后兼容
conflict_parts = []
if related_records_info['prefiling_conflicts']:
conflict_parts.append(f"预立案冲突:{len(related_records_info['prefiling_conflicts'])}ID{','.join([str(r['id']) for r in related_records_info['prefiling_conflicts'][:5]])}{'...' if len(related_records_info['prefiling_conflicts']) > 5 else ''}")
if related_records_info['project_conflicts']:
conflict_parts.append(f"立项冲突:{len(related_records_info['project_conflicts'])}条(合同编号:{','.join([r['ContractNo'] for r in related_records_info['project_conflicts'][:3]])}{'...' if len(related_records_info['project_conflicts']) > 3 else ''}")
if related_records_info['bid_conflicts']:
conflict_parts.append(f"投标冲突:{len(related_records_info['bid_conflicts'])}条(项目:{','.join([r['ProjectName'][:20] if r['ProjectName'] else '' for r in related_records_info['bid_conflicts'][:3]])}{'...' if len(related_records_info['bid_conflicts']) > 3 else ''}")
conflict_text = ";冲突记录:" + "".join(conflict_parts) if conflict_parts else ""
2026-01-30 15:47:25 +08:00
applicant_name = request_user.username if request_user else ""
content = f"{responsiblefor_dict.get('responsible_person')}{times}办理立项登记,项目类型:{project_type},合同编号:{ContractNo}{responsible_desc},收费情况:{charge},申请人:{applicant_name}{conflict_text}"
2026-01-12 22:20:36 +08:00
approval, approvers_order_json, needs_approval = create_approval_with_team_logic(
team_name=team_name,
approvers=approvers,
2026-01-14 15:53:41 +08:00
title=responsiblefor_dict.get('responsible_person', '') + "立项登记",
2026-01-12 22:20:36 +08:00
content=content,
approval_type="立项登记",
user_id=pro.id,
business_record=pro,
2026-01-30 16:04:40 +08:00
today=formatted_date,
applicant=applicant_name
2025-12-24 13:51:20 +08:00
)
2026-01-12 22:20:36 +08:00
# 如果返回None且需要审核说明缺少审核人
if approval is None and needs_approval:
return Response({
'status': 'error',
2026-01-15 11:41:49 +08:00
'message': build_missing_approvers_message(team_name, approvers),
2026-01-12 22:20:36 +08:00
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
2026-01-04 15:44:07 +08:00
2025-12-31 12:28:10 +08:00
# 记录操作日志
new_data = {
'id': pro.id,
'contract_no': pro.ContractNo,
'type': pro.type,
2026-01-14 15:40:39 +08:00
'responsiblefor': responsiblefor_dict,
2025-12-31 12:28:10 +08:00
'times': pro.times
}
log_operation(
request=request,
operation_type='CREATE',
module='Business',
action='新增立项登记',
target_type='ProjectRegistration',
target_id=pro.id,
target_name=pro.ContractNo,
new_data=new_data,
2026-01-30 18:05:41 +08:00
remark=f'新增立项登记:合同编号 {pro.ContractNo},承办人 {responsiblefor_dict.get("responsible_person", "")}'
2025-12-31 12:28:10 +08:00
)
2026-01-04 15:44:07 +08:00
2026-02-04 14:03:15 +08:00
return Response({
'message': '登记成功',
'code': 0,
'contract_no': pro.ContractNo,
}, status=status.HTTP_200_OK)
2025-12-24 13:51:20 +08:00
class Projectquerytype(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
2026-02-04 14:05:35 +08:00
根据年份和项目类型先查该年份该项目类型的编号序号再组装成合同编号返回
2026-02-04 14:03:15 +08:00
入参
- type必填项目类型 法律顾问专项服务
2026-02-04 14:05:35 +08:00
- year可选年份 2025不传则用 times 或当前年
- times可选日期字符串取前四位作为年份
2026-02-04 14:03:15 +08:00
2026-02-04 14:05:35 +08:00
逻辑1) 年份 + 项目类型 查询当前已用到的编号序号
2) 下一个可用编号 = 当前编号 + 1组装成校准()年份第n号
2026-02-04 14:03:15 +08:00
返回 data
2026-02-04 14:05:35 +08:00
- contract_no组装好的合同编号下一个可用 校准()2025第1号
- number下一个可用序号
- count该年份该项目类型当前已用数量已用到的最大序号
2025-12-24 13:51:20 +08:00
"""
2026-02-03 20:27:50 +08:00
project_type = request.data.get('type')
if not project_type:
return Response({'status': 'error', 'message': '缺少参数type', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2026-02-04 14:03:15 +08:00
2026-02-04 14:05:35 +08:00
# 解析年份:优先 year其次 times 前四位,否则当前年
year_param = request.data.get('year')
2026-02-04 14:03:15 +08:00
times = request.data.get('times')
2026-02-04 14:05:35 +08:00
if year_param is not None and str(year_param).strip():
try:
current_year = int(str(year_param).strip()[:4])
except ValueError:
current_year = datetime.now().year
elif times and len(str(times)) >= 4:
2026-02-04 14:03:15 +08:00
try:
current_year = int(str(times)[:4])
except ValueError:
current_year = datetime.now().year
else:
current_year = datetime.now().year
2026-02-04 14:05:35 +08:00
# 1) 根据年份 + 项目类型 查询该年份该项目类型的编号(当前已用到的序号)
2026-02-03 20:27:50 +08:00
try:
with transaction.atomic():
counter = ContractCounter.objects.filter(
project_type=project_type,
year=current_year
).first()
2026-02-04 14:03:15 +08:00
count = counter.current_number if counter else 0
except Exception:
2026-02-03 20:27:50 +08:00
start_of_year = datetime(current_year, 1, 1)
end_of_year = datetime(current_year, 12, 31)
start_of_year_str = start_of_year.strftime("%Y-%m-%d")
end_of_year_str = end_of_year.strftime("%Y-%m-%d")
count = ProjectRegistration.objects.filter(
2026-02-04 14:03:15 +08:00
type=project_type,
times__gte=start_of_year_str,
2026-02-03 20:27:50 +08:00
times__lte=end_of_year_str,
is_deleted=False
).count()
2026-02-04 14:03:15 +08:00
2026-02-04 14:05:35 +08:00
# 2) 下一个可用编号 = 当前数量 + 1组装成合同编号
next_number, contract_no = get_next_contract_no_preview(project_type, current_year)
if not contract_no:
return Response({
'status': 'error',
'message': f'项目类型「{project_type}」未配置合同编号规则,无法组装编号',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
2026-02-04 14:03:15 +08:00
data = {
2026-02-04 14:05:35 +08:00
"contract_no": contract_no,
"number": next_number,
2026-02-04 14:03:15 +08:00
"count": count,
}
2025-12-25 13:09:20 +08:00
return Response({'message': '查询成功', "data": data, 'code': 0}, status=status.HTTP_200_OK)
2025-12-24 13:51:20 +08:00
2026-01-30 18:05:41 +08:00
def _get_project_registration_queryset_by_permission(request, base_q=None):
"""
立项登记权限管理员可见全部承办人仅可见自己承办的立项
base_q: 额外筛选条件Q 对象可为 None
返回 (queryset, error_response)error_response None 时直接 return 给前端
"""
token = request.META.get('token')
if not token:
return None, Response({'status': 'error', 'message': '需要登录', 'code': 1}, status=status.HTTP_401_UNAUTHORIZED)
try:
current_user = User.objects.get(token=token, is_deleted=False)
except User.DoesNotExist:
return None, Response({'status': 'error', 'message': '用户不存在或未登录', 'code': 1}, status=status.HTTP_401_UNAUTHORIZED)
is_admin = (current_user.username == 'admin' or current_user.account == 'admin')
q = base_q if base_q is not None else Q()
q &= Q(is_deleted=False)
if not is_admin:
# 承办人只能看到自己承办的立项responsiblefor 中包含当前用户名,如 responsible_person 等)
q &= Q(responsiblefor__icontains=current_user.username)
return ProjectRegistration.objects.filter(q), None
2025-12-24 13:51:20 +08:00
class ProjectDetail(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
立项登记展示
2026-01-30 18:05:41 +08:00
权限管理员可见全部立项承办人仅可见自己承办的立项负责人修改为承办人
2025-12-24 13:51:20 +08:00
:param request:
:param args:
:param kwargs:
:return:
"""
page = request.data.get('page')
per_page = request.data.get('per_page')
times = request.data.get('times')
end_time = request.data.get('end_time')
2026-01-14 15:04:44 +08:00
client_info = request.data.get('client_info') # 委托人信息搜索
party_info = request.data.get('party_info') # 相对方信息搜索
2025-12-24 13:51:20 +08:00
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)
2026-01-14 15:04:44 +08:00
if client_info:
Q_obj &= Q(client_info__icontains=client_info)
if party_info:
Q_obj &= Q(party_info__icontains=party_info)
2025-12-24 13:51:20 +08:00
2026-01-30 18:05:41 +08:00
pre_qs, err = _get_project_registration_queryset_by_permission(request, Q_obj)
if err is not None:
return err
pre = pre_qs.order_by('-id')
2025-12-24 13:51:20 +08:00
total = len(pre)
paginator = Paginator(pre, 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 = []
2026-01-14 15:40:39 +08:00
import json
2025-12-24 13:51:20 +08:00
for info in user_agents_page.object_list:
2026-01-30 18:05:41 +08:00
# 解析承办人信息JSON字符串转字典原负责人即承办人
2026-01-14 15:40:39 +08:00
try:
responsiblefor_dict = json.loads(info.responsiblefor) if info.responsiblefor else {}
2026-01-30 18:05:41 +08:00
except Exception:
2026-01-14 15:40:39 +08:00
responsiblefor_dict = info.responsiblefor if info.responsiblefor else {}
2025-12-24 13:51:20 +08:00
data.append({
"id": info.id,
'times': info.times,
"type": info.type,
"ContractNo": info.ContractNo,
2026-01-14 15:04:44 +08:00
"client_info": info.client_info,
"party_info": info.party_info,
"description": info.description,
2026-01-30 18:05:41 +08:00
"responsiblefor": responsiblefor_dict, # 承办人信息(字典格式)
2025-12-24 13:51:20 +08:00
"charge": info.charge,
"contract": info.contract,
"state": info.state,
})
return Response({'message': '展示成功', "total": total, 'data': data, 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-24 13:51:20 +08:00
class EditProject(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
编辑立案登记
:param request:
:param args:
:param kwargs:
:return:
"""
id = request.data.get('id')
2026-01-04 15:44:07 +08:00
2025-12-25 16:15:14 +08:00
if not id:
return Response({'status': 'error', 'message': '缺少参数id', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2026-01-04 15:44:07 +08:00
2025-12-25 16:15:14 +08:00
# 检查不可修改的参数(前端不会传入,但作为安全措施进行检查)
forbidden_params = []
if 'type' in request.data:
forbidden_params.append('type')
if 'ContractNo' in request.data:
forbidden_params.append('ContractNo')
if forbidden_params:
return Response({
2026-01-04 15:44:07 +08:00
'status': 'error',
'message': f'以下参数不允许修改: {", ".join(forbidden_params)}',
2025-12-25 16:15:14 +08:00
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
2026-01-04 15:44:07 +08:00
2025-12-25 16:15:14 +08:00
# 获取可修改的参数(前端只传入这些参数)
2025-12-24 13:51:20 +08:00
times = request.data.get('times')
2026-01-14 15:04:44 +08:00
client_info = request.data.get('client_info')
party_info = request.data.get('party_info')
description = request.data.get('description')
2026-01-30 18:05:41 +08:00
responsiblefor = request.data.get('responsiblefor') # 承办人信息(字典格式)
2025-12-24 13:51:20 +08:00
charge = request.data.get('charge')
contract = request.FILES.getlist('contract')
2026-01-15 11:41:49 +08:00
approvers = request.data.get('approvers') # 审核人列表(可选,团队类型时需要)
personincharge = request.data.get('personincharge')
approvers = normalize_approvers_param(approvers, personincharge)
2026-01-14 15:40:39 +08:00
import json
2026-01-04 15:44:07 +08:00
2025-12-25 16:15:14 +08:00
try:
2025-12-31 14:17:33 +08:00
pro = ProjectRegistration.objects.get(id=id, is_deleted=False)
2025-12-25 16:15:14 +08:00
except ProjectRegistration.DoesNotExist:
2026-01-04 15:44:07 +08:00
return Response({'status': 'error', 'message': '立案登记不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
2025-12-24 13:51:20 +08:00
import datetime
2026-01-04 15:44:07 +08:00
2025-12-25 16:15:14 +08:00
# 保存原始值用于日志记录
original_type = pro.type
original_ContractNo = pro.ContractNo
2026-01-14 15:40:39 +08:00
try:
original_responsiblefor = json.loads(pro.responsiblefor) if pro.responsiblefor else {}
except:
original_responsiblefor = {}
2025-12-25 16:15:14 +08:00
original_times = pro.times
original_charge = pro.charge
2026-01-04 15:44:07 +08:00
2025-12-25 16:15:14 +08:00
update_fields_list = []
2026-01-04 15:44:07 +08:00
2025-12-24 13:51:20 +08:00
if contract:
contract = flies(contract)
2025-12-25 13:09:20 +08:00
# 将文件URL列表转换为字符串存储如果有多个URL用逗号分隔
if contract:
contract_str = contract[0] if len(contract) == 1 else ','.join(contract)
else:
contract_str = ""
pro.contract = contract_str
2025-12-25 16:15:14 +08:00
update_fields_list.append('contract')
2026-01-04 15:44:07 +08:00
2025-12-25 16:15:14 +08:00
if times:
pro.times = times
update_fields_list.append('times')
2026-01-04 15:44:07 +08:00
2025-12-25 16:15:14 +08:00
if responsiblefor:
2026-01-30 18:05:41 +08:00
# 解析承办人信息支持字符串JSON或字典
2026-01-14 15:40:39 +08:00
try:
if isinstance(responsiblefor, str):
responsiblefor_dict = json.loads(responsiblefor)
else:
responsiblefor_dict = responsiblefor
2026-01-30 18:05:41 +08:00
# 验证承办人responsible_person必填
2026-01-14 15:53:41 +08:00
if not responsiblefor_dict.get('responsible_person'):
2026-01-30 18:05:41 +08:00
return Response({'status': 'error', 'message': '承办人不能为空', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2026-01-14 15:40:39 +08:00
# 将字典转换为JSON字符串存储
responsiblefor_str = json.dumps(responsiblefor_dict, ensure_ascii=False)
pro.responsiblefor = responsiblefor_str
update_fields_list.append('responsiblefor')
except (json.JSONDecodeError, TypeError, AttributeError):
2026-01-30 18:05:41 +08:00
return Response({'status': 'error', 'message': '承办人信息格式错误,应为字典格式', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2026-01-04 15:44:07 +08:00
2025-12-25 16:15:14 +08:00
if charge:
pro.charge = charge
update_fields_list.append('charge')
2025-12-24 13:51:20 +08:00
2026-01-14 15:04:44 +08:00
if client_info:
pro.client_info = client_info
update_fields_list.append('client_info')
if party_info:
pro.party_info = party_info
update_fields_list.append('party_info')
if description:
pro.description = description
update_fields_list.append('description')
2025-12-25 13:09:20 +08:00
pro.state = "审核中"
2025-12-25 16:15:14 +08:00
update_fields_list.append('state')
2026-01-04 15:44:07 +08:00
2026-01-14 15:40:39 +08:00
# 记录操作前的数据original_responsiblefor已经是字典格式
2025-12-31 12:28:10 +08:00
old_data = {
'id': pro.id,
'contract_no': original_ContractNo,
'type': original_type,
2026-01-14 15:40:39 +08:00
'responsiblefor': original_responsiblefor if isinstance(original_responsiblefor, dict) else {},
2025-12-31 12:28:10 +08:00
'times': original_times,
'charge': original_charge
}
2026-01-04 15:44:07 +08:00
2025-12-25 16:15:14 +08:00
if update_fields_list:
pro.save(update_fields=update_fields_list)
2025-12-24 13:51:20 +08:00
2025-12-31 12:28:10 +08:00
# 记录操作后的数据
2026-01-14 15:40:39 +08:00
try:
current_responsiblefor = json.loads(pro.responsiblefor) if pro.responsiblefor else {}
except:
current_responsiblefor = pro.responsiblefor if pro.responsiblefor else {}
2025-12-31 12:28:10 +08:00
new_data = {
'id': pro.id,
'contract_no': pro.ContractNo,
'type': pro.type,
2026-01-14 15:40:39 +08:00
'responsiblefor': current_responsiblefor,
2025-12-31 12:28:10 +08:00
'times': pro.times,
'charge': pro.charge
}
2025-12-24 13:51:20 +08:00
today = datetime.datetime.now()
formatted_date = today.strftime("%Y-%m-%d")
2026-01-14 15:40:39 +08:00
2026-01-30 18:05:41 +08:00
# 获取承办人名称用于审批记录
2026-01-14 15:40:39 +08:00
if responsiblefor:
2026-01-14 15:53:41 +08:00
current_responsiblefor = responsiblefor_dict.get('responsible_person', '')
2026-01-30 18:05:41 +08:00
# 构建承办人信息描述
2026-01-14 15:40:39 +08:00
responsible_desc = current_responsiblefor
2026-01-14 15:53:41 +08:00
if responsiblefor_dict.get('main_lawyer'):
responsible_desc += f",主办律师:{responsiblefor_dict.get('main_lawyer')}"
if responsiblefor_dict.get('assistant_lawyer'):
responsible_desc += f",助理律师:{responsiblefor_dict.get('assistant_lawyer')}"
if responsiblefor_dict.get('case_manager_lawyer'):
responsible_desc += f",案管律师:{responsiblefor_dict.get('case_manager_lawyer')}"
2026-01-14 15:40:39 +08:00
else:
2026-01-14 15:53:41 +08:00
current_responsiblefor = original_responsiblefor.get('responsible_person', '') if isinstance(original_responsiblefor, dict) else ''
2026-01-14 15:40:39 +08:00
responsible_desc = current_responsiblefor
2026-01-30 15:47:25 +08:00
edit_user = None
token = request.META.get('token') or request.META.get('HTTP_AUTHORIZATION', '').replace('Bearer ', '')
if token:
try:
edit_user = User.objects.get(token=token, is_deleted=False)
except User.DoesNotExist:
pass
applicant_name = edit_user.username if edit_user else ""
2026-01-15 11:41:49 +08:00
from User.utils import create_approval_with_team_logic
team_name = get_team_name_from_responsiblefor(responsiblefor_dict if responsiblefor else original_responsiblefor)
content = current_responsiblefor + "" + (
2026-01-30 18:08:04 +08:00
times or original_times) + "办理立项登记,项目类型:" + original_type + ",合同编号:" + original_ContractNo + "描述:" + ",承办人:" + responsible_desc + ",收费情况:" + (charge or original_charge) + ",申请人:" + applicant_name
2026-01-15 11:41:49 +08:00
approval, approvers_order_json, needs_approval = create_approval_with_team_logic(
team_name=team_name,
approvers=approvers,
2026-01-14 15:40:39 +08:00
title=current_responsiblefor + "立项登记重新编辑",
2026-01-15 11:41:49 +08:00
content=content,
approval_type="立项登记",
user_id=pro.id,
business_record=pro,
2026-01-30 16:04:40 +08:00
today=formatted_date,
applicant=applicant_name
2025-12-24 13:51:20 +08:00
)
2026-01-15 11:41:49 +08:00
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)
2026-01-04 15:44:07 +08:00
2025-12-31 12:28:10 +08:00
# 记录操作日志
log_operation(
request=request,
operation_type='UPDATE',
module='Business',
action='编辑立项登记',
target_type='ProjectRegistration',
target_id=pro.id,
target_name=pro.ContractNo,
old_data=old_data,
new_data=new_data,
remark=f'编辑立项登记:合同编号 {pro.ContractNo}'
)
2025-12-24 13:51:20 +08:00
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-25 15:20:41 +08:00
class DeleteProject(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)
2026-01-04 15:44:07 +08:00
2025-12-25 15:20:41 +08:00
try:
2025-12-31 11:42:28 +08:00
pro = ProjectRegistration.objects.get(id=id, is_deleted=False)
2025-12-25 15:20:41 +08:00
except ProjectRegistration.DoesNotExist:
2026-01-04 15:44:07 +08:00
return Response({'status': 'error', 'message': '立项登记不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
2025-12-25 15:20:41 +08:00
# 检查是否已经被案件管理关联
2026-01-14 15:04:44 +08:00
case_exists = Case.objects.filter(project_id=pro.id, is_deleted=False).exists()
2026-01-04 15:44:07 +08:00
2025-12-25 15:20:41 +08:00
if case_exists:
2026-01-04 15:44:07 +08:00
return Response({'status': 'error', 'message': '该立项已被案件管理关联,无法删除', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
2025-12-31 12:28:10 +08:00
# 记录操作前的数据
2026-01-14 15:04:44 +08:00
old_data = {
'id': pro.id,
'contract_no': pro.ContractNo,
'type': pro.type,
'responsiblefor': pro.responsiblefor,
'client_info': pro.client_info
}
2026-01-04 15:44:07 +08:00
2025-12-31 11:42:28 +08:00
# 软删除:更新 is_deleted 字段
pro.is_deleted = True
pro.save()
2026-01-04 15:44:07 +08:00
2025-12-31 12:28:10 +08:00
# 记录操作日志
log_operation(
request=request,
operation_type='DELETE',
module='Business',
action='删除立项登记',
target_type='ProjectRegistration',
target_id=pro.id,
target_name=pro.ContractNo,
old_data=old_data,
remark=f'删除立项登记:合同编号 {pro.ContractNo}'
)
2026-01-04 15:44:07 +08:00
2025-12-25 15:20:41 +08:00
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-24 13:51:20 +08:00
class BidRegistration(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
2026-01-14 15:04:44 +08:00
投标登记 - 独立创建不再关联预立案
2025-12-24 13:51:20 +08:00
:param request:
:param args:
:param kwargs:
:return:
"""
BiddingUnit = request.data.get('BiddingUnit')
ProjectName = request.data.get('ProjectName')
times = request.data.get('times')
BiddingAnnouncement = request.FILES.getlist('BiddingAnnouncement')
2026-01-12 23:17:39 +08:00
approvers = request.data.get('approvers') # 审核人列表可选多人团队时需要推荐用户ID数组如[1,2,3],兼容:用户名数组)
2026-01-12 22:20:36 +08:00
# 兼容旧接口:如果传了 personincharge转换为 approvers
2025-12-24 13:51:20 +08:00
personincharge = request.data.get('personincharge')
2026-01-15 11:41:49 +08:00
approvers = normalize_approvers_param(approvers, personincharge)
2026-01-14 15:04:44 +08:00
if not all([BiddingUnit, ProjectName, times]):
return Response({'status': 'error', 'message': '缺少必填参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2025-12-24 13:51:20 +08:00
import datetime
2026-01-14 15:04:44 +08:00
2026-01-30 16:04:40 +08:00
# 获取当前用户信息(用于团队信息及申请人)
2026-01-14 15:04:44 +08:00
token = request.META.get('token')
team_name = None
2026-01-30 16:04:40 +08:00
user = None
2026-01-14 15:04:44 +08:00
try:
user = User.objects.get(token=token, is_deleted=False)
team_name = user.team
except User.DoesNotExist:
pass
2025-12-24 13:51:20 +08:00
bib = Bid.objects.create(
BiddingUnit=BiddingUnit,
ProjectName=ProjectName,
times=times,
BiddingAnnouncement=json.dumps(flies(BiddingAnnouncement)),
state="审核中",
)
today = datetime.datetime.now()
formatted_date = today.strftime("%Y-%m-%d")
2026-01-12 22:20:36 +08:00
2026-01-30 11:27:50 +08:00
# 利益冲突检索(与立项相同逻辑):比对预立案、立项、投标三张表
conflict_result = conflict_search(bidding_unit=BiddingUnit, exclude_bid_id=bib.id)
conflict_parts = []
if conflict_result['prefiling_conflicts']:
conflict_parts.append(f"预立案冲突:{len(conflict_result['prefiling_conflicts'])}ID{','.join([str(r['id']) for r in conflict_result['prefiling_conflicts'][:5]])}{'...' if len(conflict_result['prefiling_conflicts']) > 5 else ''}")
if conflict_result['project_conflicts']:
conflict_parts.append(f"立项冲突:{len(conflict_result['project_conflicts'])}条(合同编号:{','.join([r['ContractNo'] for r in conflict_result['project_conflicts'][:3]])}{'...' if len(conflict_result['project_conflicts']) > 3 else ''}")
if conflict_result['bid_conflicts']:
conflict_parts.append(f"投标冲突:{len(conflict_result['bid_conflicts'])}条(项目:{','.join([(r.get('ProjectName') or '')[:20] for r in conflict_result['bid_conflicts'][:3]])}{'...' if len(conflict_result['bid_conflicts']) > 3 else ''}")
conflict_text = ";冲突记录:" + "".join(conflict_parts) if conflict_parts else ""
2026-01-12 22:20:36 +08:00
# 使用统一的审核流程函数
from User.utils import create_approval_with_team_logic
2026-01-30 15:22:07 +08:00
applicant_name = user.username if user else ""
content = f"项目名称:{ProjectName},申请日期:{times},招标单位:{BiddingUnit},申请人:{applicant_name}{conflict_text}"
2026-01-12 22:20:36 +08:00
approval, approvers_order_json, needs_approval = create_approval_with_team_logic(
team_name=team_name,
approvers=approvers,
2025-12-24 13:51:20 +08:00
title=ProjectName + "投标登记",
2026-01-12 22:20:36 +08:00
content=content,
approval_type="投标登记",
user_id=bib.id,
business_record=bib,
2026-01-30 16:04:40 +08:00
today=formatted_date,
applicant=applicant_name
2025-12-24 13:51:20 +08:00
)
2026-01-12 22:20:36 +08:00
# 如果返回None且需要审核说明缺少审核人
if approval is None and needs_approval:
return Response({
'status': 'error',
2026-01-15 11:41:49 +08:00
'message': build_missing_approvers_message(team_name, approvers),
2026-01-12 22:20:36 +08:00
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
2025-12-24 13:51:20 +08:00
return Response({'message': '登记成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-24 13:51:20 +08:00
class BidDetail(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-25 16:15:14 +08:00
"""
2026-02-05 10:36:17 +08:00
投标登记展示列表+搜索
支持搜索project_name 项目名称client_username 招标单位名字times/end_time 日期范围keyword 关键词
2025-12-25 16:15:14 +08:00
"""
2025-12-24 13:51:20 +08:00
page = request.data.get('page')
per_page = request.data.get('per_page')
times = request.data.get('times')
end_time = request.data.get('end_time')
2026-02-05 10:36:17 +08:00
# 项目名称:兼容 project_name / projectName
project_name = (request.data.get('project_name') or request.data.get('projectName') or '').strip()
# 招标单位名字:变量名 client_username对应库表字段 BiddingUnit
client_username = (request.data.get('client_username') or '').strip()
# 关键词:同时匹配项目名称和招标单位
keyword = (request.data.get('keyword') or request.data.get('search') or '').strip()
2025-12-24 13:51:20 +08:00
if not all([page, per_page]):
2026-02-05 10:36:17 +08:00
return Response({'status': 'error', 'message': '缺少参数 page 或 per_page', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
Q_obj = Q(is_deleted=False)
2025-12-24 13:51:20 +08:00
if times and end_time:
Q_obj &= Q(times__gte=times) & Q(times__lte=end_time)
2026-01-14 15:04:44 +08:00
if project_name:
Q_obj &= Q(ProjectName__icontains=project_name)
2026-02-05 10:36:17 +08:00
if client_username:
Q_obj &= Q(BiddingUnit__icontains=client_username)
if keyword:
Q_obj &= (Q(ProjectName__icontains=keyword) | Q(BiddingUnit__icontains=keyword))
2025-12-24 13:51:20 +08:00
2026-02-05 10:36:17 +08:00
qs = Bid.objects.filter(Q_obj).order_by('-id')
total = qs.count()
2025-12-24 13:51:20 +08:00
2026-02-05 10:36:17 +08:00
paginator = Paginator(qs, per_page)
2025-12-24 13:51:20 +08:00
try:
2026-02-05 10:36:17 +08:00
user_agents_page = paginator.page(int(page))
2025-12-24 13:51:20 +08:00
except PageNotAnInteger:
user_agents_page = paginator.page(1)
except EmptyPage:
user_agents_page = paginator.page(paginator.num_pages)
2026-02-05 10:36:17 +08:00
2025-12-24 13:51:20 +08:00
data = []
for info in user_agents_page.object_list:
data.append({
"id": info.id,
2026-02-05 10:36:17 +08:00
"times": info.times,
2025-12-24 13:51:20 +08:00
"BiddingUnit": info.BiddingUnit,
"ProjectName": info.ProjectName,
"BiddingAnnouncement": info.BiddingAnnouncement,
"state": info.state,
})
return Response({'message': '展示成功', "total": total, 'data': data, 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 15:20:41 +08:00
class EditBid(APIView):
def post(self, request, *args, **kwargs):
"""
编辑投标登记
:param request:
:param args:
:param kwargs:
:return:
"""
id = request.data.get('id')
BiddingUnit = request.data.get('BiddingUnit')
ProjectName = request.data.get('ProjectName')
times = request.data.get('times')
BiddingAnnouncement = request.FILES.getlist('BiddingAnnouncement')
2026-01-15 11:41:49 +08:00
approvers = request.data.get('approvers')
2025-12-25 15:20:41 +08:00
personincharge = request.data.get('personincharge')
2026-01-15 11:41:49 +08:00
approvers = normalize_approvers_param(approvers, personincharge)
2026-01-04 15:44:07 +08:00
2025-12-25 15:20:41 +08:00
if not id:
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2026-01-04 15:44:07 +08:00
2025-12-25 15:20:41 +08:00
try:
2025-12-31 14:17:33 +08:00
bid = Bid.objects.get(id=id, is_deleted=False)
2025-12-25 15:20:41 +08:00
except Bid.DoesNotExist:
2026-01-04 15:44:07 +08:00
return Response({'status': 'error', 'message': '投标登记不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
2025-12-25 15:20:41 +08:00
if BiddingUnit:
bid.BiddingUnit = BiddingUnit
bid.save(update_fields=['BiddingUnit'])
if ProjectName:
bid.ProjectName = ProjectName
bid.save(update_fields=['ProjectName'])
if times:
bid.times = times
bid.save(update_fields=['times'])
if BiddingAnnouncement:
bid.BiddingAnnouncement = json.dumps(flies(BiddingAnnouncement))
bid.save(update_fields=['BiddingAnnouncement'])
2026-01-04 15:44:07 +08:00
2026-01-15 11:41:49 +08:00
if approvers:
2025-12-25 15:20:41 +08:00
import datetime
today = datetime.datetime.now()
formatted_date = today.strftime("%Y-%m-%d")
2026-01-15 11:41:49 +08:00
team_name = None
2026-01-30 15:22:07 +08:00
edit_user = None
2026-01-15 11:41:49 +08:00
token = request.META.get('token')
if token:
try:
edit_user = User.objects.get(token=token, is_deleted=False)
team_name = edit_user.team
except User.DoesNotExist:
team_name = None
from User.utils import create_approval_with_team_logic
2026-01-30 15:22:07 +08:00
applicant_name = edit_user.username if edit_user else ""
content_edit = f"项目名称:{bid.ProjectName},申请日期:{times or bid.times},申请人:{applicant_name}"
2026-01-15 11:41:49 +08:00
approval, approvers_order_json, needs_approval = create_approval_with_team_logic(
team_name=team_name,
approvers=approvers,
2025-12-25 15:20:41 +08:00
title=bid.ProjectName + "投标登记重新编辑",
2026-01-30 15:22:07 +08:00
content=content_edit,
2026-01-15 11:41:49 +08:00
approval_type="投标登记",
user_id=bid.id,
business_record=bid,
2026-01-30 16:04:40 +08:00
today=formatted_date,
applicant=applicant_name
2025-12-25 15:20:41 +08:00
)
2026-01-15 11:41:49 +08:00
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)
2026-01-04 15:44:07 +08:00
2025-12-25 15:20:41 +08:00
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
class DeleteBid(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)
2026-01-04 15:44:07 +08:00
2025-12-25 15:20:41 +08:00
try:
2025-12-31 11:42:28 +08:00
bid = Bid.objects.get(id=id, is_deleted=False)
2025-12-25 15:20:41 +08:00
except Bid.DoesNotExist:
2026-01-04 15:44:07 +08:00
return Response({'status': 'error', 'message': '投标登记不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
2025-12-31 12:28:10 +08:00
# 记录操作前的数据
old_data = {
'id': bid.id,
'project_name': bid.ProjectName,
'times': bid.times
}
2026-01-04 15:44:07 +08:00
2025-12-31 11:42:28 +08:00
# 软删除:更新 is_deleted 字段
bid.is_deleted = True
bid.save()
2026-01-04 15:44:07 +08:00
2025-12-31 12:28:10 +08:00
# 记录操作日志
log_operation(
request=request,
operation_type='DELETE',
module='Business',
action='删除投标登记',
target_type='Bid',
target_id=bid.id,
target_name=bid.ProjectName,
old_data=old_data,
remark=f'删除投标登记:{bid.ProjectName}'
)
2026-01-04 15:44:07 +08:00
2025-12-25 15:20:41 +08:00
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-24 13:51:20 +08:00
class caseManagement(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
2026-01-30 18:21:04 +08:00
补充案件资料立项审批通过即成案件本接口用于创建/补充案件材料
2026-01-31 11:21:46 +08:00
- 立项审批通过后用本接口补充合同返还结案申请等资料
2026-01-30 18:21:04 +08:00
- 首次调用该立项尚无案件创建案件立案时间 = 立项审批通过时间无则用请求 times 或当前日期
- 再次调用该立项已有案件仅更新/补充材料不修改立案时间
2025-12-24 13:51:20 +08:00
:param request:
:param args:
:param kwargs:
:return:
"""
2026-01-14 15:04:44 +08:00
project_id = request.data.get('project_id') # 立项登记ID
2026-01-30 18:21:04 +08:00
times = request.data.get('times') # 立案时间(仅当无审批通过时间时使用)
2026-01-15 13:34:07 +08:00
Contractreturn = request.FILES.getlist('Contractreturn')
Closingapplication = request.FILES.getlist('Closingapplication')
2026-01-14 15:04:44 +08:00
invoice_status = request.data.get('invoice_status') # 已开票
2025-12-24 13:51:20 +08:00
paymentcollection = request.data.get('paymentcollection')
2026-01-19 10:48:36 +08:00
tag_ids = request.data.get('tag_ids') # 标签ID列表数组或逗号分隔的字符串
2025-12-24 13:51:20 +08:00
2026-01-15 11:41:49 +08:00
approvers = request.data.get('approvers')
2025-12-24 13:51:20 +08:00
personincharge = request.data.get('personincharge')
2026-01-15 11:41:49 +08:00
approvers = normalize_approvers_param(approvers, personincharge)
2025-12-24 13:51:20 +08:00
2026-01-04 17:35:51 +08:00
# 检查是否已立项
2026-01-14 15:04:44 +08:00
if not project_id:
return Response({'status': 'error', 'message': '缺少参数project_id立项登记ID', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2026-01-04 17:49:16 +08:00
# 使用事务和行级锁防止并发重复创建
try:
with transaction.atomic():
2026-01-14 15:04:44 +08:00
# 使用 select_for_update 锁定立项登记记录,防止并发问题
project_registration = ProjectRegistration.objects.select_for_update().get(id=project_id, is_deleted=False)
2026-01-04 17:49:16 +08:00
# 再次检查是否已经存在案件(在锁内检查,防止并发重复创建)
2026-01-14 15:04:44 +08:00
case = Case.objects.filter(project_id=project_id, is_deleted=False).first()
2026-01-04 17:49:16 +08:00
if not case:
2026-01-30 18:21:04 +08:00
# 立案时间:优先取立项审批通过时间,否则用请求 times 或当前日期
project_approval = Approval.objects.filter(
type='立项登记', user_id=str(project_id), state='已通过', is_deleted=False
).order_by('-id').first()
if project_approval and getattr(project_approval, 'completeTiem', None):
filing_time = project_approval.completeTiem.strftime('%Y-%m-%d')
else:
filing_time = times if times else datetime.now().strftime('%Y-%m-%d')
2026-01-31 11:21:46 +08:00
# 处理材料上传后存储URL列表代理合同字段已从案件管理移除创建时存空
2026-01-15 13:34:07 +08:00
contract_return_list = flies(Contractreturn)
closing_application_list = flies(Closingapplication)
2026-01-15 12:28:23 +08:00
2026-01-15 11:54:16 +08:00
project_contract_no = project_registration.ContractNo if project_registration else None
project_type = project_registration.type if project_registration else None
project_client_name = project_registration.client_info if project_registration else None
project_party_name = project_registration.party_info if project_registration else None
project_description = project_registration.description if project_registration else None
project_responsiblefor = project_registration.responsiblefor if project_registration else None
project_charge = project_registration.charge if project_registration else None
2026-01-30 18:21:04 +08:00
# 创建新案件(立案时间 = 审批通过时间)
2026-01-04 17:49:16 +08:00
case_id = Case.objects.create(
2026-01-14 15:04:44 +08:00
project_id=project_id,
2026-01-15 11:54:16 +08:00
contract_no=project_contract_no,
project_type=project_type,
client_name=project_client_name,
party_name=project_party_name,
project_description=project_description,
responsiblefor=project_responsiblefor,
charge=project_charge,
2026-01-30 18:21:04 +08:00
times=filing_time,
2026-01-31 11:21:46 +08:00
AgencyContract=json.dumps([], ensure_ascii=False), # 代理合同已从案件管理移除,库字段保留
2026-01-15 12:28:23 +08:00
Contractreturn=json.dumps(contract_return_list, ensure_ascii=False),
Closingapplication=json.dumps(closing_application_list, ensure_ascii=False),
2026-01-15 14:55:50 +08:00
ChangeRequest="",
2026-01-15 16:47:13 +08:00
invoice_status=normalize_amount_value(invoice_status),
paymentcollection=normalize_amount_value(paymentcollection),
2026-01-04 17:49:16 +08:00
state="审核中"
)
2026-01-15 14:55:50 +08:00
from User.utils import create_approval_with_team_logic
team_name = get_team_name_from_responsiblefor(project_registration.responsiblefor)
approval, approvers_order_json, needs_approval = create_approval_with_team_logic(
team_name=team_name,
approvers=approvers,
title="案件管理信息提交",
2026-01-30 18:21:04 +08:00
content=build_case_approval_content(project_registration, filing_time),
2026-01-15 14:55:50 +08:00
approval_type="案件管理",
user_id=case_id.id,
business_record=case_id,
2026-01-30 18:21:04 +08:00
today=filing_time
2026-01-15 14:55:50 +08:00
)
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)
2026-01-19 10:48:36 +08:00
# 处理标签关联
if tag_ids:
try:
# 支持数组或逗号分隔的字符串
if isinstance(tag_ids, str):
if ',' in tag_ids:
tag_id_list = [int(x.strip()) for x in tag_ids.split(',') if x.strip()]
else:
tag_id_list = [int(tag_ids)]
elif isinstance(tag_ids, list):
tag_id_list = [int(x) for x in tag_ids if x]
else:
tag_id_list = []
# 验证标签是否存在
valid_tags = CaseTag.objects.filter(id__in=tag_id_list, is_deleted=False)
case_id.tags.set(valid_tags)
except (ValueError, TypeError) as e:
# 标签ID格式错误但不影响案件创建只记录警告
pass
2026-01-04 17:49:16 +08:00
# 创建成功,直接返回
return Response({'message': '创建成功', 'code': 0}, status=status.HTTP_200_OK)
# 如果已存在,事务结束,在事务外处理更新逻辑
2026-01-14 15:04:44 +08:00
except ProjectRegistration.DoesNotExist:
return Response({'status': 'error', 'message': '该立项登记不存在或已删除,请先完成立项登记', 'code': 1},
2026-01-04 17:35:51 +08:00
status=status.HTTP_400_BAD_REQUEST)
2026-01-04 17:49:16 +08:00
# 如果代码执行到这里,说明案件已存在,需要执行更新逻辑
# 注意:这里不需要再次锁定,因为更新操作不会造成重复创建问题
2026-01-14 15:04:44 +08:00
case = Case.objects.filter(project_id=project_id, is_deleted=False).first()
2025-12-24 13:51:20 +08:00
if not case:
2026-01-04 17:49:16 +08:00
# 这种情况理论上不应该发生,因为事务内已检查,但为了安全起见
return Response({'status': 'error', 'message': '案件不存在', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
2026-01-31 11:21:46 +08:00
# 执行更新逻辑(代理合同已从案件管理移除,不再接收或更新)
2026-01-14 15:04:44 +08:00
update_fields_list = []
2026-01-04 17:49:16 +08:00
if times:
case.times = times
2026-01-14 15:04:44 +08:00
update_fields_list.append('times')
2026-01-04 17:49:16 +08:00
2026-01-15 13:34:07 +08:00
if Contractreturn:
case.Contractreturn = json.dumps(flies(Contractreturn), ensure_ascii=False)
2026-01-14 15:04:44 +08:00
update_fields_list.append('Contractreturn')
2026-01-04 17:49:16 +08:00
2026-01-15 13:34:07 +08:00
if Closingapplication:
case.Closingapplication = json.dumps(flies(Closingapplication), ensure_ascii=False)
2026-01-14 15:04:44 +08:00
update_fields_list.append('Closingapplication')
2025-12-24 13:51:20 +08:00
2026-01-15 16:47:13 +08:00
if paymentcollection is not None:
case.paymentcollection = normalize_amount_value(paymentcollection)
2026-01-14 15:04:44 +08:00
update_fields_list.append('paymentcollection')
2026-01-15 16:47:13 +08:00
if invoice_status is not None:
case.invoice_status = normalize_amount_value(invoice_status)
2026-01-14 15:04:44 +08:00
update_fields_list.append('invoice_status')
if update_fields_list:
case.save(update_fields=update_fields_list)
2025-12-24 13:51:20 +08:00
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2026-01-15 12:28:23 +08:00
class CaseAttachmentUpload(APIView):
def post(self, request, *args, **kwargs):
"""
2026-01-31 11:21:46 +08:00
案件材料上传合同返还/结案申请
2026-01-15 12:28:23 +08:00
通过 type 指定上传类型返回文件URL列表
"""
upload_type = request.data.get('type')
files = request.FILES.getlist('file') or request.FILES.getlist('files')
2026-01-31 11:21:46 +08:00
allowed_types = ["Contractreturn", "Closingapplication"]
2026-01-15 12:28:23 +08:00
if upload_type not in allowed_types:
return Response({
'status': 'error',
'message': f'type参数错误支持类型{", ".join(allowed_types)}',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
if not files:
return Response({'status': 'error', 'message': '请上传文件', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
file_urls = flies(files)
return Response({
'message': '上传成功',
'code': 0,
'data': {
'type': upload_type,
'files': file_urls
}
}, status=status.HTTP_200_OK)
2026-01-15 13:34:07 +08:00
class CaseAttachmentUpdate(APIView):
def post(self, request, *args, **kwargs):
"""
2026-01-31 11:21:46 +08:00
案件材料更新合同返还/结案申请
2026-01-15 13:34:07 +08:00
通过 type 指定更新哪一种材料
"""
case_id = request.data.get('case_id')
upload_type = request.data.get('type')
files = request.FILES.getlist('file') or request.FILES.getlist('files')
Contractreturn = request.data.get('Contractreturn')
2026-01-21 14:52:28 +08:00
approvers = request.data.get('approvers')
personincharge = request.data.get('personincharge')
approvers = normalize_approvers_param(approvers, personincharge)
2026-01-15 13:34:07 +08:00
if not case_id:
return Response({'status': 'error', 'message': '缺少参数case_id', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
allowed_types = ["Contractreturn", "Closingapplication","AgencyContract"]
2026-01-15 13:34:07 +08:00
if upload_type not in allowed_types:
return Response({
'status': 'error',
'message': f'type参数错误支持类型{", ".join(allowed_types)}',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# if not files:
# return Response({'status': 'error', 'message': '请上传文件', 'code': 1},
# status=status.HTTP_400_BAD_REQUEST)
2026-01-15 13:34:07 +08:00
try:
case = Case.objects.get(id=case_id, is_deleted=False)
except Case.DoesNotExist:
return Response({'status': 'error', 'message': '案件不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
file_urls = flies(files)
update_fields = []
2026-01-31 11:21:46 +08:00
if upload_type == "Contractreturn":
case.Contractreturn = Contractreturn
2026-01-15 13:34:07 +08:00
update_fields = ['Contractreturn']
elif upload_type == "Closingapplication":
2026-02-01 14:22:56 +08:00
# 结案申请需要审批通过后才更新 Case.Closingapplication 字段
# 这里不直接更新文件URL保存在 Schedule.remark 中,审批通过后再更新到 Case
pass
elif upload_type == "AgencyContract":
case.AgencyContract = json.dumps(file_urls, ensure_ascii=False)
update_fields = ['AgencyContract']
2026-01-15 13:34:07 +08:00
2026-02-01 14:22:56 +08:00
if update_fields:
case.save(update_fields=update_fields)
2026-01-15 13:34:07 +08:00
2026-01-21 14:52:28 +08:00
# 结案申请:加入审批流程(独立于“案件管理”审批,避免覆盖 Case.approvers_order / Case.state
2026-02-01 14:22:56 +08:00
if upload_type == "Closingapplication": # 审批通过后才更新 Case.Closingapplication 字段
2026-01-21 14:52:28 +08:00
try:
token = request.META.get('token')
submitter = User.objects.get(token=token, is_deleted=False)
except Exception:
submitter = None
# 以 Schedule 作为承载对象,复用统一审批流
try:
from datetime import date
today = date.today().strftime("%Y-%m-%d")
contract_no = case.contract_no or ""
title = f"结案申请(合同号:{contract_no or ('案件#' + str(case.id))}"
remark = json.dumps({
"case_id": case.id,
"contract_no": contract_no,
"closing_application_files": file_urls
}, ensure_ascii=False)
schedule = Schedule.objects.create(
title=title,
tiems=today,
end_time=today,
remark=remark,
state="未完成",
submit=submitter.username if submitter else None
)
from User.utils import create_approval_with_team_logic
team_name = submitter.team if submitter else None
content = f"{submitter.username if submitter else '系统'}提交了结案申请,合同号:{contract_no or ('案件#' + str(case.id))}"
approval, approvers_order_json, needs_approval = create_approval_with_team_logic(
team_name=team_name,
approvers=approvers,
title="结案申请审批",
content=content,
approval_type="结案申请",
user_id=schedule.id,
business_record=schedule,
2026-02-01 14:33:23 +08:00
today=today,
applicant=submitter.username if submitter else None # 传入申请人,用于"待查看"流程
2026-01-21 14:52:28 +08:00
)
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)
except Exception as e:
# 不影响材料更新主流程,但记录错误信息
import logging
logger = logging.getLogger(__name__)
logger.error(f"结案申请审批创建失败(case_id={case.id}): {str(e)}")
2026-01-15 13:34:07 +08:00
return Response({
'message': '更新成功',
'code': 0,
'data': {
'case_id': case.id,
'type': upload_type,
'files': file_urls
}
}, status=status.HTTP_200_OK)
2025-12-24 13:51:20 +08:00
class caseManagementDetail(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
案件管理版块展示
2026-02-05 15:37:51 +08:00
搜索支持按标签 tags标签名称模糊匹配 tag_ids标签ID列表/逗号分隔匹配拥有任一标签的案件可同时使用
:param request: page, per_page 必填可选 times, end_time, type, contract_no, party_name, client_name, tags, tag_ids
2025-12-24 13:51:20 +08:00
:return:
"""
page = request.data.get('page')
per_page = request.data.get('per_page')
times = request.data.get('times')
end_time = request.data.get('end_time')
type = request.data.get('type')
serialnumber = request.data.get('contract_no')
party_name = request.data.get('party_name')
client_name = request.data.get('client_name')
2026-02-05 15:41:44 +08:00
tags = request.data.get('tags') # 标签:传字符串为名称模糊匹配,传数组 [2,3] 为标签ID列表
tag_ids = request.data.get('tag_ids') # 标签ID列表与 tags 传 ID 时等效)
2025-12-24 13:51:20 +08:00
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 type:
2026-01-14 15:04:44 +08:00
por_id = ProjectRegistration.objects.filter(type=type, is_deleted=False).values_list('id', flat=True)
Q_obj &= Q(project_id__in=por_id)
2025-12-24 13:51:20 +08:00
if serialnumber:
Q_obj &= Q(contract_no__icontains=serialnumber)
if party_name:
Q_obj &= Q(party_name__icontains=party_name)
if client_name:
Q_obj &= Q(client_name__icontains=client_name)
2026-02-05 15:44:19 +08:00
# 按标签搜索tags 可为字符串(名称模糊)、数组(标签ID)、或 form 传来的字符串 "[2]"tag_ids 为标签ID列表满足任一标签即匹配
2026-02-05 15:37:51 +08:00
tag_id_list = []
2026-02-05 15:41:44 +08:00
if tags is not None and tags != '':
if isinstance(tags, list):
try:
tag_id_list.extend([int(x) for x in tags if x is not None and str(x).strip() != ''])
except (ValueError, TypeError):
pass
elif isinstance(tags, str):
2026-02-05 15:44:19 +08:00
tags_stripped = tags.strip()
# 前端 form-data 可能把 [2] 或 [2,3] 当成字符串传,先尝试按 JSON 数组解析
if tags_stripped.startswith('[') and tags_stripped.endswith(']'):
try:
parsed = json.loads(tags_stripped)
if isinstance(parsed, list):
tag_id_list.extend([int(x) for x in parsed if x is not None and str(x).strip() != ''])
except (ValueError, TypeError, json.JSONDecodeError):
pass
if not tag_id_list:
tag_id_list.extend(
CaseTag.objects.filter(name__icontains=tags, is_deleted=False).values_list('id', flat=True)
)
2026-02-05 15:37:51 +08:00
if tag_ids:
try:
if isinstance(tag_ids, str):
tag_id_list.extend([int(x.strip()) for x in tag_ids.split(',') if x.strip()])
elif isinstance(tag_ids, list):
tag_id_list.extend([int(x) for x in tag_ids if x is not None and str(x).strip() != ''])
except (ValueError, TypeError):
pass
2026-02-05 15:41:44 +08:00
tag_id_list = list(set(tag_id_list))
2026-02-05 15:37:51 +08:00
if tag_id_list:
Q_obj &= Q(tags__in=tag_id_list)
2025-12-31 11:42:28 +08:00
pre = Case.objects.filter(Q_obj, is_deleted=False).order_by('-id')
2026-02-05 15:37:51 +08:00
if tag_id_list:
pre = pre.distinct()
2025-12-24 13:51:20 +08:00
total = len(pre)
paginator = Paginator(pre, 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)
2026-01-20 13:45:46 +08:00
# 导入财务模型,用于统计开票金额和收入确认金额
from finance.models import Invoice as FinanceInvoice, Income
# 收集所有合同号,用于批量查询财务数据
case_list = list(user_agents_page.object_list)
contract_nos = []
case_contract_map = {} # 案件ID -> 合同号的映射
for info in case_list:
pro = None
if info.project_id:
try:
pro = ProjectRegistration.objects.get(id=info.project_id, is_deleted=False)
except ProjectRegistration.DoesNotExist:
pro = None
contract_no = info.contract_no or (pro.ContractNo if pro else None)
if contract_no:
contract_nos.append(contract_no)
case_contract_map[info.id] = contract_no
# 批量查询开票金额(按合同号统计)
invoice_amounts = {} # 合同号 -> 总开票金额
if contract_nos:
# 查询所有未删除的开票记录一次性查询避免N+1问题
invoices = FinanceInvoice.objects.filter(
ContractNo__in=contract_nos,
is_deleted=False
).values('ContractNo', 'amount')
# 计算每个合同号的总开票金额(需要解析金额字符串)
for invoice in invoices:
contract_no = invoice['ContractNo']
amount_str = normalize_amount_value(invoice['amount'])
try:
amount = float(amount_str)
except (ValueError, TypeError):
amount = 0.0
if contract_no in invoice_amounts:
invoice_amounts[contract_no] = str(float(invoice_amounts[contract_no]) + amount)
else:
invoice_amounts[contract_no] = str(amount)
2026-01-20 14:11:27 +08:00
# 批量查询收入确认金额(按合同号统计,只统计审核通过的)
2026-01-20 13:45:46 +08:00
income_amounts = {} # 合同号 -> 总收入确认金额
if contract_nos:
2026-01-20 14:11:27 +08:00
# 查询所有未删除且审核通过的收入确认记录一次性查询避免N+1问题
# 只统计 state="已通过" 的记录
2026-01-20 13:45:46 +08:00
incomes = Income.objects.filter(
ContractNo__in=contract_nos,
2026-01-20 14:11:27 +08:00
is_deleted=False,
state="已通过" # 只统计审核通过的收入确认
2026-01-20 13:45:46 +08:00
).values('ContractNo', 'amount')
# 计算每个合同号的总收入确认金额
for income in incomes:
contract_no = income['ContractNo']
amount_str = normalize_amount_value(income['amount'])
try:
amount = float(amount_str)
except (ValueError, TypeError):
amount = 0.0
if contract_no in income_amounts:
income_amounts[contract_no] = str(float(income_amounts[contract_no]) + amount)
else:
income_amounts[contract_no] = str(amount)
2025-12-24 13:51:20 +08:00
data = []
2026-01-20 13:45:46 +08:00
for info in case_list:
2026-01-15 11:54:16 +08:00
pro = None
if info.project_id:
try:
pro = ProjectRegistration.objects.get(id=info.project_id, is_deleted=False)
except ProjectRegistration.DoesNotExist:
pro = None
contract_no = info.contract_no or (pro.ContractNo if pro else None)
project_type = info.project_type or (pro.type if pro else None)
client_name = info.client_name or (pro.client_info if pro else None)
party_name = info.party_name or (pro.party_info if pro else None)
project_description = info.project_description or (pro.description if pro else None)
responsiblefor_raw = info.responsiblefor or (pro.responsiblefor if pro else None)
charge = info.charge or (pro.charge if pro else None)
2026-01-04 15:44:07 +08:00
2026-01-14 15:40:39 +08:00
# 解析负责人信息JSON字符串转字典
try:
2026-01-15 11:54:16 +08:00
responsiblefor_dict = json.loads(responsiblefor_raw) if responsiblefor_raw else {}
except (json.JSONDecodeError, TypeError):
responsiblefor_dict = responsiblefor_raw if responsiblefor_raw else {}
2026-01-14 15:40:39 +08:00
2026-01-31 11:21:46 +08:00
# 解析结案申请JSON字符串转列表代理合同已从案件管理移除不再返回
2026-01-15 16:34:58 +08:00
try:
closing_application_list = json.loads(info.Closingapplication) if info.Closingapplication else []
except (json.JSONDecodeError, TypeError):
closing_application_list = []
2026-01-21 15:14:07 +08:00
if not closing_application_list:
closing_application_list = ""
2026-01-22 14:25:53 +08:00
contractreturn_str = info.Contractreturn or ""
if contractreturn_str.strip() in ["[]", ""]:
contractreturn_str = ""
2026-01-21 15:24:16 +08:00
closing_application_str = info.Closingapplication or ""
if closing_application_str.strip() in ["[]", ""]:
closing_application_str = ""
2026-01-20 13:45:46 +08:00
# 从财务数据动态获取开票金额和收入确认金额
invoice_status_value = "0"
paymentcollection_value = "0"
if contract_no:
# 从财务数据获取开票金额
invoice_status_value = invoice_amounts.get(contract_no, "0")
# 从财务数据获取收入确认金额
paymentcollection_value = income_amounts.get(contract_no, "0")
else:
# 如果没有合同号使用Case表中的旧数据向后兼容
invoice_status_value = normalize_amount_value(info.invoice_status)
paymentcollection_value = normalize_amount_value(info.paymentcollection)
2026-01-15 16:41:19 +08:00
2026-01-19 10:48:36 +08:00
# 获取案件标签
tags = info.tags.filter(is_deleted=False)
tag_list = []
for tag in tags:
tag_list.append({
'id': tag.id,
'name': tag.name,
'color': tag.color
})
2026-01-22 11:54:24 +08:00
# 如果没有标签,返回空字符串而不是空数组
tags_value = tag_list if tag_list else ""
tag_ids_value = [tag['id'] for tag in tag_list] if tag_list else ""
# 如果没有负责人信息,返回空字符串而不是空字典
# 检查 responsiblefor_dict 是否为有效的非空字典
if responsiblefor_dict and isinstance(responsiblefor_dict, dict) and len(responsiblefor_dict) > 0:
responsiblefor_value = responsiblefor_dict
else:
responsiblefor_value = ""
2026-01-14 15:04:44 +08:00
data.append({
"id": info.id,
2026-01-15 16:34:58 +08:00
"ContractNo": contract_no or "", # 合同编号
"type": project_type or "", # 项目类型
"client_info": client_name or "", # 客户名称(委托人身份信息)
"party_info": party_name or "", # 相对方名称
2026-01-22 11:54:24 +08:00
"description": project_description or "", # 项目简述(没有数据时返回空字符串)
"responsiblefor": responsiblefor_value, # 负责人信息(字典格式,没有数据时返回空字符串)
"charge": charge or "", # 收费情况(没有数据时返回空字符串)
'times': info.times or "", # 立案时间(没有数据时返回空字符串)
2026-01-22 14:25:53 +08:00
"Contractreturn": contractreturn_str, # 合同返还(没有数据时返回空字符串)
2026-01-21 15:24:16 +08:00
"Closingapplication": closing_application_str, # 结案申请JSON字符串没有文件时返回空字符串
2026-01-21 15:14:07 +08:00
"ClosingapplicationUrls": closing_application_list if closing_application_list else "", # 结案申请URL列表没有文件时返回空字符串
2026-01-22 11:54:24 +08:00
"ChangeRequest": info.ChangeRequest or "", # 变更申请(兼容旧字段,没有数据时返回空字符串)
"ChangeItem": info.ChangeItem or "", # 变更事项(没有数据时返回空字符串)
"ChangeReason": info.ChangeReason or "", # 变更原因(没有数据时返回空字符串)
"ChangeAgreement": info.ChangeAgreement or "", # 变更协议(没有数据时返回空字符串)
"invoice_status": invoice_status_value or "0", # 已开票(从财务数据动态计算,没有数据时返回"0"
"paymentcollection": paymentcollection_value or "0", # 已收款(从财务数据动态计算,没有数据时返回"0"
"state": info.state or "", # 状态(没有数据时返回空字符串)
"project_id": info.project_id if info.project_id is not None else "", # 项目ID没有数据时返回空字符串
"tags": tags_value, # 标签列表(没有数据时返回空字符串)
"tag_ids": tag_ids_value, # 标签ID列表没有数据时返回空字符串
"AgencyContract":info.AgencyContract
2026-01-14 15:04:44 +08:00
})
2025-12-24 13:51:20 +08:00
return Response({'message': '展示成功', "total": total, 'data': data, 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-25 17:08:49 +08:00
class EditCase(APIView):
def post(self, request, *args, **kwargs):
"""
编辑案件管理
:param request:
:param args:
:param kwargs:
:return:
"""
id = request.data.get('id')
times = request.data.get('times')
2026-01-15 13:34:07 +08:00
Contractreturn = request.FILES.getlist('Contractreturn')
Closingapplication = request.FILES.getlist('Closingapplication')
2026-01-14 15:04:44 +08:00
invoice_status = request.data.get('invoice_status') # 已开票
2025-12-25 17:08:49 +08:00
paymentcollection = request.data.get('paymentcollection')
2026-01-19 10:48:36 +08:00
tag_ids = request.data.get('tag_ids') # 标签ID列表数组或逗号分隔的字符串
2026-01-15 11:41:49 +08:00
approvers = request.data.get('approvers')
2025-12-25 17:08:49 +08:00
personincharge = request.data.get('personincharge')
2026-01-15 11:41:49 +08:00
approvers = normalize_approvers_param(approvers, personincharge)
2026-01-04 15:44:07 +08:00
2025-12-25 17:08:49 +08:00
# 检查不可修改的参数(前端不会传入,但作为安全措施进行检查)
forbidden_params = []
2026-01-14 15:04:44 +08:00
if 'project_id' in request.data:
forbidden_params.append('project_id')
2026-01-04 15:44:07 +08:00
2025-12-25 17:08:49 +08:00
if forbidden_params:
return Response({
2026-01-04 15:44:07 +08:00
'status': 'error',
'message': f'以下参数不允许修改: {", ".join(forbidden_params)}',
2025-12-25 17:08:49 +08:00
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
2026-01-04 15:44:07 +08:00
2025-12-25 17:08:49 +08:00
if not id:
return Response({'status': 'error', 'message': '缺少参数id', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2026-01-04 15:44:07 +08:00
2025-12-25 17:08:49 +08:00
try:
2025-12-31 14:17:33 +08:00
case = Case.objects.get(id=id, is_deleted=False)
2025-12-25 17:08:49 +08:00
except Case.DoesNotExist:
2026-01-04 15:44:07 +08:00
return Response({'status': 'error', 'message': '案件管理不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
2025-12-25 17:08:49 +08:00
update_fields_list = []
2026-01-04 15:44:07 +08:00
2025-12-25 17:08:49 +08:00
if times:
case.times = times
update_fields_list.append('times')
2026-01-04 15:44:07 +08:00
2026-01-15 13:34:07 +08:00
if Contractreturn:
case.Contractreturn = json.dumps(flies(Contractreturn), ensure_ascii=False)
2025-12-25 17:08:49 +08:00
update_fields_list.append('Contractreturn')
2026-01-04 15:44:07 +08:00
2026-01-15 13:34:07 +08:00
if Closingapplication:
case.Closingapplication = json.dumps(flies(Closingapplication), ensure_ascii=False)
2025-12-25 17:08:49 +08:00
update_fields_list.append('Closingapplication')
2026-01-04 15:44:07 +08:00
2026-01-15 16:47:13 +08:00
if paymentcollection is not None:
case.paymentcollection = normalize_amount_value(paymentcollection)
2025-12-25 17:08:49 +08:00
update_fields_list.append('paymentcollection')
2026-01-04 15:44:07 +08:00
2026-01-15 16:47:13 +08:00
if invoice_status is not None:
case.invoice_status = normalize_amount_value(invoice_status)
2026-01-14 15:04:44 +08:00
update_fields_list.append('invoice_status')
2025-12-25 17:08:49 +08:00
if update_fields_list:
case.save(update_fields=update_fields_list)
2026-01-19 10:48:36 +08:00
# 处理标签更新
if tag_ids is not None:
try:
# 支持数组或逗号分隔的字符串
if isinstance(tag_ids, str):
if tag_ids.strip() == '':
tag_id_list = []
elif ',' in tag_ids:
tag_id_list = [int(x.strip()) for x in tag_ids.split(',') if x.strip()]
else:
tag_id_list = [int(tag_ids)]
elif isinstance(tag_ids, list):
tag_id_list = [int(x) for x in tag_ids if x]
else:
tag_id_list = []
# 验证标签是否存在并更新关联
valid_tags = CaseTag.objects.filter(id__in=tag_id_list, is_deleted=False)
case.tags.set(valid_tags)
except (ValueError, TypeError) as e:
# 标签ID格式错误但不影响案件更新
pass
2026-01-04 15:44:07 +08:00
2025-12-25 17:08:49 +08:00
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 15:20:41 +08:00
class DeleteCase(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)
2026-01-04 15:44:07 +08:00
2025-12-25 15:20:41 +08:00
try:
2025-12-31 11:42:28 +08:00
case = Case.objects.get(id=id, is_deleted=False)
2025-12-25 15:20:41 +08:00
except Case.DoesNotExist:
2026-01-04 15:44:07 +08:00
return Response({'status': 'error', 'message': '案件管理不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
2025-12-31 12:28:10 +08:00
# 记录操作前的数据
old_data = {
'id': case.id,
'times': case.times,
'state': case.state
}
2026-01-04 15:44:07 +08:00
2025-12-31 11:42:28 +08:00
# 软删除:更新 is_deleted 字段
case.is_deleted = True
case.save()
2026-01-04 15:44:07 +08:00
2025-12-31 12:28:10 +08:00
# 记录操作日志
log_operation(
request=request,
operation_type='DELETE',
module='Business',
action='删除案件管理',
target_type='Case',
target_id=case.id,
target_name=f'案件-{case.id}',
old_data=old_data,
remark=f'删除案件管理ID {case.id}'
)
2026-01-04 15:44:07 +08:00
2025-12-25 15:20:41 +08:00
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-24 13:51:20 +08:00
class Uploadinvoice(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2026-01-14 15:04:44 +08:00
"""案件管理发票 - 关联案件"""
case_id = request.data.get('case_id') # 案件ID
2025-12-24 13:51:20 +08:00
amount = request.data.get('amount')
file = request.FILES.getlist('file')
2026-01-14 15:04:44 +08:00
if not case_id:
return Response({'status': 'error', 'message': '缺少参数case_id', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
# 检查案件是否存在
try:
case = Case.objects.get(id=case_id, is_deleted=False)
except Case.DoesNotExist:
return Response({'status': 'error', 'message': '案件不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
2025-12-24 13:51:20 +08:00
Invoice.objects.create(
2026-01-14 15:04:44 +08:00
case_id=case_id,
2025-12-24 13:51:20 +08:00
amount=amount,
file=json.dumps(flies(file)),
)
return Response({'message': '上传成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-24 13:51:20 +08:00
class InvoiceDetail(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
2026-01-14 15:04:44 +08:00
发票展示 - 关联案件
2025-12-24 13:51:20 +08:00
:param request:
:param args:
:param kwargs:
:return:
"""
2026-01-14 15:04:44 +08:00
case_id = request.data.get('case_id') # 案件ID
if not case_id:
return Response({'status': 'error', 'message': '缺少参数case_id', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2025-12-24 13:51:20 +08:00
2026-01-14 15:04:44 +08:00
invoices = Invoice.objects.filter(case_id=case_id, is_deleted=False)
2025-12-24 13:51:20 +08:00
data = []
for invoice in invoices:
data.append({
"id": invoice.id,
"amount": invoice.amount,
"file": invoice.file,
})
return Response({'message': '展示成功', 'data': data, 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-24 13:51:20 +08:00
class Log(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
2026-01-14 15:04:44 +08:00
案件日志 - 关联案件
2025-12-24 13:51:20 +08:00
"""
token = request.META.get('token')
2026-01-15 17:51:42 +08:00
# 明确拒绝 user_id 参数此接口使用案件ID不再使用预立案ID
if 'user_id' in request.data:
return Response({'status': 'error', 'message': '此接口不需要 user_id 参数,请使用 case_id案件ID', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2026-01-14 15:04:44 +08:00
case_id = request.data.get('case_id') # 案件ID
2025-12-24 13:51:20 +08:00
content = request.data.get('content')
file = request.FILES.getlist('file')
2026-01-14 15:04:44 +08:00
if not all([case_id, content]):
2026-01-15 17:51:42 +08:00
return Response({'status': 'error', 'message': '缺少参数case_id或content', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2026-01-14 15:04:44 +08:00
# 检查案件是否存在
try:
case = Case.objects.get(id=case_id, is_deleted=False)
except Case.DoesNotExist:
return Response({'status': 'error', 'message': '案件不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
2026-01-12 17:32:15 +08:00
user = User.objects.get(token=token, is_deleted=False)
2025-12-24 13:51:20 +08:00
now = datetime.now()
date_str = now.strftime('%Y-%m-%d')
Caselog.objects.create(
2026-01-14 15:04:44 +08:00
case_id=case_id,
2025-12-24 13:51:20 +08:00
content=content,
file=json.dumps(flies(file)),
2025-12-25 13:09:20 +08:00
times=date_str,
username=user.username,
2025-12-24 13:51:20 +08:00
)
return Response({'message': '添加成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-24 13:51:20 +08:00
class LogDetail(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
2026-01-31 11:39:15 +08:00
案件日志展示按案件ID查询
必填参数case_id案件IDpageper_page
2025-12-24 13:51:20 +08:00
"""
page = request.data.get('page')
per_page = request.data.get('per_page')
2026-01-31 11:39:15 +08:00
case_id = request.data.get('case_id') # 案件ID必填
2025-12-24 13:51:20 +08:00
if not all([page, per_page]):
2026-01-31 11:39:15 +08:00
return Response({'status': 'error', 'message': '缺少参数page或per_page', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
if not case_id:
return Response({'status': 'error', 'message': '缺少参数case_id案件ID', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2025-12-24 13:51:20 +08:00
2026-01-15 17:51:42 +08:00
# 根据案件ID过滤日志
2026-01-31 11:39:15 +08:00
pre = Caselog.objects.filter(is_deleted=False, case_id=case_id)
total = pre.count()
2025-12-24 13:51:20 +08:00
paginator = Paginator(pre, per_page)
try:
user_agents_page = paginator.page(page)
except PageNotAnInteger:
user_agents_page = paginator.page(1)
except EmptyPage:
user_agents_page = paginator.page(paginator.num_pages)
data = []
for info in user_agents_page.object_list:
data.append({
"id": info.id,
2026-01-15 17:51:42 +08:00
"case_id": info.case_id if info.case else None, # 案件ID
2026-01-21 17:04:02 +08:00
"content": info.content, # 日志内容
"times": info.times, # 时间
"username": info.username, # 提交人(保留兼容性)
"recorder": info.username, # 记录人
"file": info.file, # 文件
2025-12-24 13:51:20 +08:00
})
return Response({'message': '展示成功', "total": total, 'data': data, 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-24 13:51:20 +08:00
class accumulate(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
2026-01-14 15:04:44 +08:00
累加 - 关联案件
2025-12-24 13:51:20 +08:00
:param request:
:param args:
:param kwargs:
:return:
"""
2026-01-14 15:04:44 +08:00
case_id = request.data.get('case_id') # 案件ID
2025-12-24 13:51:20 +08:00
paymentcollection = request.data.get('paymentcollection')
2026-01-14 15:04:44 +08:00
if not case_id:
return Response({'status': 'error', 'message': '缺少参数case_id', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
try:
case = Case.objects.get(id=case_id, is_deleted=False)
except Case.DoesNotExist:
return Response({'status': 'error', 'message': '案件不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
2026-01-15 16:47:13 +08:00
current_amount_str = normalize_amount_value(case.paymentcollection)
add_amount_str = normalize_amount_value(paymentcollection)
try:
current_amount = Decimal(current_amount_str)
except InvalidOperation:
current_amount = Decimal("0")
try:
add_amount = Decimal(add_amount_str)
except InvalidOperation:
add_amount = Decimal("0")
case.paymentcollection = str(current_amount + add_amount)
2025-12-24 13:51:20 +08:00
case.save(update_fields=['paymentcollection'])
2025-12-25 13:09:20 +08:00
return Response({'message': '成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-24 13:51:20 +08:00
class preFilingLinkedCases(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
2026-01-14 15:04:44 +08:00
立项登记列表接口用于新增案件
2026-01-30 18:05:41 +08:00
权限管理员可见全部承办人仅可见自己承办的立项
2026-01-14 15:04:44 +08:00
返回可以用于新增案件的立项登记列表排除已有案件的立项登记
2025-12-24 13:51:20 +08:00
:param request:
:param args:
:param kwargs:
:return:
"""
2026-01-30 18:05:41 +08:00
pre_qs, err = _get_project_registration_queryset_by_permission(request)
if err is not None:
return err
2026-01-14 15:04:44 +08:00
case_project_ids = Case.objects.filter(is_deleted=False).values_list('project_id', flat=True)
2026-01-30 18:05:41 +08:00
projects = pre_qs.exclude(id__in=case_project_ids)
2026-01-04 15:44:07 +08:00
2025-12-24 13:51:20 +08:00
data = []
2026-01-14 15:04:44 +08:00
for project in projects:
data.append({
"id": project.id,
"ContractNo": project.ContractNo,
"type": project.type,
"client_info": project.client_info,
"party_info": project.party_info,
"description": project.description,
})
2026-01-04 15:44:07 +08:00
2025-12-24 13:51:20 +08:00
return Response({'message': '展示成功', 'data': data, 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2026-01-15 15:20:35 +08:00
class ProjectDropdownList(APIView):
"""获取成功立项的下拉列表接口"""
def post(self, request, *args, **kwargs):
"""
获取已通过审核的立项登记列表用于下拉选择
2026-01-30 18:05:41 +08:00
权限管理员可见全部承办人仅可见自己承办的立项
2026-01-15 15:20:35 +08:00
返回成功立项的数据格式简洁适合下拉列表使用
:param request:
:param args:
:param kwargs:
:return:
"""
2026-01-30 18:05:41 +08:00
# 权限过滤:管理员全部,承办人只看自己承办的
pre_qs, err = _get_project_registration_queryset_by_permission(request)
if err is not None:
return err
# 已通过审核的立项
projects = pre_qs.filter(state="已通过").order_by('-id')
2026-01-15 15:20:35 +08:00
2026-01-30 18:05:41 +08:00
# 可选参数:是否排除已有案件的立项
exclude_has_case = request.data.get('exclude_has_case', False)
2026-01-15 15:20:35 +08:00
if exclude_has_case:
case_project_ids = Case.objects.filter(is_deleted=False).values_list('project_id', flat=True)
projects = projects.exclude(id__in=case_project_ids)
data = []
import json
for project in projects:
2026-01-30 18:05:41 +08:00
# 解析承办人信息(原 responsible_person 即承办人)
2026-01-15 15:20:35 +08:00
responsible_person = ""
try:
responsiblefor_dict = json.loads(project.responsiblefor) if project.responsiblefor else {}
if isinstance(responsiblefor_dict, dict):
responsible_person = responsiblefor_dict.get('responsible_person', '')
except (json.JSONDecodeError, TypeError):
pass
display_text = f"{project.ContractNo}"
if project.type:
display_text += f" - {project.type}"
if responsible_person:
display_text += f" ({responsible_person})"
data.append({
"id": project.id,
2026-01-30 18:05:41 +08:00
"value": project.id,
"label": display_text,
"ContractNo": project.ContractNo,
"type": project.type,
"client_info": project.client_info,
"party_info": project.party_info,
"description": project.description,
"responsible_person": responsible_person, # 承办人(保留字段名兼容)
"times": project.times,
2026-01-15 15:20:35 +08:00
})
return Response({
'message': '查询成功',
'data': data,
'total': len(data),
'code': 0
}, status=status.HTTP_200_OK)
2026-01-15 16:55:35 +08:00
class CaseDropdownList(APIView):
"""获取案件的下拉列表接口"""
def post(self, request, *args, **kwargs):
"""
获取案件列表用于下拉选择
2026-02-05 15:37:51 +08:00
返回案件数据格式简洁适合下拉列表使用
2026-02-05 15:41:44 +08:00
可选按标签筛选 tags字符串为名称模糊数组如 [2,3] 为标签ID tag_ids匹配拥有任一标签的案件
2026-02-05 15:37:51 +08:00
:param request: 可选 state, tags, tag_ids
2026-01-15 16:55:35 +08:00
:return:
"""
2026-02-05 15:41:44 +08:00
state_filter = request.data.get('state', None)
tags = request.data.get('tags') # 字符串=名称模糊,数组=标签ID如 [2,3]
tag_ids = request.data.get('tag_ids')
2026-01-15 16:55:35 +08:00
Q_obj = Q(is_deleted=False)
if state_filter:
Q_obj &= Q(state=state_filter)
2026-02-05 15:37:51 +08:00
tag_id_list = []
2026-02-05 15:41:44 +08:00
if tags is not None and tags != '':
if isinstance(tags, list):
try:
tag_id_list.extend([int(x) for x in tags if x is not None and str(x).strip() != ''])
except (ValueError, TypeError):
pass
elif isinstance(tags, str):
2026-02-05 15:44:19 +08:00
tags_stripped = tags.strip()
if tags_stripped.startswith('[') and tags_stripped.endswith(']'):
try:
parsed = json.loads(tags_stripped)
if isinstance(parsed, list):
tag_id_list.extend([int(x) for x in parsed if x is not None and str(x).strip() != ''])
except (ValueError, TypeError, json.JSONDecodeError):
pass
if not tag_id_list:
tag_id_list.extend(
CaseTag.objects.filter(name__icontains=tags, is_deleted=False).values_list('id', flat=True)
)
2026-02-05 15:37:51 +08:00
if tag_ids:
try:
if isinstance(tag_ids, str):
tag_id_list.extend([int(x.strip()) for x in tag_ids.split(',') if x.strip()])
elif isinstance(tag_ids, list):
tag_id_list.extend([int(x) for x in tag_ids if x is not None and str(x).strip() != ''])
except (ValueError, TypeError):
pass
2026-02-05 15:41:44 +08:00
tag_id_list = list(set(tag_id_list))
2026-02-05 15:37:51 +08:00
if tag_id_list:
Q_obj &= Q(tags__in=tag_id_list)
2026-01-15 16:55:35 +08:00
cases = Case.objects.filter(Q_obj).order_by('-id')
2026-02-05 15:37:51 +08:00
if tag_id_list:
cases = cases.distinct()
2026-01-15 16:55:35 +08:00
data = []
for case in cases:
# 解析负责人信息,获取负责人姓名
responsible_person = ""
try:
responsiblefor_dict = json.loads(case.responsiblefor) if case.responsiblefor else {}
if isinstance(responsiblefor_dict, dict):
responsible_person = responsiblefor_dict.get('responsible_person', '')
except (json.JSONDecodeError, TypeError):
pass
# 构建显示文本(用于下拉列表显示)
display_text = case.contract_no or f"案件#{case.id}"
if case.project_type:
display_text += f" - {case.project_type}"
if case.client_name:
display_text += f" ({case.client_name})"
elif responsible_person:
display_text += f" ({responsible_person})"
data.append({
"id": case.id,
"value": case.id, # 下拉列表的值
"label": display_text, # 下拉列表的显示文本
"contract_no": case.contract_no or "", # 合同编号
"project_type": case.project_type or "", # 项目类型
"client_name": case.client_name or "", # 客户名称
"party_name": case.party_name or "", # 相对方名称
"project_description": case.project_description or "", # 项目简述
"responsible_person": responsible_person, # 负责人
"times": case.times, # 立案时间
"state": case.state, # 状态
})
return Response({
'message': '查询成功',
'data': data,
'total': len(data),
'code': 0
}, status=status.HTTP_200_OK)
2025-12-24 13:51:20 +08:00
class Application(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
申请用印
:param request:
:param args:
:param kwargs:
:return:
"""
token = request.META.get('token')
Printingpurpose = request.data.get('Printingpurpose')
2026-01-21 10:34:51 +08:00
# 案件编号(前端可不传),后端自动同步为“合同号”
# 兼容:仍支持直接传 CaseNumber即合同号
2025-12-24 13:51:20 +08:00
CaseNumber = request.data.get('CaseNumber')
2026-01-21 10:34:51 +08:00
case_id = request.data.get('case_id')
project_id = request.data.get('project_id')
2025-12-24 13:51:20 +08:00
Reason = request.data.get('Reason')
seal_number = request.data.get('seal_number')
seal_type = request.data.get('seal_type')
file = request.FILES.getlist('file')
2026-01-15 11:41:49 +08:00
approvers = request.data.get('approvers')
2025-12-24 13:51:20 +08:00
personincharge = request.data.get('personincharge')
2026-01-15 11:41:49 +08:00
approvers = normalize_approvers_param(approvers, personincharge)
2025-12-24 13:51:20 +08:00
2026-01-15 11:41:49 +08:00
if not all([Printingpurpose, Reason, seal_number, seal_type, file]):
2025-12-24 13:51:20 +08:00
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
now = datetime.now()
date_str = now.strftime('%Y-%m-%d')
2026-01-12 17:32:15 +08:00
user = User.objects.get(token=token, is_deleted=False)
2026-01-21 10:34:51 +08:00
# 自动查询合同号写入 CaseNumber案件编号
# 优先级case_id > project_id > CaseNumber兼容旧传参
contract_no = None
if case_id:
try:
case = Case.objects.select_related('project').get(id=case_id, is_deleted=False)
contract_no = case.contract_no or (case.project.ContractNo if case.project else None)
except Case.DoesNotExist:
return Response({'status': 'error', 'message': '案件不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
elif project_id:
try:
project = ProjectRegistration.objects.get(id=project_id, is_deleted=False)
contract_no = project.ContractNo
except ProjectRegistration.DoesNotExist:
return Response({'status': 'error', 'message': '立项登记不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
else:
contract_no = CaseNumber
if not contract_no:
return Response({
'status': 'error',
'message': '缺少案件信息:请传 case_id 或 project_id 以自动获取合同号,或直接传 CaseNumber合同号',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
2025-12-24 13:51:20 +08:00
sea = SealApplication.objects.create(
Printingpurpose=Printingpurpose,
2026-01-21 10:34:51 +08:00
CaseNumber=contract_no,
2025-12-24 13:51:20 +08:00
Reason=Reason,
seal_number=seal_number,
seal_type=seal_type,
file=json.dumps(flies(file)),
times=date_str,
state="审核中",
username=user.username,
)
2026-01-15 14:42:21 +08:00
team_name = user.team
2026-01-15 11:41:49 +08:00
from User.utils import create_approval_with_team_logic
approval, approvers_order_json, needs_approval = create_approval_with_team_logic(
2026-01-15 14:42:21 +08:00
team_name=team_name,
2026-01-15 11:41:49 +08:00
approvers=approvers,
2025-12-24 13:51:20 +08:00
title=user.username + "申请用印",
2026-01-21 10:34:51 +08:00
content=user.username + "" + date_str + "申请用印,合同编号:" + str(contract_no) + ",用印事由:" + Reason + ",盖章份数:" + seal_number + "盖着类型:" + seal_type,
2026-01-15 11:41:49 +08:00
approval_type="申请用印",
user_id=sea.id,
business_record=sea,
today=date_str
2025-12-24 13:51:20 +08:00
)
2026-01-15 11:41:49 +08:00
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)
2025-12-24 13:51:20 +08:00
return Response({'message': '添加成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-24 13:51:20 +08:00
class ApplicationDetail(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
申请用印展示
:param request:
:param args:
:param kwargs:
:return:
"""
page = request.data.get('page')
per_page = request.data.get('per_page')
times = request.data.get('times')
end_time = request.data.get('end_time')
seal_type = request.data.get('seal_type')
CaseNumber = request.data.get('CaseNumber')
2025-12-24 13:51:20 +08:00
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 seal_type:
Q_obj &= Q(seal_type=seal_type)
if CaseNumber:
Q_obj &= Q(CaseNumber__icontains=CaseNumber)
2025-12-31 14:17:33 +08:00
seas = SealApplication.objects.filter(Q_obj, is_deleted=False).order_by('-id')
2025-12-24 13:51:20 +08:00
total = len(seas)
paginator = Paginator(seas, per_page)
try:
user_agents_page = paginator.page(page)
except PageNotAnInteger:
user_agents_page = paginator.page(1)
except EmptyPage:
user_agents_page = paginator.page(paginator.num_pages)
data = []
for info in user_agents_page.object_list:
data.append({
"id": info.id,
"Printingpurpose": info.Printingpurpose,
"CaseNumber": info.CaseNumber,
"Reason": info.Reason,
"seal_number": info.seal_number,
"seal_type": info.seal_type,
"file": info.file,
'times': info.times,
"state": info.state,
"username": info.username,
})
return Response({'message': '展示成功', "total": total, 'data': data, 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-25 15:20:41 +08:00
class EditApplication(APIView):
def post(self, request, *args, **kwargs):
"""
编辑申请用印
:param request:
:param args:
:param kwargs:
:return:
"""
id = request.data.get('id')
Printingpurpose = request.data.get('Printingpurpose')
2026-01-21 10:34:51 +08:00
# 案件编号(合同号):支持前端直接传,也支持通过 case_id/project_id 自动同步
2025-12-25 15:20:41 +08:00
CaseNumber = request.data.get('CaseNumber')
2026-01-21 10:34:51 +08:00
case_id = request.data.get('case_id')
project_id = request.data.get('project_id')
2025-12-25 15:20:41 +08:00
Reason = request.data.get('Reason')
seal_number = request.data.get('seal_number')
seal_type = request.data.get('seal_type')
file = request.FILES.getlist('file')
2026-01-15 14:42:21 +08:00
approvers = request.data.get('approvers')
2025-12-25 15:20:41 +08:00
personincharge = request.data.get('personincharge')
2026-01-15 14:42:21 +08:00
approvers = normalize_approvers_param(approvers, personincharge)
2026-01-04 15:44:07 +08:00
2025-12-25 15:20:41 +08:00
if not id:
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2026-01-04 15:44:07 +08:00
2025-12-25 15:20:41 +08:00
try:
2025-12-31 14:17:33 +08:00
app = SealApplication.objects.get(id=id, is_deleted=False)
2025-12-25 15:20:41 +08:00
except SealApplication.DoesNotExist:
2026-01-04 15:44:07 +08:00
return Response({'status': 'error', 'message': '申请用印不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
2025-12-25 15:20:41 +08:00
if Printingpurpose:
app.Printingpurpose = Printingpurpose
app.save(update_fields=['Printingpurpose'])
2026-01-21 10:34:51 +08:00
# 优先通过 case_id / project_id 自动同步合同号
contract_no = None
if case_id:
try:
case = Case.objects.select_related('project').get(id=case_id, is_deleted=False)
contract_no = case.contract_no or (case.project.ContractNo if case.project else None)
except Case.DoesNotExist:
return Response({'status': 'error', 'message': '案件不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
elif project_id:
try:
project = ProjectRegistration.objects.get(id=project_id, is_deleted=False)
contract_no = project.ContractNo
except ProjectRegistration.DoesNotExist:
return Response({'status': 'error', 'message': '立项登记不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
elif CaseNumber:
contract_no = CaseNumber
if contract_no:
app.CaseNumber = contract_no
2025-12-25 15:20:41 +08:00
app.save(update_fields=['CaseNumber'])
if Reason:
app.Reason = Reason
app.save(update_fields=['Reason'])
if seal_number:
app.seal_number = seal_number
app.save(update_fields=['seal_number'])
if seal_type:
app.seal_type = seal_type
app.save(update_fields=['seal_type'])
if file:
app.file = json.dumps(flies(file))
app.save(update_fields=['file'])
2026-01-04 15:44:07 +08:00
2026-01-15 11:41:49 +08:00
if approvers:
2025-12-25 15:20:41 +08:00
now = datetime.now()
date_str = now.strftime('%Y-%m-%d')
2026-01-15 11:41:49 +08:00
from User.utils import create_approval_with_team_logic
team_name = get_user_team_name(app.username)
approval, approvers_order_json, needs_approval = create_approval_with_team_logic(
team_name=team_name,
approvers=approvers,
2025-12-25 15:20:41 +08:00
title=app.username + "申请用印重新编辑",
2026-01-21 10:34:51 +08:00
content=app.username + "" + date_str + "申请用印,合同编号:" + str(contract_no or app.CaseNumber) + ",用印事由:" + (
2026-01-04 15:54:35 +08:00
Reason or app.Reason) + ",盖章份数:" + (seal_number or app.seal_number) + "盖着类型:" + (
seal_type or app.seal_type),
2026-01-15 11:41:49 +08:00
approval_type="申请用印",
user_id=app.id,
business_record=app,
today=date_str
2025-12-25 15:20:41 +08:00
)
2026-01-15 11:41:49 +08:00
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)
2026-01-04 15:44:07 +08:00
2025-12-25 15:20:41 +08:00
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
class DeleteApplication(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)
2026-01-04 15:44:07 +08:00
2025-12-25 15:20:41 +08:00
try:
2025-12-31 11:42:28 +08:00
app = SealApplication.objects.get(id=id, is_deleted=False)
2025-12-25 15:20:41 +08:00
except SealApplication.DoesNotExist:
2026-01-04 15:44:07 +08:00
return Response({'status': 'error', 'message': '申请用印不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
2025-12-31 11:42:28 +08:00
# 软删除:更新 is_deleted 字段
app.is_deleted = True
app.save()
2025-12-25 15:20:41 +08:00
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-24 13:51:20 +08:00
class WarehousingRegistration(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
入库登记
:param request:
:param args:
:param kwargs:
:return:
"""
unit = request.data.get('unit')
mark = request.data.get('mark')
lawyer = request.data.get('lawyer')
deadline = request.data.get('deadline')
contract = request.FILES.getlist('contract')
if not all([unit, mark, lawyer, deadline, contract]):
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
now = datetime.now()
date_str = now.strftime('%Y-%m-%d')
Warehousing.objects.create(
unit=unit,
mark=mark,
lawyer=lawyer,
deadline=deadline,
contract=json.dumps(flies(contract)),
times=date_str,
)
return Response({'message': '添加成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-24 13:51:20 +08:00
class WarehousingDetail(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
入库登记展示
:param request:
:param args:
:param kwargs:
:return:
"""
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')
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)
2025-12-31 14:17:33 +08:00
seas = Warehousing.objects.filter(Q_obj, is_deleted=False).order_by('-id')
2025-12-24 13:51:20 +08:00
total = len(seas)
paginator = Paginator(seas, per_page)
try:
user_agents_page = paginator.page(page)
except PageNotAnInteger:
user_agents_page = paginator.page(1)
except EmptyPage:
user_agents_page = paginator.page(paginator.num_pages)
data = []
for info in user_agents_page.object_list:
data.append({
"id": info.id,
"unit": info.unit,
"mark": info.mark,
"lawyer": info.lawyer,
"deadline": info.deadline,
"contract": info.contract,
"times": info.times,
})
return Response({'message': '展示成功', "total": total, 'data': data, 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-25 15:20:41 +08:00
class EditWarehousing(APIView):
def post(self, request, *args, **kwargs):
"""
编辑入库登记
:param request:
:param args:
:param kwargs:
:return:
"""
id = request.data.get('id')
unit = request.data.get('unit')
mark = request.data.get('mark')
lawyer = request.data.get('lawyer')
deadline = request.data.get('deadline')
contract = request.FILES.getlist('contract')
2026-01-04 15:44:07 +08:00
2025-12-25 15:20:41 +08:00
if not id:
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2026-01-04 15:44:07 +08:00
2025-12-25 15:20:41 +08:00
try:
2025-12-31 14:17:33 +08:00
ware = Warehousing.objects.get(id=id, is_deleted=False)
2025-12-25 15:20:41 +08:00
except Warehousing.DoesNotExist:
2026-01-04 15:44:07 +08:00
return Response({'status': 'error', 'message': '入库登记不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
2025-12-25 15:20:41 +08:00
if unit:
ware.unit = unit
ware.save(update_fields=['unit'])
if mark:
ware.mark = mark
ware.save(update_fields=['mark'])
if lawyer:
ware.lawyer = lawyer
ware.save(update_fields=['lawyer'])
if deadline:
ware.deadline = deadline
ware.save(update_fields=['deadline'])
if contract:
ware.contract = json.dumps(flies(contract))
ware.save(update_fields=['contract'])
2026-01-04 15:44:07 +08:00
2025-12-25 15:20:41 +08:00
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
class DeleteWarehousing(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)
2026-01-04 15:44:07 +08:00
2025-12-25 15:20:41 +08:00
try:
2025-12-31 11:42:28 +08:00
ware = Warehousing.objects.get(id=id, is_deleted=False)
2025-12-25 15:20:41 +08:00
except Warehousing.DoesNotExist:
2026-01-04 15:44:07 +08:00
return Response({'status': 'error', 'message': '入库登记不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
2025-12-31 11:42:28 +08:00
# 软删除:更新 is_deleted 字段
ware.is_deleted = True
ware.save()
2025-12-25 15:20:41 +08:00
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-24 13:51:20 +08:00
class PlatformRegistration(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
注册平台登记
:param request:
:param args:
:param kwargs:
:return:
"""
platform = request.data.get('platform')
number = request.data.get('number')
password = request.data.get('password')
username = request.data.get('username')
if not all([platform, number, password, username]):
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
RegisterPlatform.objects.create(
platform=platform,
number=number,
password=password,
username=username,
)
return Response({'message': '添加成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-24 13:51:20 +08:00
class PlatformDetail(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
注册平台登记展示
:param request:
:param args:
:param kwargs:
:return:
"""
page = request.data.get('page')
per_page = request.data.get('per_page')
2025-12-31 11:42:28 +08:00
seas = RegisterPlatform.objects.filter(is_deleted=False).order_by('-id')
2025-12-24 13:51:20 +08:00
total = len(seas)
paginator = Paginator(seas, per_page)
try:
user_agents_page = paginator.page(page)
except PageNotAnInteger:
user_agents_page = paginator.page(1)
except EmptyPage:
user_agents_page = paginator.page(paginator.num_pages)
data = []
for info in user_agents_page.object_list:
data.append({
"id": info.id,
"platform": info.platform,
"number": info.number,
"password": info.password,
"username": info.username,
})
return Response({'message': '展示成功', "total": total, 'data': data, 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-24 13:51:20 +08:00
class EditPlatformDetail(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
注册平台登记编辑
:param request:
:param args:
:param kwargs:
:return:
"""
ID = request.data.get('id')
platform = request.data.get('platform')
number = request.data.get('number')
password = request.data.get('password')
username = request.data.get('username')
2025-12-25 13:09:20 +08:00
if not all([platform, number, password, username, ID]):
2025-12-24 13:51:20 +08:00
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2025-12-31 14:17:33 +08:00
reg = RegisterPlatform.objects.get(id=ID, is_deleted=False)
2025-12-24 13:51:20 +08:00
reg.platform = platform
reg.number = number
reg.password = password
reg.username = username
reg.save(update_fields=['platform', 'number', 'password', 'username'])
return Response({'message': '修改成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-24 13:51:20 +08:00
class DeletePlatformDetail(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
删除注册平台登记
:param request:
:param args:
:param kwargs:
:return:
"""
ID = request.data.get('id')
2025-12-31 11:42:28 +08:00
try:
platform = RegisterPlatform.objects.get(id=ID, is_deleted=False)
# 软删除:更新 is_deleted 字段
platform.is_deleted = True
platform.save()
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
except RegisterPlatform.DoesNotExist:
2026-01-04 15:44:07 +08:00
return Response({'status': 'error', 'message': '注册平台登记不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
2025-12-24 13:51:20 +08:00
2025-12-25 13:09:20 +08:00
2025-12-24 13:51:20 +08:00
class bulletin(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
公告
:param request:
:param args:
:param kwargs:
:return:
"""
2025-12-25 13:09:20 +08:00
title = request.data.get('title')
2025-12-24 13:51:20 +08:00
content = request.data.get('content')
file = request.FILES.getlist('file')
state = request.data.get('state')
token = request.META.get('token')
if not all([title, content]):
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
now = datetime.now()
date_str = now.strftime('%Y-%m-%d')
bol = 0
if state == "置顶":
bol = 1
2026-01-12 17:32:15 +08:00
user = User.objects.get(token=token, is_deleted=False).username
2025-12-24 13:51:20 +08:00
Announcement.objects.create(
title=title,
content=content,
file=json.dumps(flies(file)),
username=user,
times=date_str,
state=bol
)
return Response({'message': '添加成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-24 13:51:20 +08:00
class BulletinDetail(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
公告展示
:param request:
:param args:
:param kwargs:
:return:
"""
page = request.data.get('page')
per_page = request.data.get('per_page')
2025-12-31 11:42:28 +08:00
seas = Announcement.objects.filter(is_deleted=False).order_by('-state', '-times')
2025-12-24 13:51:20 +08:00
total = len(seas)
paginator = Paginator(seas, 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:
2026-02-01 13:02:16 +08:00
# 解析 file 字段:有文件返回数组,无文件返回空字符串
try:
file_list = json.loads(info.file) if info.file else []
except (json.JSONDecodeError, TypeError):
file_list = []
# 空列表返回空字符串
file_result = file_list if file_list else ""
if file_list !="":
file_result = json.dumps(file_result)
2025-12-24 13:51:20 +08:00
data.append({
"id": info.id,
"title": info.title,
"content": info.content,
"times": info.times,
"file": file_result,#file_result,
2025-12-24 13:51:20 +08:00
"username": info.username,
"state": info.state,
})
return Response({'message': '展示成功', "total": total, 'data': data, 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-24 13:51:20 +08:00
class EditBulletin(APIView):
def post(self, request, *args, **kwargs):
"""
编辑公告
:param request:
:param args:
:param kwargs:
:return:
"""
ID = request.data.get('id')
title = request.data.get('title')
content = request.data.get('content')
file = request.FILES.getlist('file')
state = request.data.get('state')
2025-12-29 11:53:05 +08:00
token = request.META.get('token') # 从中间件设置的 token 获取
2025-12-24 13:51:20 +08:00
if not all([title, content, ID]):
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2025-12-29 11:53:05 +08:00
try:
2025-12-31 14:17:33 +08:00
ann = Announcement.objects.get(id=ID, is_deleted=False)
2025-12-29 11:53:05 +08:00
except Announcement.DoesNotExist:
return Response({'status': 'error', 'message': '公告不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
2025-12-24 13:51:20 +08:00
now = datetime.now()
date_str = now.strftime('%Y-%m-%d')
if state:
bol = 0
if state == "置顶":
bol = 1
ann.state = bol
ann.save(update_fields=['state'])
2025-12-29 11:53:05 +08:00
# 获取用户信息(如果 token 存在)
if token:
try:
2026-01-12 17:32:15 +08:00
user = User.objects.get(token=token, is_deleted=False).username
2025-12-29 11:53:05 +08:00
except User.DoesNotExist:
# 如果用户不存在,使用原有用户名
user = ann.username
else:
# 如果 token 不存在,使用原有用户名
user = ann.username
2025-12-24 13:51:20 +08:00
if title:
2025-12-29 11:53:05 +08:00
ann.title = title
2025-12-24 13:51:20 +08:00
ann.save(update_fields=['title'])
if content:
2025-12-29 11:53:05 +08:00
ann.content = content
2025-12-24 13:51:20 +08:00
ann.save(update_fields=['content'])
if file:
2025-12-29 11:53:05 +08:00
ann.file = json.dumps(flies(file)) # 使用 flies 函数处理文件
2025-12-24 13:51:20 +08:00
ann.save(update_fields=['file'])
2025-12-29 11:53:05 +08:00
ann.username = user
ann.times = date_str
2025-12-24 13:51:20 +08:00
ann.save(update_fields=['username', 'times'])
2025-12-29 11:53:05 +08:00
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-24 13:51:20 +08:00
2025-12-25 13:09:20 +08:00
2025-12-24 13:51:20 +08:00
class deleteBulletin(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
ID = request.data.get('id')
2025-12-31 11:42:28 +08:00
try:
announcement = Announcement.objects.get(id=ID, is_deleted=False)
# 软删除:更新 is_deleted 字段
announcement.is_deleted = True
announcement.save()
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
except Announcement.DoesNotExist:
return Response({'status': 'error', 'message': '公告不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
2025-12-24 13:51:20 +08:00
class Lawyersdocuments(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2026-01-22 18:38:29 +08:00
token = request.META.get('token')
2025-12-24 13:51:20 +08:00
file = request.FILES.getlist('file')
filename = file[0].name
name_without_extension, extension = os.path.splitext(filename)
2026-01-22 18:38:29 +08:00
user = User.objects.get(token=token, is_deleted=False).username
title = name_without_extension + extension
if not all([file]):
2025-12-24 13:51:20 +08:00
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
now = datetime.now()
date_str = now.strftime('%Y-%m-%d')
LawyerFlie.objects.create(
title=title,
2026-01-22 18:38:29 +08:00
remark=user,
2025-12-24 13:51:20 +08:00
file=json.dumps(flies(file)),
times=date_str,
)
return Response({'message': '文件上传成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-24 13:51:20 +08:00
class LawyersdocumentsDetail(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2026-01-22 18:38:29 +08:00
title = request.data.get('title')
obj =Q(is_deleted=False)
if title:
obj &= Q(title__contains=title)
law = LawyerFlie.objects.filter(obj).order_by('-id')
2025-12-24 13:51:20 +08:00
total = law.count()
data = []
for info in law:
data.append({
"id": info.id,
"title": info.title,
"remark": info.remark,
"file": info.file,
"times": info.times,
})
return Response({'message': '展示成功', "total": total, 'data': data, 'code': 0}, status=status.HTTP_200_OK)
class LwaDetail(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
id = request.data.get('id')
2025-12-31 11:42:28 +08:00
try:
lawyer_file = LawyerFlie.objects.get(id=id, is_deleted=False)
# 软删除:更新 is_deleted 字段
lawyer_file.is_deleted = True
lawyer_file.save()
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
except LawyerFlie.DoesNotExist:
2026-01-04 15:44:07 +08:00
return Response({'status': 'error', 'message': '律师文件不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
2025-12-24 13:51:20 +08:00
2025-12-25 13:09:20 +08:00
2025-12-25 15:20:41 +08:00
class EditLawyerFlie(APIView):
def post(self, request, *args, **kwargs):
"""
编辑律师文件
:param request:
:param args:
:param kwargs:
:return:
"""
id = request.data.get('id')
title = request.data.get('title')
file = request.FILES.getlist('file')
remark = request.data.get('remark')
2026-01-04 15:44:07 +08:00
2025-12-25 15:20:41 +08:00
if not id:
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2026-01-04 15:44:07 +08:00
2025-12-25 15:20:41 +08:00
try:
2025-12-31 14:17:33 +08:00
law = LawyerFlie.objects.get(id=id, is_deleted=False)
2025-12-25 15:20:41 +08:00
except LawyerFlie.DoesNotExist:
2026-01-04 15:44:07 +08:00
return Response({'status': 'error', 'message': '律师文件不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
2025-12-25 15:20:41 +08:00
if title:
law.title = title
law.save(update_fields=['title'])
if remark:
law.remark = remark
law.save(update_fields=['remark'])
if file:
law.file = json.dumps(flies(file))
law.save(update_fields=['file'])
2026-01-04 15:44:07 +08:00
2025-12-25 15:20:41 +08:00
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-24 13:51:20 +08:00
class CreateSchedule(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
2026-01-12 18:29:56 +08:00
待办创建
2026-01-22 18:38:29 +08:00
2025-12-24 13:51:20 +08:00
:param request:
:param args:
:param kwargs:
:return:
"""
title = request.data.get('title')
tiems = request.data.get('tiems')
end_time = request.data.get('end_time')
remark = request.data.get('remark')
2026-01-12 23:17:39 +08:00
approvers = request.data.get('approvers') # 审核人列表团队类型时需要推荐用户ID数组如[1,2,3],兼容:用户名数组)
2026-01-15 11:41:49 +08:00
personincharge = request.data.get('personincharge')
approvers = normalize_approvers_param(approvers, personincharge)
2025-12-24 13:51:20 +08:00
if not all([title, tiems, end_time]):
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2026-01-12 18:29:56 +08:00
# 获取当前用户
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_404_NOT_FOUND)
2026-01-22 18:38:29 +08:00
Schedule.objects.create(
2025-12-24 13:51:20 +08:00
title=title,
tiems=tiems,
end_time=end_time,
remark=remark,
2026-01-12 18:29:56 +08:00
state="未完成",
2026-01-15 11:41:49 +08:00
submit=user.username # 记录提交人
2025-12-24 13:51:20 +08:00
)
2026-01-22 18:38:29 +08:00
return Response({'message': '添加成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-24 13:51:20 +08:00
2025-12-25 13:09:20 +08:00
2025-12-24 13:51:20 +08:00
class DeleteSchedule(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-25 18:17:05 +08:00
"""
删除日程
:param request:
:param args:
:param kwargs:
:return:
"""
2025-12-24 13:51:20 +08:00
id = request.data.get('id')
2025-12-31 11:42:28 +08:00
try:
schedule = Schedule.objects.get(id=id, is_deleted=False)
# 软删除:更新 is_deleted 字段
schedule.is_deleted = True
schedule.save()
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
except Schedule.DoesNotExist:
return Response({'status': 'error', 'message': '日程不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
2025-12-24 13:51:20 +08:00
class ScheduleDetail(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
"""
2026-02-05 11:50:50 +08:00
日程展示仅展示用户主动创建的日程/待办不包含结案申请等审批承载的 Schedule
2025-12-24 13:51:20 +08:00
:param request:
:param args:
:param kwargs:
:return:
"""
2026-02-02 17:03:47 +08:00
title = request.data.get('title')
2026-02-05 11:50:50 +08:00
obj = Q(is_deleted=False)
2026-02-02 17:03:47 +08:00
if title:
obj &= Q(title__contains=title)
2026-02-05 11:50:50 +08:00
# 排除作为「结案申请」审批承载的 Schedule结案申请应出现在「审核中」而非「日程中」
closing_application_schedule_ids = Approval.objects.filter(
type="结案申请", is_deleted=False
).values_list('user_id', flat=True)
closing_application_schedule_ids = [int(sid) for sid in closing_application_schedule_ids if sid and str(sid).isdigit()]
if closing_application_schedule_ids:
obj &= ~Q(id__in=closing_application_schedule_ids)
2026-02-02 17:03:47 +08:00
sches = Schedule.objects.filter(obj).order_by('-id')
2025-12-24 13:51:20 +08:00
page = request.data.get('page')
per_page = request.data.get('per_page')
total = len(sches)
paginator = Paginator(sches, per_page)
try:
user_agents_page = paginator.page(page)
except PageNotAnInteger:
user_agents_page = paginator.page(1)
except EmptyPage:
user_agents_page = paginator.page(paginator.num_pages)
data = []
for info in user_agents_page.object_list:
data.append({
"id": info.id,
"title": info.title,
"tiems": info.tiems,
"end_time": info.end_time,
"remark": info.remark,
'state': info.state,
})
return Response({'message': '展示成功', "total": total, 'data': data, 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-25 15:20:41 +08:00
class EditSchedule(APIView):
def post(self, request, *args, **kwargs):
"""
编辑日程
:param request:
:param args:
:param kwargs:
:return:
"""
id = request.data.get('id')
title = request.data.get('title')
tiems = request.data.get('tiems')
end_time = request.data.get('end_time')
remark = request.data.get('remark')
2026-01-04 15:44:07 +08:00
2025-12-25 15:20:41 +08:00
if not id:
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2026-01-04 15:44:07 +08:00
2025-12-25 15:20:41 +08:00
try:
2025-12-31 14:08:01 +08:00
schedule = Schedule.objects.get(id=id, is_deleted=False)
2025-12-25 15:20:41 +08:00
except Schedule.DoesNotExist:
return Response({'status': 'error', 'message': '日程不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
2026-01-04 15:44:07 +08:00
2025-12-25 15:20:41 +08:00
if title:
schedule.title = title
schedule.save(update_fields=['title'])
if tiems:
schedule.tiems = tiems
schedule.save(update_fields=['tiems'])
if end_time:
schedule.end_time = end_time
schedule.save(update_fields=['end_time'])
if remark:
schedule.remark = remark
schedule.save(update_fields=['remark'])
2026-01-04 15:44:07 +08:00
2025-12-25 15:20:41 +08:00
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-24 13:51:20 +08:00
class handleSchedule(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
id = request.data.get('id')
state = request.data.get('state')
if not all([id, state]):
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2025-12-31 14:08:01 +08:00
try:
sc = Schedule.objects.get(id=id, is_deleted=False)
2026-01-22 18:38:29 +08:00
sc.state = "已完成"
2025-12-31 14:08:01 +08:00
sc.save(update_fields=['state'])
return Response({'message': '修改成功', 'code': 0}, status=status.HTTP_200_OK)
except Schedule.DoesNotExist:
return Response({'status': 'error', 'message': '日程不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
2025-12-24 13:51:20 +08:00
2025-12-25 13:09:20 +08:00
2025-12-24 13:51:20 +08:00
class AddRermission(APIView):
"""
添加权限
"""
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
permission_name = request.data.get('permission_name')
permission_logo = request.data.get('permission_logo')
parent = request.data.get('parent')
2025-12-25 13:09:20 +08:00
if not all([permission_name, permission_logo, parent]):
2025-12-24 13:51:20 +08:00
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
permission.objects.create(
permission_name=permission_name,
permission_logo=permission_logo,
parent=parent,
)
return Response({'message': '添加成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-24 13:51:20 +08:00
class DisplayRermission(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-26 10:27:46 +08:00
"""
权限列表展示
"""
2025-12-31 11:42:28 +08:00
permissions = permission.objects.filter(is_deleted=False)
2025-12-24 13:51:20 +08:00
# 先将数据转换为字典格式
data = []
permission_dict = {}
for per in permissions:
item = {
"id": str(per.id),
"permission_name": per.permission_name,
"permission_logo": per.permission_logo,
"parent": str(per.parent),
"children": []
}
data.append(item)
permission_dict[str(per.id)] = item
# 构建树形结构
tree = []
for item in data:
parent_id = item["parent"]
# 如果parent是0或者不存在说明是根节点
if parent_id == "0" or parent_id not in permission_dict:
tree.append(item)
else:
# 找到父节点并添加到children中
parent_node = permission_dict.get(parent_id)
if parent_node:
parent_node["children"].append(item)
return Response({
'message': '展示成功',
"data": tree,
'code': 0
}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-24 13:51:20 +08:00
class DeleteRermission(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-26 10:27:46 +08:00
"""
删除权限
"""
2025-12-24 13:51:20 +08:00
ID = request.data.get('id')
2025-12-31 11:42:28 +08:00
try:
perm = permission.objects.get(id=ID, is_deleted=False)
2025-12-31 12:28:10 +08:00
# 记录操作前的数据
old_data = {
'id': perm.id,
'permission_name': perm.permission_name,
'permission_logo': perm.permission_logo
}
2025-12-31 11:42:28 +08:00
# 软删除:更新 is_deleted 字段
perm.is_deleted = True
perm.save()
2025-12-31 12:28:10 +08:00
# 记录操作日志
log_operation(
request=request,
operation_type='DELETE',
module='Business',
action='删除权限',
target_type='Permission',
target_id=perm.id,
target_name=perm.permission_name,
old_data=old_data,
remark=f'删除权限:{perm.permission_name}'
)
2025-12-31 11:42:28 +08:00
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
except permission.DoesNotExist:
return Response({'status': 'error', 'message': '权限不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
2025-12-24 13:51:20 +08:00
2025-12-25 13:09:20 +08:00
2025-12-24 13:51:20 +08:00
class EditRermission(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-26 10:27:46 +08:00
"""
编辑权限
"""
2025-12-24 13:51:20 +08:00
id = request.data.get('id')
permission_name = request.data.get('permission_name')
permission_logo = request.data.get('permission_logo')
parent = request.data.get('parent')
per = permission.objects.get(id=id)
if permission_name:
per.permission_name = permission_name
per.save(update_fields=['permission_name'])
if permission_logo:
per.permission_logo = permission_logo
per.save(update_fields=['permission_logo'])
if parent:
per.parent = parent
per.save(update_fields=['parent'])
return Response({'message': '修改成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-24 13:51:20 +08:00
class addRole(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
RoleName = request.data.get('RoleName')
remark = request.data.get('remark')
if not all([RoleName]):
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2025-12-30 12:12:37 +08:00
# 检查角色名是否已存在
if role.objects.filter(RoleName=RoleName).exists():
2026-01-04 15:44:07 +08:00
return Response({'status': 'error', 'message': '角色名已存在,不能重复', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
2025-12-30 12:12:37 +08:00
2025-12-25 13:09:20 +08:00
role.objects.create(RoleName=RoleName, remark=remark)
2025-12-24 13:51:20 +08:00
return Response({'message': '添加成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-24 13:51:20 +08:00
class DeleteRole(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
id = request.data.get('id')
2025-12-31 11:42:28 +08:00
try:
r = role.objects.get(id=id, is_deleted=False)
2025-12-31 12:28:10 +08:00
# 记录操作前的数据
old_data = {
'id': r.id,
'role_name': r.RoleName,
'permission_id': r.permissionId
}
2025-12-31 11:42:28 +08:00
# 软删除:更新 is_deleted 字段
r.is_deleted = True
r.save()
2025-12-31 12:28:10 +08:00
# 记录操作日志
log_operation(
request=request,
operation_type='DELETE',
module='Business',
action='删除角色',
target_type='Role',
target_id=r.id,
target_name=r.RoleName,
old_data=old_data,
remark=f'删除角色:{r.RoleName}'
)
2025-12-31 11:42:28 +08:00
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
except role.DoesNotExist:
return Response({'status': 'error', 'message': '角色不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
2025-12-24 13:51:20 +08:00
class EditRole(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
id = request.data.get('id')
RoleName = request.data.get('RoleName')
remark = request.data.get('remark')
if not all([id]):
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
ro = role.objects.get(id=id)
if RoleName:
2025-12-30 12:12:37 +08:00
# 检查角色名是否已被其他角色使用(排除当前角色)
if role.objects.filter(RoleName=RoleName).exclude(id=id).exists():
2026-01-04 15:44:07 +08:00
return Response({'status': 'error', 'message': '角色名已存在,不能重复', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
2025-12-24 13:51:20 +08:00
ro.RoleName = RoleName
ro.save(update_fields=['RoleName'])
if remark:
ro.remark = remark
ro.save(update_fields=['remark'])
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-24 13:51:20 +08:00
class displayRole(APIView):
2025-12-26 11:36:48 +08:00
"""
角色列表展示
"""
2026-01-04 15:44:07 +08:00
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-24 13:51:20 +08:00
RoleName = request.data.get('RoleName')
obj = Q()
if RoleName:
obj = Q(RoleName__icontains=RoleName)
roles = role.objects.filter(obj)
data = []
for ro in roles:
data.append({
"id": str(ro.id),
"RoleName": ro.RoleName,
2025-12-24 13:51:20 +08:00
"remark": ro.remark,
})
2025-12-25 13:09:20 +08:00
return Response({'message': '展示成功', 'data': data, 'code': 0}, status=status.HTTP_200_OK)
2025-12-24 13:51:20 +08:00
class modifypermissions(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-26 14:02:48 +08:00
"""
修改角色权限
"""
2025-12-24 13:51:20 +08:00
id = request.data.get('id')
permissionId = request.data.get('permissionId')
2025-12-25 13:09:20 +08:00
if not all([id, permissionId]):
2025-12-24 13:51:20 +08:00
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
permissionId = ast.literal_eval(permissionId)
ro = role.objects.get(id=id)
2025-12-26 14:01:07 +08:00
# Convert list to compact comma-separated string format to save space
if isinstance(permissionId, list):
ro.permissionId = ','.join(map(str, permissionId))
else:
ro.permissionId = str(permissionId)
2025-12-24 13:51:20 +08:00
ro.save(update_fields=['permissionId'])
return Response({'message': '权限修改成功', 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-25 10:47:40 +08:00
class getRolePermissions(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-25 10:47:40 +08:00
"""
查询角色权限
:param request:
:param args:
:param kwargs:
:return:
"""
role_id = request.data.get('roleId')
if not role_id:
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2025-12-25 13:09:20 +08:00
2025-12-25 10:47:40 +08:00
try:
ro = role.objects.get(id=role_id)
except role.DoesNotExist:
return Response({'status': 'error', 'message': '角色不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
2025-12-25 13:09:20 +08:00
2025-12-25 10:47:40 +08:00
# 解析权限ID列表
try:
if ro.permissionId:
2025-12-26 14:01:07 +08:00
# Try to parse as comma-separated format first (new format)
if ',' in ro.permissionId and not ro.permissionId.strip().startswith('['):
permission_ids = [int(x.strip()) for x in ro.permissionId.split(',') if x.strip()]
else:
# Fall back to ast.literal_eval for backward compatibility
permission_ids = ast.literal_eval(ro.permissionId)
if not isinstance(permission_ids, list):
permission_ids = []
2025-12-25 10:47:40 +08:00
else:
permission_ids = []
except (ValueError, SyntaxError):
# 如果解析失败,返回空列表
permission_ids = []
2025-12-25 13:09:20 +08:00
2025-12-25 10:47:40 +08:00
return Response({'message': '查询成功', 'data': permission_ids, 'code': 0}, status=status.HTTP_200_OK)
2025-12-25 13:09:20 +08:00
2025-12-25 10:52:52 +08:00
class DeleteRegistration(APIView):
2025-12-25 13:09:20 +08:00
def post(self, request, *args, **kwargs):
2025-12-25 10:52:52 +08:00
"""
删除预立案登记
: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)
2025-12-25 13:09:20 +08:00
2025-12-25 10:52:52 +08:00
try:
2025-12-31 11:42:28 +08:00
pre = PreFiling.objects.get(id=id, is_deleted=False)
2026-01-04 15:44:07 +08:00
2026-01-14 15:04:44 +08:00
# 注意:由于预立案和立项登记、投标登记已经拆分,不再需要检查关联
# 预立案现在可以独立删除(如果不再需要的话)
# 如果将来需要保留预立案用于其他用途,可以保留此检查逻辑
2026-01-04 15:44:07 +08:00
2025-12-31 11:42:28 +08:00
# 软删除:更新 is_deleted 字段
pre.is_deleted = True
pre.save()
2025-12-25 10:52:52 +08:00
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
except PreFiling.DoesNotExist:
2025-12-25 13:09:20 +08:00
return Response({'status': 'error', 'message': '预立案登记不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
2025-12-29 15:25:24 +08:00
class TransferCase(APIView):
def post(self, request, *args, **kwargs):
"""
案件转移接口
将案件的承办人员从离职用户转移到新用户
:param request:
:param args:
:param kwargs:
:return:
"""
old_undertaker = request.data.get('old_undertaker') # 原承办人员(离职用户)
new_undertaker = request.data.get('new_undertaker') # 新承办人员
case_ids = request.data.get('case_ids') # 要转移的案件ID列表可选如果不传则转移所有案件
2026-01-15 12:16:00 +08:00
project_ids = request.data.get('project_ids') # 要转移的立项ID列表可选
2026-01-04 15:44:07 +08:00
2025-12-29 15:25:24 +08:00
if not all([old_undertaker, new_undertaker]):
return Response({
'status': 'error',
'message': '缺少参数:原承办人员和新承办人员不能为空',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
2026-01-04 15:44:07 +08:00
2026-01-12 17:32:15 +08:00
# 检查原承办人员是否存在(排除软删除用户)
2025-12-29 15:25:24 +08:00
try:
2026-01-12 17:32:15 +08:00
old_user = User.objects.get(username=old_undertaker, is_deleted=False)
2025-12-29 15:25:24 +08:00
except User.DoesNotExist:
return Response({
'status': 'error',
2026-01-12 17:32:15 +08:00
'message': '原承办人员不存在或已被删除',
2025-12-29 15:25:24 +08:00
'code': 1
}, status=status.HTTP_404_NOT_FOUND)
2026-01-04 15:44:07 +08:00
2026-01-12 17:32:15 +08:00
# 检查新承办人员是否存在(排除软删除用户)
2025-12-29 15:25:24 +08:00
try:
2026-01-12 17:32:15 +08:00
new_user = User.objects.get(username=new_undertaker, is_deleted=False)
2025-12-29 15:25:24 +08:00
except User.DoesNotExist:
return Response({
'status': 'error',
2026-01-12 17:32:15 +08:00
'message': '新承办人员不存在或已被删除',
2025-12-29 15:25:24 +08:00
'code': 1
}, status=status.HTTP_404_NOT_FOUND)
2026-01-04 15:44:07 +08:00
2026-01-15 12:16:00 +08:00
# 查询要转移的预立案
2025-12-29 15:25:24 +08:00
if case_ids and isinstance(case_ids, list):
2026-01-15 12:16:00 +08:00
prefiling_cases = PreFiling.objects.filter(Undertaker=old_undertaker, id__in=case_ids)
2025-12-29 15:25:24 +08:00
else:
2026-01-15 12:16:00 +08:00
prefiling_cases = PreFiling.objects.filter(Undertaker=old_undertaker)
prefiling_count = prefiling_cases.count()
2026-01-04 15:44:07 +08:00
2026-01-15 12:16:00 +08:00
def _replace_responsible(responsiblefor_value, old_name, new_name):
try:
data = json.loads(responsiblefor_value) if responsiblefor_value else {}
except (json.JSONDecodeError, TypeError):
data = responsiblefor_value if responsiblefor_value else {}
if not isinstance(data, dict):
return None, False
changed = False
for key, value in list(data.items()):
if value == old_name:
data[key] = new_name
changed = True
return data, changed
# 查询要转移的正式案件
formal_cases_qs = Case.objects.filter(is_deleted=False, responsiblefor__icontains=old_undertaker)
if case_ids and isinstance(case_ids, list):
formal_cases_qs = formal_cases_qs.filter(id__in=case_ids)
# 查询要转移的立项登记
formal_projects_qs = ProjectRegistration.objects.filter(is_deleted=False, responsiblefor__icontains=old_undertaker)
if project_ids and isinstance(project_ids, list):
formal_projects_qs = formal_projects_qs.filter(id__in=project_ids)
2026-01-04 15:44:07 +08:00
2026-01-15 12:16:00 +08:00
formal_cases = list(formal_cases_qs)
formal_projects = list(formal_projects_qs)
total_count = prefiling_count + len(formal_cases) + len(formal_projects)
if total_count == 0:
2025-12-29 15:25:24 +08:00
return Response({
'status': 'error',
'message': '没有找到需要转移的案件',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
2026-01-04 15:44:07 +08:00
2025-12-29 15:25:24 +08:00
# 执行转移
2026-01-15 12:16:00 +08:00
transferred_prefilings = []
for case in prefiling_cases:
2025-12-29 15:25:24 +08:00
case.Undertaker = new_undertaker
case.save(update_fields=['Undertaker'])
2026-01-15 12:16:00 +08:00
transferred_prefilings.append({
2025-12-29 15:25:24 +08:00
'id': case.id,
'times': case.times,
'client_username': case.client_username,
'party_username': case.party_username,
'description': case.description
})
2026-01-04 15:44:07 +08:00
2026-01-15 12:16:00 +08:00
transferred_formal_cases = []
for case in formal_cases:
updated_data, changed = _replace_responsible(case.responsiblefor, old_undertaker, new_undertaker)
if changed:
case.responsiblefor = json.dumps(updated_data, ensure_ascii=False)
case.save(update_fields=['responsiblefor'])
transferred_formal_cases.append({
'id': case.id,
'project_id': case.project_id,
'contract_no': case.contract_no,
'project_type': case.project_type,
'times': case.times
})
transferred_projects = []
for project in formal_projects:
updated_data, changed = _replace_responsible(project.responsiblefor, old_undertaker, new_undertaker)
if changed:
project.responsiblefor = json.dumps(updated_data, ensure_ascii=False)
project.save(update_fields=['responsiblefor'])
transferred_projects.append({
'id': project.id,
'contract_no': project.ContractNo,
'type': project.type,
'times': project.times
})
2025-12-29 15:25:24 +08:00
return Response({
2026-01-15 12:16:00 +08:00
'message': f'案件转移成功,共转移{total_count}个案件',
2025-12-29 15:25:24 +08:00
'code': 0,
'data': {
'old_undertaker': old_undertaker,
'new_undertaker': new_undertaker,
2026-01-15 12:16:00 +08:00
'transferred_count': total_count,
'prefiling_count': prefiling_count,
'formal_case_count': len(transferred_formal_cases),
'formal_project_count': len(transferred_projects),
'prefiling_cases': transferred_prefilings,
'formal_cases': transferred_formal_cases,
'formal_projects': transferred_projects
2025-12-29 15:25:24 +08:00
}
}, status=status.HTTP_200_OK)
2026-01-15 14:55:50 +08:00
class CaseChangeRequestCreate(APIView):
2026-01-31 12:18:44 +08:00
"""创建案件变更申请(需传入审批人)"""
2026-01-15 14:55:50 +08:00
def post(self, request, *args, **kwargs):
2026-01-16 17:06:36 +08:00
case_id = request.data.get('case_id') # 案件ID可选
contract_no = request.data.get('contract_no') # 合同编号(必填)
change_item = request.data.get('change_item') # 变更事项
change_reason = request.data.get('change_reason') # 变更原因
change_agreement = request.FILES.getlist('change_agreement') or request.FILES.getlist('ChangeAgreement') # 变更协议文件
2026-01-31 12:18:44 +08:00
approvers = request.data.get('approvers') # 审批人必填用户ID数组如[1,2,3],或用户名数组["张三","李四"],或逗号分隔字符串
personincharge = request.data.get('personincharge') # 兼容旧接口未传approvers时使用
approvers = normalize_approvers_param(approvers, personincharge)
2026-01-15 14:55:50 +08:00
# 验证必填参数
2026-01-16 17:06:36 +08:00
if not contract_no:
2026-01-15 14:55:50 +08:00
return Response({
'status': 'error',
2026-01-16 17:06:36 +08:00
'message': '缺少参数contract_no合同编号',
2026-01-15 14:55:50 +08:00
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
if not all([change_item, change_reason, change_agreement]):
return Response({
'status': 'error',
'message': '变更申请需要填写变更事项、变更原因并上传变更协议',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
2026-01-31 12:18:44 +08:00
if not approvers:
return Response({
'status': 'error',
'message': '缺少参数approvers审批人请传入审批人列表如用户ID数组[1,2,3]或用户名数组["张三","李四"]',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
2026-01-16 17:06:36 +08:00
# 如果提供了case_id验证案件是否存在可选
case = None
if case_id:
try:
case = Case.objects.get(id=case_id, is_deleted=False)
except Case.DoesNotExist:
return Response({
'status': 'error',
'message': '案件不存在或已删除',
'code': 1
}, status=status.HTTP_404_NOT_FOUND)
2026-01-15 14:55:50 +08:00
# 获取申请人
applicant_name = "未知用户"
token = request.META.get('token')
if token:
try:
applicant = User.objects.get(token=token, is_deleted=False)
applicant_name = applicant.username
except User.DoesNotExist:
pass
# 上传变更协议文件
agreement_urls = flies(change_agreement)
if not agreement_urls:
return Response({
'status': 'error',
'message': '变更协议上传失败',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
2026-01-31 12:18:44 +08:00
# 创建变更申请(审批人由入参 approvers 传入create_approval_with_team_logic 会解析并回写 approvers_order
2026-01-15 14:55:50 +08:00
today = datetime.now().strftime("%Y-%m-%d")
change_request = CaseChangeRequest.objects.create(
2026-01-16 17:06:36 +08:00
case=case, # 案件关联(可选)
contract_no=contract_no, # 合同编号(必填)
2026-01-15 14:55:50 +08:00
change_item=change_item,
change_reason=change_reason,
change_agreement=json.dumps(agreement_urls, ensure_ascii=False),
times=today,
applicant=applicant_name,
state='审核中',
2026-01-31 12:18:44 +08:00
approvers_order=None, # 由 create_approval_with_team_logic 根据 approvers 入参回写
need_approval=True
2026-01-15 14:55:50 +08:00
)
2026-01-31 11:52:22 +08:00
2026-01-16 17:06:36 +08:00
# 如果提供了case_id更新案件的change_request字段
if case:
case.change_request = change_request
case.save(update_fields=['change_request'])
2026-01-31 11:52:22 +08:00
# 构建审批内容(不含审批流程,由 create_approval_with_team_logic 追加)
2026-01-16 17:06:36 +08:00
if case:
content = build_change_approval_content(
applicant_name, case, change_item, change_reason, agreement_urls
)
else:
content = f"{applicant_name}提交案件变更申请,合同编号:{contract_no},变更事项:{change_item},变更原因:{change_reason},变更协议:{', '.join(agreement_urls)}"
2026-01-31 11:52:22 +08:00
from User.utils import create_approval_with_team_logic
2026-01-31 12:18:44 +08:00
approval, approvers_order_json, needs_approval = create_approval_with_team_logic(
team_name=None,
approvers=approvers,
title="案件变更申请",
content=content,
approval_type="案件变更",
user_id=str(change_request.id),
business_record=change_request,
today=today,
applicant=applicant_name
)
if approval is None and needs_approval:
return Response({
'status': 'error',
'message': '审批人无效或不存在请检查approvers参数需为系统中的用户ID或用户名',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
2026-01-31 11:52:22 +08:00
2026-01-15 14:55:50 +08:00
return Response({
'message': '变更申请提交成功',
'code': 0,
'data': {
'change_request_id': change_request.id
}
}, status=status.HTTP_200_OK)
class CaseChangeRequestList(APIView):
2026-01-31 12:18:44 +08:00
"""
案件变更申请列表查询
2026-01-31 13:23:50 +08:00
默认仅展示审批通过的数据审批通过后才进变更申请表格
我的申请/全部 show_all=true state=审核中,待查看,已通过,未通过 可查全部或按状态筛选
2026-01-31 12:18:44 +08:00
"""
2026-01-15 14:55:50 +08:00
def post(self, request, *args, **kwargs):
page = request.data.get('page')
per_page = request.data.get('per_page')
case_id = request.data.get('case_id') # 可选按案件ID筛选
2026-01-31 13:23:50 +08:00
state = request.data.get('state') # 可选:按状态筛选;不传且不传 show_all 时默认只返回已通过
approved_only = request.data.get('approved_only') # 可选:为 true 时仅返回已通过
show_all = request.data.get('show_all') # 可选:为 true 时返回全部状态(我的申请用),不传则默认只返回已通过
2026-01-15 14:55:50 +08:00
2026-01-19 11:47:27 +08:00
# 参数验证和默认值处理
try:
page = int(page) if page and str(page).lower() != 'undefined' else 1
per_page = int(per_page) if per_page and str(per_page).lower() != 'undefined' else 10
except (ValueError, TypeError):
2026-01-15 14:55:50 +08:00
return Response({
'status': 'error',
2026-01-19 11:47:27 +08:00
'message': '参数page和per_page必须是有效的数字',
2026-01-15 14:55:50 +08:00
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
2026-01-19 11:47:27 +08:00
if page < 1:
page = 1
if per_page < 1:
per_page = 10
2026-01-15 14:55:50 +08:00
Q_obj = Q(is_deleted=False)
if case_id:
Q_obj &= Q(case_id=case_id)
2026-01-31 13:23:50 +08:00
# 变更申请表格:默认只展示审批通过的数据;传 show_all=true 可查全部(我的申请)
if show_all in (True, 'true', '1', 1):
pass # 不按状态筛选,返回全部
elif approved_only in (True, 'true', '1', 1) or state == '已通过':
2026-01-31 12:18:44 +08:00
Q_obj &= Q(state='已通过')
elif state:
# 支持单状态或逗号分隔多状态
if isinstance(state, str) and ',' in state:
states = [s.strip() for s in state.split(',') if s.strip()]
if states:
Q_obj &= Q(state__in=states)
else:
Q_obj &= Q(state=state)
2026-01-31 13:23:50 +08:00
else:
# 默认:仅展示已通过,数据审批通过后才进变更申请表格
Q_obj &= Q(state='已通过')
2026-01-15 14:55:50 +08:00
queryset = CaseChangeRequest.objects.filter(Q_obj).order_by('-id')
total = queryset.count()
paginator = Paginator(queryset, per_page)
try:
page_obj = paginator.page(page)
except PageNotAnInteger:
page_obj = paginator.page(1)
except EmptyPage:
page_obj = paginator.page(paginator.num_pages)
data = []
for change_request in page_obj.object_list:
try:
agreement_list = json.loads(change_request.change_agreement) if change_request.change_agreement else []
except (json.JSONDecodeError, TypeError):
agreement_list = []
try:
approvers_list = json.loads(change_request.approvers_order) if change_request.approvers_order else []
except (json.JSONDecodeError, TypeError):
approvers_list = []
data.append({
'id': change_request.id,
2026-01-16 17:06:36 +08:00
'contract_no': change_request.contract_no, # 合同编号
'case_id': change_request.case.id if change_request.case else None,
'case_contract_no': change_request.case.contract_no if change_request.case else None,
'case_project_type': change_request.case.project_type if change_request.case else None,
'change_item': change_request.change_item, # 变更事项
'change_reason': change_request.change_reason, # 变更原因
'change_agreement': change_request.change_agreement, # 变更协议JSON字符串
2026-01-15 14:55:50 +08:00
'change_agreement_urls': agreement_list, # 解析后的URL列表
'times': change_request.times,
'applicant': change_request.applicant,
'state': change_request.state,
'approvers_order': approvers_list
})
return Response({
'message': '查询成功',
'total': total,
'data': data,
'code': 0
}, status=status.HTTP_200_OK)
class CaseChangeRequestDetail(APIView):
"""案件变更申请详情查询"""
def post(self, request, *args, **kwargs):
change_request_id = request.data.get('change_request_id') or request.data.get('id')
if not change_request_id:
return Response({
'status': 'error',
'message': '缺少参数change_request_id或id',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
try:
change_request = CaseChangeRequest.objects.get(id=change_request_id, is_deleted=False)
except CaseChangeRequest.DoesNotExist:
return Response({
'status': 'error',
'message': '变更申请不存在或已删除',
'code': 1
}, status=status.HTTP_404_NOT_FOUND)
try:
agreement_list = json.loads(change_request.change_agreement) if change_request.change_agreement else []
except (json.JSONDecodeError, TypeError):
agreement_list = []
try:
approvers_list = json.loads(change_request.approvers_order) if change_request.approvers_order else []
except (json.JSONDecodeError, TypeError):
approvers_list = []
2026-01-16 17:06:36 +08:00
# 获取案件信息(如果有关联)
case_data = None
if change_request.case:
case = change_request.case
case_data = {
'id': case.id,
'contract_no': case.contract_no,
'project_type': case.project_type,
'client_name': case.client_name,
'party_name': case.party_name,
'project_description': case.project_description
}
2026-01-15 14:55:50 +08:00
data = {
'id': change_request.id,
2026-01-16 17:06:36 +08:00
'contract_no': change_request.contract_no, # 合同编号
'case': case_data, # 案件信息(如果有关联)
'change_item': change_request.change_item, # 变更事项
'change_reason': change_request.change_reason, # 变更原因
'change_agreement': change_request.change_agreement, # 变更协议JSON字符串
2026-01-15 14:55:50 +08:00
'change_agreement_urls': agreement_list, # 解析后的URL列表
'times': change_request.times,
'applicant': change_request.applicant,
'state': change_request.state,
'approvers_order': approvers_list
}
return Response({
'message': '查询成功',
'data': data,
'code': 0
}, status=status.HTTP_200_OK)
2026-01-19 10:19:00 +08:00
2026-01-20 19:30:36 +08:00
def parse_person_info(person_str):
"""
解析人员信息字符串支持多种格式
- "张三3263452342342342"
- "张三身份证号3263452342342342"
- "张三,3263452342342342"
- "张三"
Returns:
dict: {
'name': '张三',
'id_number': '3263452342342342', # 可能为空
'original': '张三3263452342342342' # 原始字符串
}
"""
if not person_str:
return {'name': '', 'id_number': '', 'original': ''}
person_str = str(person_str).strip()
result = {
'name': '',
'id_number': '',
'original': person_str
}
# 匹配格式张三3263452342342342或张三(3263452342342342)
match = re.match(r'^([^(]+?)[(]([^)]+)[)]', person_str)
if match:
result['name'] = match.group(1).strip()
result['id_number'] = match.group(2).strip()
return result
# 匹配格式张三身份证号3263452342342342 或 张三,身份证号:3263452342342342
match = re.match(r'^([^,]+?)[,]\s*[身身]份证[号号]?[:]?\s*([^\s,]+)', person_str)
if match:
result['name'] = match.group(1).strip()
result['id_number'] = match.group(2).strip()
return result
# 匹配格式:张三,3263452342342342逗号分隔
match = re.match(r'^([^,]+?)[,]\s*(\d{15,18})', person_str)
if match:
result['name'] = match.group(1).strip()
result['id_number'] = match.group(2).strip()
return result
# 如果没有匹配到特定格式,尝试提取姓名(去除可能的身份证号)
# 先尝试提取姓名部分(去除末尾的数字)
name_match = re.match(r'^([^,\d(]+)', person_str)
if name_match:
result['name'] = name_match.group(1).strip()
else:
result['name'] = person_str
# 尝试提取身份证号15-18位数字
id_match = re.search(r'(\d{15,18})', person_str)
if id_match:
result['id_number'] = id_match.group(1)
return result
2026-01-19 10:19:00 +08:00
def conflict_search(client_info=None, party_info=None, undertaker=None, bidding_unit=None, exclude_prefiling_id=None, exclude_project_id=None, exclude_bid_id=None):
"""
利益冲突检索函数 - 比对预立案登记投标登记立项登记三张表
Args:
client_info: 委托人身份信息可选
party_info: 相对方身份信息可选
undertaker: 承办人员可选
bidding_unit: 招标单位可选
exclude_prefiling_id: 要排除的预立案ID可选
exclude_project_id: 要排除的立项ID可选
exclude_bid_id: 要排除的投标ID可选
Returns:
dict: 包含三个表的冲突记录信息
{
'prefiling_conflicts': [...], # 预立案冲突记录列表
'project_conflicts': [...], # 立项冲突记录列表
'bid_conflicts': [...] # 投标冲突记录列表
}
"""
result = {
'prefiling_conflicts': [],
'project_conflicts': [],
'bid_conflicts': []
}
# 如果没有提供任何查询条件,返回空结果
if not any([client_info, party_info, undertaker, bidding_unit]):
return result
2026-01-20 19:30:36 +08:00
# 解析委托人信息和相对方信息
client_parsed = parse_person_info(client_info) if client_info else {'name': '', 'id_number': '', 'original': ''}
party_parsed = parse_person_info(party_info) if party_info else {'name': '', 'id_number': '', 'original': ''}
2026-01-19 10:19:00 +08:00
2026-01-20 19:30:36 +08:00
# 提取姓名/名称(用于匹配)
client_name = client_parsed['name']
client_id_number = client_parsed['id_number']
client_original = client_parsed['original']
2026-01-19 10:19:00 +08:00
2026-01-20 19:30:36 +08:00
party_name = party_parsed['name']
party_id_number = party_parsed['id_number']
party_original = party_parsed['original']
2026-01-19 10:19:00 +08:00
# 1. 检索预立案表
prefiling_q = Q(is_deleted=False)
if exclude_prefiling_id:
prefiling_q &= ~Q(id=exclude_prefiling_id)
matched_prefilings = []
prefiling_all = PreFiling.objects.filter(prefiling_q)
for pf in prefiling_all:
if len(matched_prefilings) >= 50: # 限制返回数量
break
is_match = False
match_reason = []
# 检查委托人/相对方匹配
if client_info or client_name:
pf_client_names = []
try:
client_data = json.loads(pf.client_username) if isinstance(pf.client_username, str) else pf.client_username
if isinstance(client_data, list):
for item in client_data:
if isinstance(item, dict) and 'name' in item:
pf_client_names.append(item['name'].strip())
elif isinstance(client_data, dict) and 'name' in client_data:
pf_client_names.append(client_data['name'].strip())
except (json.JSONDecodeError, TypeError, AttributeError):
pf_client_names = [pf.client_username.strip()] if pf.client_username else []
pf_party_names = []
try:
party_data = json.loads(pf.party_username) if isinstance(pf.party_username, str) else pf.party_username
if isinstance(party_data, list):
for item in party_data:
if isinstance(item, dict) and 'name' in item:
pf_party_names.append(item['name'].strip())
elif isinstance(party_data, dict) and 'name' in party_data:
pf_party_names.append(party_data['name'].strip())
except (json.JSONDecodeError, TypeError, AttributeError):
pf_party_names = [pf.party_username.strip()] if pf.party_username else []
2026-01-20 19:30:36 +08:00
# 检查委托人匹配(支持姓名和身份证号匹配)
2026-01-19 10:19:00 +08:00
if client_info or client_name:
for pf_client in pf_client_names:
2026-01-20 19:30:36 +08:00
# 匹配姓名
if client_name and pf_client in client_name:
2026-01-19 10:19:00 +08:00
is_match = True
2026-01-20 19:30:36 +08:00
match_reason.append(f"委托人姓名匹配:{pf_client}")
break
# 匹配完整信息
if client_original and pf_client in client_original:
is_match = True
match_reason.append(f"委托人信息匹配:{pf_client}")
2026-01-19 10:19:00 +08:00
break
# 检查相对方是否匹配委托人
for pf_party in pf_party_names:
2026-01-20 19:30:36 +08:00
if client_name and pf_party in client_name:
is_match = True
match_reason.append(f"相对方与委托人姓名匹配:{pf_party}")
break
if client_original and pf_party in client_original:
2026-01-19 10:19:00 +08:00
is_match = True
2026-01-20 19:30:36 +08:00
match_reason.append(f"相对方与委托人信息匹配:{pf_party}")
2026-01-19 10:19:00 +08:00
break
2026-01-20 19:30:36 +08:00
# 检查相对方匹配(支持姓名和身份证号匹配)
2026-01-19 10:19:00 +08:00
if party_info or party_name:
for pf_party in pf_party_names:
2026-01-20 19:30:36 +08:00
# 匹配姓名
if party_name and pf_party in party_name:
2026-01-19 10:19:00 +08:00
is_match = True
2026-01-20 19:30:36 +08:00
match_reason.append(f"相对方姓名匹配:{pf_party}")
break
# 匹配完整信息
if party_original and pf_party in party_original:
is_match = True
match_reason.append(f"相对方信息匹配:{pf_party}")
2026-01-19 10:19:00 +08:00
break
# 检查委托人是否匹配相对方
for pf_client in pf_client_names:
2026-01-20 19:30:36 +08:00
if party_name and pf_client in party_name:
2026-01-19 10:19:00 +08:00
is_match = True
2026-01-20 19:30:36 +08:00
match_reason.append(f"委托人与相对方姓名匹配:{pf_client}")
2026-01-19 10:19:00 +08:00
break
2026-01-20 19:30:36 +08:00
if party_original and pf_client in party_original:
is_match = True
match_reason.append(f"委托人与相对方信息匹配:{pf_client}")
break
# 检查身份证号匹配(如果提供了身份证号)
if client_id_number:
# 检查预立案中的身份证号
try:
client_data = json.loads(pf.client_username) if isinstance(pf.client_username, str) else pf.client_username
if isinstance(client_data, list):
for item in client_data:
if isinstance(item, dict):
pf_id = item.get('idNumber', '') or item.get('id_number', '')
if pf_id and client_id_number in str(pf_id):
is_match = True
match_reason.append(f"委托人身份证号匹配")
break
elif isinstance(client_data, dict):
pf_id = client_data.get('idNumber', '') or client_data.get('id_number', '')
if pf_id and client_id_number in str(pf_id):
is_match = True
match_reason.append(f"委托人身份证号匹配")
except (json.JSONDecodeError, TypeError, AttributeError):
pass
if party_id_number:
# 检查预立案中的身份证号
try:
party_data = json.loads(pf.party_username) if isinstance(pf.party_username, str) else pf.party_username
if isinstance(party_data, list):
for item in party_data:
if isinstance(item, dict):
pf_id = item.get('idNumber', '') or item.get('id_number', '')
if pf_id and party_id_number in str(pf_id):
is_match = True
match_reason.append(f"相对方身份证号匹配")
break
elif isinstance(party_data, dict):
pf_id = party_data.get('idNumber', '') or party_data.get('id_number', '')
if pf_id and party_id_number in str(pf_id):
is_match = True
match_reason.append(f"相对方身份证号匹配")
except (json.JSONDecodeError, TypeError, AttributeError):
pass
2026-01-19 10:19:00 +08:00
# 检查承办人员匹配
if undertaker and pf.Undertaker:
if undertaker in pf.Undertaker or pf.Undertaker in undertaker:
is_match = True
match_reason.append(f"承办人员匹配:{pf.Undertaker}")
if is_match:
matched_prefilings.append({
'id': pf.id,
'times': pf.times,
'client_username': pf.client_username,
'party_username': pf.party_username,
'description': pf.description,
'Undertaker': pf.Undertaker,
2026-01-19 10:19:00 +08:00
'match_reason': ''.join(match_reason) if match_reason else '匹配'
})
result['prefiling_conflicts'] = matched_prefilings
# 2. 检索立项表
project_q = Q(is_deleted=False)
if exclude_project_id:
project_q &= ~Q(id=exclude_project_id)
project_records = ProjectRegistration.objects.filter(project_q)
project_list = []
for pro in project_records[:50]: # 限制查询数量
is_match = False
match_reason = []
2026-01-20 19:30:36 +08:00
# 检查委托人/相对方匹配(支持姓名和身份证号匹配)
2026-01-19 10:19:00 +08:00
if client_info or client_name:
2026-01-20 19:30:36 +08:00
if pro.client_info:
# 匹配姓名
if client_name and client_name in pro.client_info:
is_match = True
match_reason.append("委托人姓名匹配")
# 匹配完整信息
elif client_original and client_original in pro.client_info:
is_match = True
match_reason.append("委托人信息匹配")
# 匹配身份证号
elif client_id_number and client_id_number in pro.client_info:
is_match = True
match_reason.append("委托人身份证号匹配")
if pro.party_info:
# 匹配姓名
if client_name and client_name in pro.party_info:
is_match = True
match_reason.append("相对方与委托人姓名匹配")
# 匹配完整信息
elif client_original and client_original in pro.party_info:
is_match = True
match_reason.append("相对方与委托人信息匹配")
# 匹配身份证号
elif client_id_number and client_id_number in pro.party_info:
is_match = True
match_reason.append("相对方与委托人身份证号匹配")
2026-01-19 10:19:00 +08:00
if party_info or party_name:
2026-01-20 19:30:36 +08:00
if pro.party_info:
# 匹配姓名
if party_name and party_name in pro.party_info:
is_match = True
match_reason.append("相对方姓名匹配")
# 匹配完整信息
elif party_original and party_original in pro.party_info:
is_match = True
match_reason.append("相对方信息匹配")
# 匹配身份证号
elif party_id_number and party_id_number in pro.party_info:
is_match = True
match_reason.append("相对方身份证号匹配")
if pro.client_info:
# 匹配姓名
if party_name and party_name in pro.client_info:
is_match = True
match_reason.append("委托人与相对方姓名匹配")
# 匹配完整信息
elif party_original and party_original in pro.client_info:
is_match = True
match_reason.append("委托人与相对方信息匹配")
# 匹配身份证号
elif party_id_number and party_id_number in pro.client_info:
is_match = True
match_reason.append("委托人与相对方身份证号匹配")
2026-01-19 10:19:00 +08:00
# 检查负责人匹配
if undertaker and pro.responsiblefor:
try:
responsiblefor_data = json.loads(pro.responsiblefor) if isinstance(pro.responsiblefor, str) else pro.responsiblefor
if isinstance(responsiblefor_data, dict):
responsible_person = responsiblefor_data.get('responsible_person', '')
main_lawyer = responsiblefor_data.get('main_lawyer', '')
assistant_lawyer = responsiblefor_data.get('assistant_lawyer', '')
case_manager_lawyer = responsiblefor_data.get('case_manager_lawyer', '')
if undertaker in str(responsible_person) or undertaker in str(main_lawyer) or \
undertaker in str(assistant_lawyer) or undertaker in str(case_manager_lawyer):
is_match = True
match_reason.append("负责人匹配")
except (json.JSONDecodeError, TypeError):
if undertaker in pro.responsiblefor:
is_match = True
match_reason.append("负责人匹配")
if is_match:
project_list.append({
'id': pro.id,
'ContractNo': pro.ContractNo,
'times': pro.times,
'type': pro.type,
'client_info': pro.client_info,
'party_info': pro.party_info,
'description': pro.description,
'responsiblefor': pro.responsiblefor,
'match_reason': ''.join(match_reason) if match_reason else '匹配'
})
result['project_conflicts'] = project_list
# 3. 检索投标表
bid_q = Q(is_deleted=False)
if exclude_bid_id:
bid_q &= ~Q(id=exclude_bid_id)
bid_records = Bid.objects.filter(bid_q)
bid_list = []
for bid in bid_records[:50]: # 限制查询数量
is_match = False
match_reason = []
# 检查招标单位匹配
if bidding_unit and bid.BiddingUnit:
if bidding_unit in bid.BiddingUnit or bid.BiddingUnit in bidding_unit:
is_match = True
match_reason.append("招标单位匹配")
2026-01-20 19:30:36 +08:00
# 检查委托人/相对方是否在招标单位中(支持姓名和身份证号匹配)
2026-01-19 10:19:00 +08:00
if (client_info or client_name) and bid.BiddingUnit:
2026-01-20 19:30:36 +08:00
if client_name and client_name in bid.BiddingUnit:
is_match = True
match_reason.append("委托人姓名在招标单位中")
elif client_original and client_original in bid.BiddingUnit:
2026-01-19 10:19:00 +08:00
is_match = True
match_reason.append("委托人信息在招标单位中")
2026-01-20 19:30:36 +08:00
elif client_id_number and client_id_number in bid.BiddingUnit:
is_match = True
match_reason.append("委托人身份证号在招标单位中")
2026-01-19 10:19:00 +08:00
if (party_info or party_name) and bid.BiddingUnit:
2026-01-20 19:30:36 +08:00
if party_name and party_name in bid.BiddingUnit:
is_match = True
match_reason.append("相对方姓名在招标单位中")
elif party_original and party_original in bid.BiddingUnit:
2026-01-19 10:19:00 +08:00
is_match = True
match_reason.append("相对方信息在招标单位中")
2026-01-20 19:30:36 +08:00
elif party_id_number and party_id_number in bid.BiddingUnit:
is_match = True
match_reason.append("相对方身份证号在招标单位中")
2026-01-19 10:19:00 +08:00
if is_match:
bid_list.append({
'id': bid.id,
'ProjectName': bid.ProjectName,
'times': bid.times,
'BiddingUnit': bid.BiddingUnit,
'state': bid.state,
'match_reason': ''.join(match_reason) if match_reason else '匹配'
})
result['bid_conflicts'] = bid_list
return result
class ConflictSearch(APIView):
"""
利益冲突检索查询接口
比对预立案登记投标登记立项登记三张表返回冲突数据
"""
def post(self, request, *args, **kwargs):
"""
利益冲突检索
请求参数至少提供一个
2026-01-20 19:30:36 +08:00
- client_info: 委托人身份信息可选支持格式
* "张三3263452342342342"
* "张三身份证号3263452342342342"
* "张三,3263452342342342"
* "张三"
- party_info: 相对方身份信息可选支持格式同上
2026-01-19 10:19:00 +08:00
- undertaker: 承办人员可选
- bidding_unit: 招标单位可选
- exclude_prefiling_id: 要排除的预立案ID可选
- exclude_project_id: 要排除的立项ID可选
- exclude_bid_id: 要排除的投标ID可选
"""
client_info = request.data.get('client_info', '').strip() if request.data.get('client_info') else None
party_info = request.data.get('party_info', '').strip() if request.data.get('party_info') else None
undertaker = request.data.get('undertaker', '').strip() if request.data.get('undertaker') else None
bidding_unit = request.data.get('bidding_unit', '').strip() if request.data.get('bidding_unit') else None
exclude_prefiling_id = request.data.get('exclude_prefiling_id')
exclude_project_id = request.data.get('exclude_project_id')
exclude_bid_id = request.data.get('exclude_bid_id')
# 至少需要提供一个查询条件
if not any([client_info, party_info, undertaker, bidding_unit]):
return Response({
'status': 'error',
'message': '至少需要提供一个查询条件client_info、party_info、undertaker、bidding_unit之一',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 执行冲突检索
conflict_result = conflict_search(
client_info=client_info,
party_info=party_info,
undertaker=undertaker,
bidding_unit=bidding_unit,
exclude_prefiling_id=exclude_prefiling_id,
exclude_project_id=exclude_project_id,
exclude_bid_id=exclude_bid_id
)
2026-02-05 16:44:17 +08:00
# 归一化冲突记录为前端期望格式 [{ index, name, idNumber }, ...](预立案用 client_username/party_username立项用 client_info/party_info投标用 BiddingUnit
def _normalize_person_list(val):
if not val:
return []
try:
lst = json.loads(val) if isinstance(val, str) else val
if not isinstance(lst, list):
return None
out = []
for i, item in enumerate(lst):
if not isinstance(item, dict):
continue
name = item.get('name') or item.get('name_original') or ''
id_num = item.get('idNumber') or item.get('id_number') or ''
if not isinstance(name, str):
name = str(name) if name else ''
if not isinstance(id_num, str):
id_num = str(id_num) if id_num else ''
out.append({
'index': item.get('index') if isinstance(item.get('index'), (int, float)) else (i + 1),
'name': name,
'idNumber': id_num
})
return out
except (json.JSONDecodeError, TypeError, ValueError):
return None
def _normalize_conflict_records(records):
processed = []
for record in records:
new_record = dict(record)
for key in ('client_info', 'party_info', 'BiddingUnit', 'client_username', 'party_username'):
if key not in new_record or not new_record[key]:
continue
normalized = _normalize_person_list(new_record[key])
if normalized is not None:
new_record[key] = normalized
processed.append(new_record)
return processed
prefiling = _normalize_conflict_records(conflict_result['prefiling_conflicts'])
project = _normalize_conflict_records(conflict_result['project_conflicts'])
bid = _normalize_conflict_records(conflict_result['bid_conflicts'])
2026-01-19 10:19:00 +08:00
# 统计冲突数量
2026-02-05 16:44:17 +08:00
total_conflicts = len(prefiling) + len(project) + len(bid)
2026-01-19 10:19:00 +08:00
return Response({
'message': '检索成功',
'code': 0,
'data': {
'total_conflicts': total_conflicts,
2026-02-05 16:44:17 +08:00
'prefiling_conflicts_count': len(prefiling),
'project_conflicts_count': len(project),
'bid_conflicts_count': len(bid),
'prefiling_conflicts': prefiling,
'project_conflicts': project,
'bid_conflicts': bid
2026-01-19 10:19:00 +08:00
}
2026-01-19 10:48:36 +08:00
}, status=status.HTTP_200_OK)
# ==================== 案件标签管理接口 ====================
class CreateCaseTag(APIView):
"""创建案件标签"""
def post(self, request, *args, **kwargs):
name = request.data.get('name')
color = request.data.get('color', '#1890ff')
description = request.data.get('description', '')
if not name:
return Response({'status': 'error', 'message': '缺少参数:标签名称', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
# 检查标签名是否已存在
if CaseTag.objects.filter(name=name, is_deleted=False).exists():
return Response({'status': 'error', 'message': '标签名称已存在', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
import datetime
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
tag = CaseTag.objects.create(
name=name,
color=color,
description=description,
created_at=now,
updated_at=now
)
# 记录操作日志
log_operation(
request=request,
operation_type='CREATE',
module='Business',
action='创建案件标签',
target_type='CaseTag',
target_id=tag.id,
target_name=tag.name,
new_data={'name': name, 'color': color, 'description': description},
remark=f'创建案件标签:{name}'
)
return Response({
'message': '创建成功',
'code': 0,
'data': {
'id': tag.id,
'name': tag.name,
'color': tag.color,
'description': tag.description
}
}, status=status.HTTP_200_OK)
class CaseTagList(APIView):
"""案件标签列表"""
def post(self, request, *args, **kwargs):
page = request.data.get('page', 1)
per_page = request.data.get('per_page', 10)
name = request.data.get('name', '') # 搜索标签名称
Q_obj = Q(is_deleted=False)
if name:
Q_obj &= Q(name__icontains=name)
tags = CaseTag.objects.filter(Q_obj).order_by('-id')
total = len(tags)
paginator = Paginator(tags, per_page)
try:
tags_page = paginator.page(page)
except PageNotAnInteger:
tags_page = paginator.page(1)
except EmptyPage:
tags_page = paginator.page(paginator.num_pages)
data = []
for tag in tags_page.object_list:
# 统计使用该标签的案件数量
case_count = tag.cases.filter(is_deleted=False).count()
data.append({
'id': tag.id,
'name': tag.name,
'color': tag.color,
'description': tag.description,
'case_count': case_count, # 使用该标签的案件数量
'created_at': tag.created_at,
'updated_at': tag.updated_at
})
return Response({
'message': '查询成功',
'total': total,
'data': data,
'code': 0
}, status=status.HTTP_200_OK)
class CaseTagDetail(APIView):
"""案件标签详情"""
def post(self, request, *args, **kwargs):
id = request.data.get('id')
if not id:
return Response({'status': 'error', 'message': '缺少参数id', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
try:
tag = CaseTag.objects.get(id=id, is_deleted=False)
except CaseTag.DoesNotExist:
return Response({'status': 'error', 'message': '标签不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
# 获取使用该标签的案件列表
cases = tag.cases.filter(is_deleted=False)
case_list = []
for case in cases:
case_list.append({
'id': case.id,
'contract_no': case.contract_no or '',
'project_type': case.project_type or '',
'client_name': case.client_name or '',
'times': case.times
})
return Response({
'message': '查询成功',
'code': 0,
'data': {
'id': tag.id,
'name': tag.name,
'color': tag.color,
'description': tag.description,
'case_count': len(case_list),
'cases': case_list,
'created_at': tag.created_at,
'updated_at': tag.updated_at
}
}, status=status.HTTP_200_OK)
class EditCaseTag(APIView):
"""编辑案件标签"""
def post(self, request, *args, **kwargs):
id = request.data.get('id')
name = request.data.get('name')
color = request.data.get('color')
description = request.data.get('description')
if not id:
return Response({'status': 'error', 'message': '缺少参数id', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
try:
tag = CaseTag.objects.get(id=id, is_deleted=False)
except CaseTag.DoesNotExist:
return Response({'status': 'error', 'message': '标签不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
# 记录操作前的数据
old_data = {
'id': tag.id,
'name': tag.name,
'color': tag.color,
'description': tag.description
}
update_fields = []
if name:
# 检查新名称是否已被其他标签使用
if CaseTag.objects.filter(name=name, is_deleted=False).exclude(id=id).exists():
return Response({'status': 'error', 'message': '标签名称已存在', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
tag.name = name
update_fields.append('name')
if color:
tag.color = color
update_fields.append('color')
if description is not None:
tag.description = description
update_fields.append('description')
if update_fields:
import datetime
tag.updated_at = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
update_fields.append('updated_at')
tag.save(update_fields=update_fields)
# 记录操作日志
log_operation(
request=request,
operation_type='UPDATE',
module='Business',
action='编辑案件标签',
target_type='CaseTag',
target_id=tag.id,
target_name=tag.name,
old_data=old_data,
new_data={'name': tag.name, 'color': tag.color, 'description': tag.description},
remark=f'编辑案件标签:{tag.name}'
)
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
class DeleteCaseTag(APIView):
"""删除案件标签"""
def post(self, request, *args, **kwargs):
id = request.data.get('id')
if not id:
return Response({'status': 'error', 'message': '缺少参数id', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
try:
tag = CaseTag.objects.get(id=id, is_deleted=False)
except CaseTag.DoesNotExist:
return Response({'status': 'error', 'message': '标签不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
# 记录操作前的数据
old_data = {
'id': tag.id,
'name': tag.name,
'color': tag.color,
'description': tag.description
}
# 软删除:更新 is_deleted 字段
tag.is_deleted = True
tag.save()
# 记录操作日志
log_operation(
request=request,
operation_type='DELETE',
module='Business',
action='删除案件标签',
target_type='CaseTag',
target_id=tag.id,
target_name=tag.name,
old_data=old_data,
remark=f'删除案件标签:{tag.name}'
)
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
class CaseTagDropdownList(APIView):
"""获取标签下拉列表(用于案件管理中选择标签)"""
def post(self, request, *args, **kwargs):
tags = CaseTag.objects.filter(is_deleted=False).order_by('name')
data = []
for tag in tags:
data.append({
'id': tag.id,
'name': tag.name,
'color': tag.color
})
return Response({
'message': '查询成功',
'data': data,
'code': 0
}, status=status.HTTP_200_OK)
2026-01-19 16:16:05 +08:00
class SetCaseTags(APIView):
"""给案件设置标签"""
def post(self, request, *args, **kwargs):
"""
给案件设置标签
支持设置多个标签也可以清空所有标签
"""
case_id = request.data.get('case_id')
tag_ids = request.data.get('tag_ids') # 标签ID列表数组或逗号分隔的字符串
if not case_id:
return Response({
'status': 'error',
'message': '缺少参数case_id案件ID',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 验证案件是否存在
try:
case = Case.objects.get(id=case_id, is_deleted=False)
except Case.DoesNotExist:
return Response({
'status': 'error',
'message': '案件不存在或已被删除',
'code': 1
}, status=status.HTTP_404_NOT_FOUND)
# 记录操作前的标签
old_tags = list(case.tags.filter(is_deleted=False).values('id', 'name'))
old_tag_ids = [tag['id'] for tag in old_tags]
# 处理标签ID列表
tag_id_list = []
if tag_ids is not None:
try:
# 支持数组或逗号分隔的字符串
if isinstance(tag_ids, str):
if tag_ids.strip() == '':
tag_id_list = [] # 空字符串表示清空所有标签
elif ',' in tag_ids:
tag_id_list = [int(x.strip()) for x in tag_ids.split(',') if x.strip()]
else:
tag_id_list = [int(tag_ids)]
elif isinstance(tag_ids, list):
tag_id_list = [int(x) for x in tag_ids if x]
else:
tag_id_list = []
except (ValueError, TypeError) as e:
return Response({
'status': 'error',
'message': f'标签ID格式错误{str(e)}',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 验证标签是否存在
if tag_id_list:
valid_tags = CaseTag.objects.filter(id__in=tag_id_list, is_deleted=False)
valid_tag_ids = list(valid_tags.values_list('id', flat=True))
# 检查是否有无效的标签ID
invalid_tag_ids = set(tag_id_list) - set(valid_tag_ids)
if invalid_tag_ids:
return Response({
'status': 'error',
'message': f'以下标签ID不存在或已被删除{", ".join(map(str, invalid_tag_ids))}',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 设置标签关联
case.tags.set(valid_tags)
else:
# 清空所有标签
case.tags.clear()
# 获取更新后的标签信息
new_tags = list(case.tags.filter(is_deleted=False).values('id', 'name', 'color'))
new_tag_ids = [tag['id'] for tag in new_tags]
# 记录操作日志
log_operation(
request=request,
operation_type='UPDATE',
module='Business',
action='设置案件标签',
target_type='Case',
target_id=case.id,
target_name=case.contract_no or f'案件ID-{case.id}',
old_data={'tag_ids': old_tag_ids, 'tags': old_tags},
new_data={'tag_ids': new_tag_ids, 'tags': new_tags},
remark=f'设置案件标签案件ID {case_id}标签ID {new_tag_ids}'
)
return Response({
'message': '设置成功',
'code': 0,
'data': {
'case_id': case.id,
'tags': new_tags,
'tag_ids': new_tag_ids
}
}, status=status.HTTP_200_OK)
2026-01-19 10:48:36 +08:00
class CaseListByTag(APIView):
"""按标签筛选案件列表"""
def post(self, request, *args, **kwargs):
"""
根据标签ID筛选案件列表
"""
page = request.data.get('page')
per_page = request.data.get('per_page')
tag_ids = request.data.get('tag_ids') # 标签ID列表数组或逗号分隔的字符串
times = request.data.get('times')
end_time = request.data.get('end_time')
type = request.data.get('type')
if not all([page, per_page]):
return Response({'status': 'error', 'message': '缺少参数page和per_page', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
if not tag_ids:
return Response({'status': 'error', 'message': '缺少参数tag_ids', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
# 解析标签ID
try:
if isinstance(tag_ids, str):
if ',' in tag_ids:
tag_id_list = [int(x.strip()) for x in tag_ids.split(',') if x.strip()]
else:
tag_id_list = [int(tag_ids)]
elif isinstance(tag_ids, list):
tag_id_list = [int(x) for x in tag_ids if x]
else:
tag_id_list = []
except (ValueError, TypeError):
return Response({'status': 'error', 'message': '标签ID格式错误', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
if not tag_id_list:
return Response({'status': 'error', 'message': '标签ID列表为空', 'code': 1},
status=status.HTTP_400_BAD_REQUEST)
# 验证标签是否存在
valid_tags = CaseTag.objects.filter(id__in=tag_id_list, is_deleted=False)
if not valid_tags.exists():
return Response({'status': 'error', 'message': '标签不存在', 'code': 1},
status=status.HTTP_404_NOT_FOUND)
# 构建查询条件
Q_obj = Q(is_deleted=False)
# 按标签筛选(多标签取交集,即案件必须同时拥有所有指定标签)
for tag_id in tag_id_list:
Q_obj &= Q(tags__id=tag_id)
# 时间范围筛选
if times and end_time:
Q_obj &= Q(times__gte=times) & Q(times__lte=end_time)
# 项目类型筛选
if type:
por_id = ProjectRegistration.objects.filter(type=type, is_deleted=False).values_list('id', flat=True)
Q_obj &= Q(project_id__in=por_id)
# 查询案件使用distinct去重因为多对多关系可能产生重复
cases = Case.objects.filter(Q_obj).distinct().order_by('-id')
total = len(cases)
paginator = Paginator(cases, per_page)
try:
cases_page = paginator.page(page)
except PageNotAnInteger:
cases_page = paginator.page(1)
except EmptyPage:
cases_page = paginator.page(paginator.num_pages)
data = []
for info in cases_page.object_list:
pro = None
if info.project_id:
try:
pro = ProjectRegistration.objects.get(id=info.project_id, is_deleted=False)
except ProjectRegistration.DoesNotExist:
pro = None
contract_no = info.contract_no or (pro.ContractNo if pro else None)
project_type = info.project_type or (pro.type if pro else None)
client_name = info.client_name or (pro.client_info if pro else None)
party_name = info.party_name or (pro.party_info if pro else None)
project_description = info.project_description or (pro.description if pro else None)
responsiblefor_raw = info.responsiblefor or (pro.responsiblefor if pro else None)
charge = info.charge or (pro.charge if pro else None)
# 解析负责人信息
try:
responsiblefor_dict = json.loads(responsiblefor_raw) if responsiblefor_raw else {}
except (json.JSONDecodeError, TypeError):
responsiblefor_dict = responsiblefor_raw if responsiblefor_raw else {}
2026-01-31 11:21:46 +08:00
# 解析结案申请;代理合同已从案件管理移除,不再返回
2026-01-19 10:48:36 +08:00
try:
closing_application_list = json.loads(info.Closingapplication) if info.Closingapplication else []
except (json.JSONDecodeError, TypeError):
closing_application_list = []
2026-01-21 15:24:16 +08:00
if not closing_application_list:
closing_application_list = ""
2026-01-22 14:25:53 +08:00
contractreturn_str = info.Contractreturn or ""
if contractreturn_str.strip() in ["[]", ""]:
contractreturn_str = ""
2026-01-21 15:24:16 +08:00
closing_application_str = info.Closingapplication or ""
if closing_application_str.strip() in ["[]", ""]:
closing_application_str = ""
2026-01-19 10:48:36 +08:00
# 处理已开票/已收款字段
invoice_status_value = normalize_amount_value(info.invoice_status)
paymentcollection_value = normalize_amount_value(info.paymentcollection)
# 获取案件标签
tags = info.tags.filter(is_deleted=False)
tag_list = []
for tag in tags:
tag_list.append({
'id': tag.id,
'name': tag.name,
'color': tag.color
})
data.append({
"id": info.id,
"ContractNo": contract_no or "",
"type": project_type or "",
"client_info": client_name or "",
"party_info": party_name or "",
"description": project_description,
"responsiblefor": responsiblefor_dict,
"charge": charge,
'times': info.times,
2026-01-22 14:25:53 +08:00
"Contractreturn": contractreturn_str, # 合同返还(没有数据时返回空字符串)
2026-01-21 15:24:16 +08:00
"Closingapplication": closing_application_str, # 结案申请JSON字符串没有文件时返回空字符串
"ClosingapplicationUrls": closing_application_list if closing_application_list else "", # 结案申请URL列表没有文件时返回空字符串
2026-01-19 10:48:36 +08:00
"ChangeRequest": info.ChangeRequest,
"ChangeItem": info.ChangeItem,
"ChangeReason": info.ChangeReason,
"ChangeAgreement": info.ChangeAgreement,
"invoice_status": invoice_status_value,
"paymentcollection": paymentcollection_value,
"state": info.state,
"project_id": info.project_id,
"tags": tag_list,
"tag_ids": [tag['id'] for tag in tag_list],
})
return Response({
'message': '查询成功',
"total": total,
'data': data,
'code': 0
2026-01-22 18:38:29 +08:00
}, status=status.HTTP_200_OK)
class PropagandaEit(APIView):
def post(self, request):
content = request.data.get('content')
pro = Propaganda.objects.get(id="1")
pro.content = content
pro.save()
return Response({
'message': '更新成功',
'code': 0
}, status=status.HTTP_200_OK)
class addSystem(APIView):
def post(self, request):
title = request.data.get('title')
content = request.data.get('content')
file = request.FILES.getlist('file')
state = request.data.get('state')
token = request.META.get('token')
if not all([title, content]):
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
now = datetime.now()
date_str = now.strftime('%Y-%m-%d')
bol = 0
if state == "置顶":
bol = 1
user = User.objects.get(token=token, is_deleted=False).username
System.objects.create(
title=title,
content=content,
file=json.dumps(flies(file)),
username=user,
times=date_str,
state=bol
)
return Response({'message': '添加成功', 'code': 0}, status=status.HTTP_200_OK)
class SystemList(APIView):
def post(self, request):
page = request.data.get('page')
per_page = request.data.get('per_page')
seas = System.objects.filter(is_deleted=False).order_by('-state', '-times')
total = len(seas)
paginator = Paginator(seas, 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:
2026-02-01 13:02:16 +08:00
# 解析 file 字段:有文件返回数组,无文件返回空字符串
try:
file_list = json.loads(info.file) if info.file else []
except (json.JSONDecodeError, TypeError):
file_list = []
# 空列表返回空字符串
file_result = file_list if file_list else ""
if file_result !="":
file_result = json.dumps(file_result)
data.append({
"id": info.id,
"title": info.title,
"content": info.content,
"times": info.times,
"file": file_result, # file 字段解析,
"username": info.username,
"state": info.state,
})
return Response({'message': '展示成功', "total": total, 'data': data, 'code': 0}, status=status.HTTP_200_OK)
class eitSystem(APIView):
def post(self, request):
ID = request.data.get('id')
title = request.data.get('title')
content = request.data.get('content')
file = request.FILES.getlist('file')
state = request.data.get('state')
token = request.META.get('token') # 从中间件设置的 token 获取
if not all([title, content, ID]):
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
try:
2026-02-01 12:55:19 +08:00
system_record = System.objects.get(id=ID, is_deleted=False)
except System.DoesNotExist:
return Response({'status': 'error', 'message': '系统公告不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
now = datetime.now()
date_str = now.strftime('%Y-%m-%d')
if state:
bol = 0
if state == "置顶":
bol = 1
2026-02-01 12:55:19 +08:00
system_record.state = bol
system_record.save(update_fields=['state'])
# 获取用户信息(如果 token 存在)
if token:
try:
user = User.objects.get(token=token, is_deleted=False).username
except User.DoesNotExist:
# 如果用户不存在,使用原有用户名
2026-02-01 12:55:19 +08:00
user = system_record.username
else:
# 如果 token 不存在,使用原有用户名
2026-02-01 12:55:19 +08:00
user = system_record.username
if title:
2026-02-01 12:55:19 +08:00
system_record.title = title
system_record.save(update_fields=['title'])
if content:
2026-02-01 12:55:19 +08:00
system_record.content = content
system_record.save(update_fields=['content'])
if file:
2026-02-01 12:55:19 +08:00
system_record.file = json.dumps(flies(file)) # 使用 flies 函数处理文件
system_record.save(update_fields=['file'])
2026-02-01 12:55:19 +08:00
system_record.username = user
system_record.times = date_str
2026-02-01 12:55:19 +08:00
system_record.save(update_fields=['username', 'times'])
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
class deleteSystem(APIView):
def post(self, request, *args, **kwargs):
ID = request.data.get('id')
try:
2026-02-01 12:55:19 +08:00
system_record = System.objects.get(id=ID, is_deleted=False)
# 软删除:更新 is_deleted 字段
2026-02-01 12:55:19 +08:00
system_record.is_deleted = True
system_record.save()
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
2026-02-01 12:55:19 +08:00
except System.DoesNotExist:
return Response({'status': 'error', 'message': '系统公告不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
2026-02-01 18:17:40 +08:00
class ExportCaseLogExcel(APIView):
def post(self, request, *args, **kwargs):
"""
2026-02-01 22:53:15 +08:00
导出指定案件的日志到Excel上传到OSS并返回下载链接
2026-02-01 18:17:40 +08:00
必填参数case_id案件ID
可选参数start_time开始时间end_time结束时间
2026-02-01 22:53:15 +08:00
返回下载链接
2026-02-01 18:17:40 +08:00
"""
import pandas as pd
2026-02-01 22:53:15 +08:00
import oss2
import uuid
2026-02-01 18:17:40 +08:00
from io import BytesIO
2026-02-01 20:22:04 +08:00
case_id = request.data.get('case_id')
start_time = request.data.get('start_time')
end_time = request.data.get('end_time')
2026-02-01 18:17:40 +08:00
if not case_id:
2026-02-01 20:22:04 +08:00
return Response({'status': 'error', 'message': '缺少参数case_id', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
2026-02-01 18:17:40 +08:00
# 获取案件信息
try:
case = Case.objects.get(id=case_id, is_deleted=False)
except Case.DoesNotExist:
return Response({'status': 'error', 'message': '案件不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
# 构建查询条件
Q_obj = Q(is_deleted=False, case_id=case_id)
if start_time:
Q_obj &= Q(times__gte=start_time)
if end_time:
Q_obj &= Q(times__lte=end_time)
2026-02-01 20:22:04 +08:00
# 查询日志
2026-02-01 18:17:40 +08:00
logs = Caselog.objects.filter(Q_obj).order_by('-times', '-id')
if not logs.exists():
2026-02-01 20:22:04 +08:00
return Response({'status': 'error', 'message': '该案件没有日志记录', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
2026-02-01 18:17:40 +08:00
2026-02-01 23:03:45 +08:00
# 构建Excel数据只保留记录人、记录时间、工作内容
2026-02-01 18:17:40 +08:00
excel_data = []
for log in logs:
excel_data.append({
'记录人': log.username or '',
2026-02-01 23:03:45 +08:00
'记录时间': log.times or '',
'工作内容': log.content or '',
2026-02-01 18:17:40 +08:00
})
2026-02-01 22:53:15 +08:00
# 生成Excel文件
2026-02-01 20:22:04 +08:00
df = pd.DataFrame(excel_data)
2026-02-01 18:17:40 +08:00
output = BytesIO()
with pd.ExcelWriter(output, engine='openpyxl') as writer:
df.to_excel(writer, index=False, sheet_name='案件日志')
2026-02-01 20:22:04 +08:00
ws = writer.sheets['案件日志']
2026-02-01 18:17:40 +08:00
for idx, col in enumerate(df.columns):
2026-02-01 20:22:04 +08:00
width = min(max(df[col].astype(str).map(len).max(), len(col)) + 2, 50)
ws.column_dimensions[chr(65 + idx) if idx < 26 else chr(64 + idx // 26) + chr(65 + idx % 26)].width = width
2026-02-01 18:17:40 +08:00
output.seek(0)
2026-02-01 22:53:15 +08:00
# 上传到阿里云OSS
try:
endpoint = 'https://oss-cn-beijing.aliyuncs.com'
access_key_id = "LTAI5tRMxrM95Pi8JEEmqRcg"
access_key_secret = "8vueGCsRVeFyQMcAA7sysO7LSnuJDG"
bucket_name = 'oss-bucket-yj'
auth = oss2.Auth(access_key_id, access_key_secret)
bucket = oss2.Bucket(auth, endpoint, bucket_name)
# 生成唯一文件名
contract_no_safe = (case.contract_no or f'case{case_id}').replace('/', '_').replace('\\', '_')
filename = f'{uuid.uuid4().hex[:12]}_{contract_no_safe}_日志_{datetime.now().strftime("%Y%m%d%H%M%S")}.xlsx'
# 上传文件
result = bucket.put_object(filename, output.getvalue())
if result.status == 200:
# 生成下载链接
2026-02-01 23:07:28 +08:00
download_url = f'http://{bucket_name}.{endpoint.replace("https://", "")}/{filename}'
2026-02-01 22:53:15 +08:00
return Response({
'message': '导出成功',
'code': 0,
'data': {
'url': download_url,
'filename': f'{case.contract_no or f"案件{case_id}"}_日志.xlsx'
}
}, status=status.HTTP_200_OK)
else:
return Response({'status': 'error', 'message': '文件上传失败', 'code': 1}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
except Exception as e:
return Response({'status': 'error', 'message': f'文件上传失败: {str(e)}', 'code': 1}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
2026-02-01 18:17:40 +08:00