5540 lines
233 KiB
Python
5540 lines
233 KiB
Python
from rest_framework.views import APIView
|
||
from rest_framework.response import Response
|
||
from rest_framework import status
|
||
import json
|
||
import ast
|
||
import re
|
||
from decimal import Decimal, InvalidOperation
|
||
from User.models import User, Approval
|
||
from User.utils import log_operation, normalize_approvers_param, build_missing_approvers_message
|
||
from .models import PreFiling, ProjectRegistration, Bid, Case, Invoice, Caselog, SealApplication, Warehousing, \
|
||
RegisterPlatform, Announcement, LawyerFlie, Schedule, permission, role, CaseChangeRequest, CaseTag,Propaganda,System, ContractCounter
|
||
from .contract_no import generate_next_contract_no, get_next_contract_no_preview
|
||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||
from utility.utility import flies
|
||
from datetime import datetime
|
||
|
||
from django.db.models import Count, Q
|
||
from django.db import transaction
|
||
import os
|
||
|
||
def normalize_match_reason(reason_str):
|
||
"""
|
||
将多条重复语义的冲突原因归纳为简洁、易读的一条或几条说明。
|
||
例如:委托人姓名匹配;相对方与委托人姓名匹配;相对方姓名匹配;委托人与相对方姓名匹配
|
||
归纳为:委托人姓名与历史记录一致;相对方姓名与历史记录一致
|
||
"""
|
||
if not reason_str or reason_str.strip() == '匹配':
|
||
return reason_str.strip() or '匹配'
|
||
parts = [p.strip() for p in reason_str.split(';') if p.strip()]
|
||
# 按类型归纳,避免重复表述
|
||
has_client_name = any(
|
||
'委托人姓名匹配' in p or '相对方与委托人姓名匹配' in p for p in parts
|
||
)
|
||
has_client_info = any(
|
||
'委托人信息匹配' in p or '相对方与委托人信息匹配' in p for p in parts
|
||
)
|
||
has_client_id = any('委托人身份证号匹配' in p for p in parts)
|
||
has_party_name = any(
|
||
'相对方姓名匹配' in p or '委托人与相对方姓名匹配' in p for p in parts
|
||
)
|
||
has_party_info = any(
|
||
'相对方信息匹配' in p or '委托人与相对方信息匹配' in p for p in parts
|
||
)
|
||
has_party_id = any('相对方身份证号匹配' in p for p in parts)
|
||
# 立项表:相对方与委托人身份证号匹配 等
|
||
has_party_client_id = any(
|
||
'相对方与委托人身份证号匹配' in p or '委托人与相对方身份证号匹配' in p for p in parts
|
||
)
|
||
undertaker_parts = [p for p in parts if '承办人员匹配' in p or '承办人' in p]
|
||
responsible_parts = [p for p in parts if '负责人匹配' in p]
|
||
# 招标单位相关(投标表)
|
||
bid_parts = [
|
||
p for p in parts
|
||
if '招标单位' in p or '在招标单位中' in p or '在招标单位' in p
|
||
]
|
||
result = []
|
||
if has_client_name:
|
||
result.append('委托人姓名与历史记录一致')
|
||
if has_client_info:
|
||
result.append('委托人信息与历史记录一致')
|
||
if has_client_id:
|
||
result.append('委托人身份证号与历史记录一致')
|
||
if has_party_name:
|
||
result.append('相对方姓名与历史记录一致')
|
||
if has_party_info:
|
||
result.append('相对方信息与历史记录一致')
|
||
if has_party_id:
|
||
result.append('相对方身份证号与历史记录一致')
|
||
# 立项表:委托人身份证在相对方字段/相对方身份证在委托人字段出现时
|
||
if has_party_client_id and not (has_client_id or has_party_id):
|
||
result.append('委托人或相对方身份证号与历史记录一致')
|
||
result.extend(undertaker_parts)
|
||
result.extend(responsible_parts)
|
||
result.extend(bid_parts)
|
||
return ';'.join(result) if result else '匹配'
|
||
|
||
|
||
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):
|
||
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)
|
||
return None
|
||
|
||
|
||
def search_related_records(client_info, party_info, exclude_project_id=None):
|
||
"""
|
||
根据委托人和相对方信息检索预立案、投标、立项三个表的冲突记录。
|
||
冲突规则:当前委托人只与历史「委托人」匹配,当前相对方只与历史「相对方」匹配,任一匹配即视为冲突。
|
||
|
||
Args:
|
||
client_info: 委托人身份信息(字符串)
|
||
party_info: 相对方身份信息(字符串)
|
||
exclude_project_id: 要排除的立项ID(可选,用于排除当前正在创建的记录)
|
||
|
||
Returns:
|
||
dict: 包含三个表的冲突记录信息
|
||
{
|
||
'prefiling_conflicts': [...], # 预立案冲突记录列表
|
||
'project_conflicts': [...], # 立项冲突记录列表
|
||
'bid_conflicts': [...] # 投标冲突记录列表
|
||
}
|
||
"""
|
||
result = {
|
||
'prefiling_conflicts': [],
|
||
'project_conflicts': [],
|
||
'bid_conflicts': []
|
||
}
|
||
|
||
if not client_info and not party_info:
|
||
return result
|
||
|
||
# 解析委托人和相对方信息(支持 JSON 数组,提取所有人员)
|
||
client_names, client_id_numbers, client_originals = _extract_all_person_info(client_info)
|
||
party_names, party_id_numbers, party_originals = _extract_all_person_info(party_info)
|
||
|
||
# 检索预立案表 - 委托人只匹配历史委托人,相对方只匹配历史相对方,任一匹配即冲突
|
||
if client_names or party_names:
|
||
matched_prefilings = []
|
||
prefiling_all = PreFiling.objects.filter(is_deleted=False)
|
||
for pf in prefiling_all[:100]:
|
||
if len(matched_prefilings) >= 10:
|
||
break
|
||
pf_client_names = []
|
||
try:
|
||
cd = json.loads(pf.client_username) if isinstance(pf.client_username, str) else pf.client_username
|
||
if isinstance(cd, list):
|
||
for item in cd:
|
||
if isinstance(item, dict) and 'name' in item:
|
||
pf_client_names.append(item['name'].strip())
|
||
elif isinstance(cd, dict) and 'name' in cd:
|
||
pf_client_names.append(cd['name'].strip())
|
||
except (json.JSONDecodeError, TypeError, AttributeError):
|
||
pf_client_names = [pf.client_username.strip()] if pf.client_username else []
|
||
pf_party_names = []
|
||
try:
|
||
pd = json.loads(pf.party_username) if isinstance(pf.party_username, str) else pf.party_username
|
||
if isinstance(pd, list):
|
||
for item in pd:
|
||
if isinstance(item, dict) and 'name' in item:
|
||
pf_party_names.append(item['name'].strip())
|
||
elif isinstance(pd, dict) and 'name' in pd:
|
||
pf_party_names.append(pd['name'].strip())
|
||
except (json.JSONDecodeError, TypeError, AttributeError):
|
||
pf_party_names = [pf.party_username.strip()] if pf.party_username else []
|
||
client_match = False
|
||
if client_names and pf_client_names:
|
||
for pf_c in pf_client_names:
|
||
if any(pf_c in cn for cn in client_names) or any(pf_c in co for co in client_originals):
|
||
client_match = True
|
||
break
|
||
party_match = False
|
||
if party_names and pf_party_names:
|
||
for pf_p in pf_party_names:
|
||
if any(pf_p in pn for pn in party_names) or any(pf_p in po for po in party_originals):
|
||
party_match = True
|
||
break
|
||
if client_match or party_match:
|
||
matched_prefilings.append({
|
||
'id': pf.id,
|
||
'times': pf.times,
|
||
'client_username': pf.client_username,
|
||
'party_username': pf.party_username,
|
||
'description': pf.description
|
||
})
|
||
result['prefiling_conflicts'] = matched_prefilings
|
||
|
||
# 检索立项表 - 委托人只匹配历史 client_info,相对方只匹配历史 party_info
|
||
project_records = ProjectRegistration.objects.filter(is_deleted=False)
|
||
if exclude_project_id:
|
||
project_records = project_records.exclude(id=exclude_project_id)
|
||
project_q = Q()
|
||
for cn in client_names:
|
||
project_q |= Q(client_info__icontains=cn)
|
||
for co in client_originals:
|
||
project_q |= Q(client_info__icontains=co[:50])
|
||
for cid in client_id_numbers:
|
||
project_q |= Q(client_info__icontains=cid)
|
||
for pn in party_names:
|
||
project_q |= Q(party_info__icontains=pn)
|
||
for po in party_originals:
|
||
project_q |= Q(party_info__icontains=po[:50])
|
||
for pid in party_id_numbers:
|
||
project_q |= Q(party_info__icontains=pid)
|
||
if project_q:
|
||
project_records = project_records.filter(project_q)
|
||
project_list = list(project_records[:10].values('id', 'ContractNo', 'times', 'type', 'client_info', 'party_info'))
|
||
result['project_conflicts'] = project_list
|
||
|
||
# 检索投标表 - 招标单位是否包含委托人或相对方信息
|
||
bid_records = Bid.objects.filter(is_deleted=False)
|
||
bid_q = Q()
|
||
for cn in client_names:
|
||
bid_q |= Q(BiddingUnit__icontains=cn)
|
||
for pn in party_names:
|
||
bid_q |= Q(BiddingUnit__icontains=pn)
|
||
for co in client_originals:
|
||
bid_q |= Q(BiddingUnit__icontains=co[:50])
|
||
for po in party_originals:
|
||
bid_q |= Q(BiddingUnit__icontains=po[:50])
|
||
for cid in client_id_numbers:
|
||
bid_q |= Q(BiddingUnit__icontains=cid)
|
||
for pid in party_id_numbers:
|
||
bid_q |= Q(BiddingUnit__icontains=pid)
|
||
if bid_q:
|
||
bid_records = bid_records.filter(bid_q)
|
||
bid_list = list(bid_records[:10].values('id', 'ProjectName', 'times', 'BiddingUnit'))
|
||
result['bid_conflicts'] = bid_list
|
||
|
||
return result
|
||
|
||
|
||
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}"
|
||
)
|
||
|
||
|
||
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:
|
||
# 解析客户名称:如果是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}")
|
||
if project_registration.party_info:
|
||
# 解析相对方名称:如果是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}")
|
||
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"):
|
||
content_parts.append(f"承办人:{responsiblefor_dict.get('responsible_person')}")
|
||
|
||
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)
|
||
|
||
|
||
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"
|
||
|
||
class registration(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
预立案登记
|
||
: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')
|
||
user = User.objects.get(token=token, is_deleted=False).username
|
||
if not all([times, description, Undertaker]):
|
||
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
prefiling = PreFiling.objects.create(
|
||
times=times,
|
||
client_username=client_username,
|
||
party_username=party_username,
|
||
description=description,
|
||
Undertaker=Undertaker,
|
||
submit=user
|
||
)
|
||
|
||
# 利益冲突检索:比对预立案、立项、投标三张表(直接传 JSON,conflict_search 内部解析所有人员)
|
||
conflict_result = conflict_search(
|
||
client_info=prefiling.client_username,
|
||
party_info=prefiling.party_username,
|
||
undertaker=Undertaker,
|
||
exclude_prefiling_id=prefiling.id
|
||
)
|
||
total_conflicts = (
|
||
len(conflict_result['prefiling_conflicts'])
|
||
+ len(conflict_result['project_conflicts'])
|
||
+ len(conflict_result['bid_conflicts'])
|
||
)
|
||
|
||
# 记录操作日志
|
||
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}'
|
||
)
|
||
|
||
return Response({
|
||
'message': '登记成功',
|
||
'code': 0,
|
||
'conflict_result': {
|
||
'total_conflicts': total_conflicts,
|
||
'prefiling_conflicts_count': len(conflict_result['prefiling_conflicts']),
|
||
'project_conflicts_count': len(conflict_result['project_conflicts']),
|
||
'bid_conflicts_count': len(conflict_result['bid_conflicts']),
|
||
'prefiling_conflicts': conflict_result['prefiling_conflicts'],
|
||
'project_conflicts': conflict_result['project_conflicts'],
|
||
'bid_conflicts': conflict_result['bid_conflicts'],
|
||
}
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class registrationList(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
预立案登记列表接口
|
||
注意:由于预立案和立项登记、投标登记已经拆分,现在返回所有未删除的预立案
|
||
:param request:
|
||
:param args:
|
||
:param kwargs:
|
||
:return:
|
||
"""
|
||
# 由于预立案和立项登记、投标登记已经拆分,不再需要检查关联
|
||
# 直接返回所有未删除的预立案
|
||
prefiling_list = PreFiling.objects.filter(is_deleted=False)
|
||
data = []
|
||
for prefiling in prefiling_list:
|
||
data.append({
|
||
"id": prefiling.id,
|
||
"client_username": prefiling.client_username,
|
||
"party_username": prefiling.party_username,
|
||
})
|
||
return Response({'message': '展示成功', 'data': data, 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class registrationDetail(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
预立案登记展示
|
||
:param request:
|
||
:param args:
|
||
:param kwargs:
|
||
:return:
|
||
"""
|
||
page = request.data.get('page')
|
||
per_page = request.data.get('per_page')
|
||
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)
|
||
|
||
pre = PreFiling.objects.filter(Q_obj, is_deleted=False).order_by('-id')
|
||
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,
|
||
})
|
||
|
||
return Response({'message': '展示成功', "total": total, 'data': data, 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
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')
|
||
|
||
if not id:
|
||
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
pre = PreFiling.objects.get(id=id, is_deleted=False)
|
||
except PreFiling.DoesNotExist:
|
||
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'])
|
||
|
||
# 利益冲突检索:比对预立案、立项、投标三张表(直接传 JSON,conflict_search 内部解析所有人员)
|
||
conflict_result = conflict_search(
|
||
client_info=pre.client_username,
|
||
party_info=pre.party_username,
|
||
undertaker=pre.Undertaker,
|
||
exclude_prefiling_id=pre.id
|
||
)
|
||
total_conflicts = (
|
||
len(conflict_result['prefiling_conflicts'])
|
||
+ len(conflict_result['project_conflicts'])
|
||
+ len(conflict_result['bid_conflicts'])
|
||
)
|
||
|
||
return Response({
|
||
'message': '编辑成功',
|
||
'code': 0,
|
||
'conflict_result': {
|
||
'total_conflicts': total_conflicts,
|
||
'prefiling_conflicts_count': len(conflict_result['prefiling_conflicts']),
|
||
'project_conflicts_count': len(conflict_result['project_conflicts']),
|
||
'bid_conflicts_count': len(conflict_result['bid_conflicts']),
|
||
'prefiling_conflicts': conflict_result['prefiling_conflicts'],
|
||
'project_conflicts': conflict_result['project_conflicts'],
|
||
'bid_conflicts': conflict_result['bid_conflicts'],
|
||
}
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class Project(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
立项登记 - 独立创建,不再需要预立案。
|
||
必填:项目类型、立项日期、项目简述、收费情况、承办人信息、合同。
|
||
合同编号(ContractNo)可选:不传或传空时由后端按规则自动生成(校准(字)字【年份】第n号)。
|
||
相对方(party_info)为非必填项,委托人(client_info)可为空。
|
||
:param request:
|
||
:param args:
|
||
:param kwargs:
|
||
:return:
|
||
"""
|
||
project_type = request.data.get('type')
|
||
ContractNo = request.data.get('ContractNo')
|
||
if ContractNo is not None and not isinstance(ContractNo, str):
|
||
ContractNo = str(ContractNo)
|
||
times = request.data.get('times')
|
||
client_info = request.data.get('client_info') # 委托人身份信息(可选)
|
||
party_info = request.data.get('party_info') # 相对方身份信息(非必填)
|
||
description = request.data.get('description') # 项目简述
|
||
responsiblefor = request.data.get('responsiblefor') # 承办人信息(字典格式)
|
||
charge = request.data.get('charge')
|
||
contract = request.FILES.getlist('contract')
|
||
approvers = request.data.get('approvers') # 审核人列表(可选,多人团队时需要,推荐:用户ID数组如[1,2,3],兼容:用户名数组)
|
||
# 兼容旧接口:如果传了 personincharge,转换为 approvers
|
||
personincharge = request.data.get('personincharge')
|
||
|
||
|
||
approvers = normalize_approvers_param(approvers, personincharge)
|
||
|
||
import datetime
|
||
import json
|
||
|
||
# 明确拒绝 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)
|
||
|
||
# 验证必填字段(合同编号可选,不传则后端自动生成)
|
||
if not all([project_type, times, description, charge]):
|
||
return Response({'status': 'error', 'message': '缺少必填参数(项目类型、立项日期、项目简述、收费情况)', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 验证承办人信息(字典格式)
|
||
if not responsiblefor:
|
||
return Response({'status': 'error', 'message': '承办人信息不能为空', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 解析承办人信息(支持字符串JSON或字典)
|
||
try:
|
||
if isinstance(responsiblefor, str):
|
||
responsiblefor_dict = json.loads(responsiblefor)
|
||
else:
|
||
responsiblefor_dict = responsiblefor
|
||
|
||
# 验证承办人(responsible_person)必填
|
||
if not responsiblefor_dict.get('responsible_person'):
|
||
return Response({'status': 'error', 'message': '承办人不能为空', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 将字典转换为JSON字符串存储
|
||
responsiblefor_str = json.dumps(responsiblefor_dict, ensure_ascii=False)
|
||
except (json.JSONDecodeError, TypeError, AttributeError):
|
||
return Response({'status': 'error', 'message': '承办人信息格式错误,应为字典格式', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
if contract:
|
||
contract = flies(contract)
|
||
else:
|
||
contract = []
|
||
|
||
# 将文件URL列表转换为字符串存储(如果有多个URL,用逗号分隔)
|
||
if contract:
|
||
# 如果只有一个URL,直接存储;如果有多个,用逗号分隔
|
||
contract_str = contract[0] if len(contract) == 1 else ','.join(contract)
|
||
else:
|
||
contract_str = ""
|
||
|
||
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
|
||
|
||
# 使用事务确保创建立项和更新计数器是原子操作
|
||
with transaction.atomic():
|
||
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)
|
||
|
||
# 创建立项登记(相对方可为空,空字符串统一为 None)
|
||
pro = ProjectRegistration.objects.create(
|
||
type=project_type,
|
||
ContractNo=ContractNo.strip() if isinstance(ContractNo, str) else ContractNo,
|
||
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="审核中",
|
||
)
|
||
|
||
# 仅当用户手动传入合同编号时,更新计数器(与自动生成逻辑一致,保证序号连续)
|
||
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}")
|
||
|
||
|
||
today = datetime.datetime.now()
|
||
formatted_date = today.strftime("%Y-%m-%d")
|
||
|
||
# 获取团队信息:优先承办人,其次当前登录用户
|
||
team_name = get_team_name_from_responsiblefor(responsiblefor_dict)
|
||
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:
|
||
team_name = request_user.team
|
||
except User.DoesNotExist:
|
||
pass
|
||
|
||
# 使用统一的审核流程函数
|
||
from User.utils import create_approval_with_team_logic
|
||
|
||
# 构建承办人信息描述
|
||
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'):
|
||
responsible_desc += f",协办律师:{responsiblefor_dict.get('assistant_lawyer')}"
|
||
if responsiblefor_dict.get('case_manager_lawyer'):
|
||
responsible_desc += f",案管律师:{responsiblefor_dict.get('case_manager_lawyer')}"
|
||
|
||
# 检索相关记录(预立案、投标、立项)- 使用 conflict_search 函数,支持更灵活的参数组合
|
||
related_records_info = conflict_search(client_info=client_info, party_info=party_info, exclude_project_id=pro.id)
|
||
|
||
# 构建冲突信息文本(用于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 ""
|
||
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}"
|
||
|
||
approval, approvers_order_json, needs_approval = create_approval_with_team_logic(
|
||
team_name=team_name,
|
||
approvers=approvers,
|
||
title=responsiblefor_dict.get('responsible_person', '') + "立项登记",
|
||
content=content,
|
||
approval_type="立项登记",
|
||
user_id=pro.id,
|
||
business_record=pro,
|
||
today=formatted_date,
|
||
applicant=applicant_name
|
||
)
|
||
|
||
# 如果返回None且需要审核,说明缺少审核人
|
||
if approval is None and needs_approval:
|
||
return Response({
|
||
'status': 'error',
|
||
'message': build_missing_approvers_message(team_name, approvers),
|
||
'code': 1
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 记录操作日志
|
||
new_data = {
|
||
'id': pro.id,
|
||
'contract_no': pro.ContractNo,
|
||
'type': pro.type,
|
||
'responsiblefor': responsiblefor_dict,
|
||
'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,
|
||
remark=f'新增立项登记:合同编号 {pro.ContractNo},承办人 {responsiblefor_dict.get("responsible_person", "")}'
|
||
)
|
||
|
||
return Response({
|
||
'message': '登记成功',
|
||
'code': 0,
|
||
'contract_no': pro.ContractNo,
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class Projectquerytype(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
根据年份和项目类型,先查该年份该项目类型的编号(序号),再组装成合同编号返回。
|
||
|
||
入参:
|
||
- type(必填):项目类型,如 法律顾问、专项服务
|
||
- year(可选):年份,如 2025;不传则用 times 或当前年
|
||
- times(可选):日期字符串,取前四位作为年份
|
||
|
||
逻辑:1) 按 年份 + 项目类型 查询当前已用到的编号(序号)
|
||
2) 下一个可用编号 = 当前编号 + 1,组装成「校准(字)字【年份】第n号」
|
||
返回 data:
|
||
- contract_no:组装好的合同编号(下一个可用),如 校准(顾)字【2025】第1号
|
||
- number:下一个可用序号
|
||
- count:该年份该项目类型当前已用数量(已用到的最大序号)
|
||
"""
|
||
project_type = request.data.get('type')
|
||
if not project_type:
|
||
return Response({'status': 'error', 'message': '缺少参数type', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 解析年份:优先 year,其次 times 前四位,否则当前年
|
||
year_param = request.data.get('year')
|
||
times = request.data.get('times')
|
||
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:
|
||
try:
|
||
current_year = int(str(times)[:4])
|
||
except ValueError:
|
||
current_year = datetime.now().year
|
||
else:
|
||
current_year = datetime.now().year
|
||
|
||
# 1) 根据年份 + 项目类型 查询该年份该项目类型的编号(当前已用到的序号)
|
||
try:
|
||
with transaction.atomic():
|
||
counter = ContractCounter.objects.filter(
|
||
project_type=project_type,
|
||
year=current_year
|
||
).first()
|
||
count = counter.current_number if counter else 0
|
||
except Exception:
|
||
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(
|
||
type=project_type,
|
||
times__gte=start_of_year_str,
|
||
times__lte=end_of_year_str,
|
||
is_deleted=False
|
||
).count()
|
||
|
||
# 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)
|
||
|
||
data = {
|
||
"contract_no": contract_no,
|
||
"number": next_number,
|
||
"count": count,
|
||
}
|
||
return Response({'message': '查询成功', "data": data, 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
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
|
||
|
||
|
||
class ProjectDetail(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
立项登记展示
|
||
权限:管理员可见全部立项;承办人仅可见自己承办的立项(负责人修改为承办人)。
|
||
:param request:
|
||
:param args:
|
||
:param kwargs:
|
||
:return:
|
||
"""
|
||
page = request.data.get('page')
|
||
per_page = request.data.get('per_page')
|
||
times = request.data.get('times')
|
||
end_time = request.data.get('end_time')
|
||
client_info = request.data.get('client_info') # 委托人信息搜索
|
||
party_info = request.data.get('party_info') # 相对方信息搜索
|
||
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 client_info:
|
||
Q_obj &= Q(client_info__icontains=client_info)
|
||
if party_info:
|
||
Q_obj &= Q(party_info__icontains=party_info)
|
||
|
||
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')
|
||
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 = []
|
||
import json
|
||
for info in user_agents_page.object_list:
|
||
# 解析承办人信息(JSON字符串转字典,原负责人即承办人)
|
||
try:
|
||
responsiblefor_dict = json.loads(info.responsiblefor) if info.responsiblefor else {}
|
||
except Exception:
|
||
responsiblefor_dict = info.responsiblefor if info.responsiblefor else {}
|
||
|
||
data.append({
|
||
"id": info.id,
|
||
'times': info.times,
|
||
"type": info.type,
|
||
"ContractNo": info.ContractNo,
|
||
"client_info": info.client_info,
|
||
"party_info": info.party_info,
|
||
"description": info.description,
|
||
"responsiblefor": responsiblefor_dict, # 承办人信息(字典格式)
|
||
"charge": info.charge,
|
||
"contract": info.contract,
|
||
"state": info.state,
|
||
})
|
||
|
||
return Response({'message': '展示成功', "total": total, 'data': data, 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class EditProject(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': '缺少参数id', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 检查不可修改的参数(前端不会传入,但作为安全措施进行检查)
|
||
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({
|
||
'status': 'error',
|
||
'message': f'以下参数不允许修改: {", ".join(forbidden_params)}',
|
||
'code': 1
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 获取可修改的参数(前端只传入这些参数)
|
||
times = request.data.get('times')
|
||
client_info = request.data.get('client_info')
|
||
party_info = request.data.get('party_info')
|
||
description = request.data.get('description')
|
||
responsiblefor = request.data.get('responsiblefor') # 承办人信息(字典格式)
|
||
charge = request.data.get('charge')
|
||
contract = request.FILES.getlist('contract')
|
||
approvers = request.data.get('approvers') # 审核人列表(可选,团队类型时需要)
|
||
personincharge = request.data.get('personincharge')
|
||
approvers = normalize_approvers_param(approvers, personincharge)
|
||
|
||
import json
|
||
|
||
try:
|
||
pro = ProjectRegistration.objects.get(id=id, is_deleted=False)
|
||
except ProjectRegistration.DoesNotExist:
|
||
return Response({'status': 'error', 'message': '立案登记不存在', 'code': 1},
|
||
status=status.HTTP_404_NOT_FOUND)
|
||
|
||
import datetime
|
||
|
||
# 保存原始值用于日志记录
|
||
original_type = pro.type
|
||
original_ContractNo = pro.ContractNo
|
||
try:
|
||
original_responsiblefor = json.loads(pro.responsiblefor) if pro.responsiblefor else {}
|
||
except:
|
||
original_responsiblefor = {}
|
||
original_times = pro.times
|
||
original_charge = pro.charge
|
||
|
||
update_fields_list = []
|
||
|
||
if contract:
|
||
contract = flies(contract)
|
||
# 将文件URL列表转换为字符串存储(如果有多个URL,用逗号分隔)
|
||
if contract:
|
||
contract_str = contract[0] if len(contract) == 1 else ','.join(contract)
|
||
else:
|
||
contract_str = ""
|
||
pro.contract = contract_str
|
||
update_fields_list.append('contract')
|
||
|
||
if times:
|
||
pro.times = times
|
||
update_fields_list.append('times')
|
||
|
||
if responsiblefor:
|
||
# 解析承办人信息(支持字符串JSON或字典)
|
||
try:
|
||
if isinstance(responsiblefor, str):
|
||
responsiblefor_dict = json.loads(responsiblefor)
|
||
else:
|
||
responsiblefor_dict = responsiblefor
|
||
|
||
# 验证承办人(responsible_person)必填
|
||
if not responsiblefor_dict.get('responsible_person'):
|
||
return Response({'status': 'error', 'message': '承办人不能为空', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 将字典转换为JSON字符串存储
|
||
responsiblefor_str = json.dumps(responsiblefor_dict, ensure_ascii=False)
|
||
pro.responsiblefor = responsiblefor_str
|
||
update_fields_list.append('responsiblefor')
|
||
except (json.JSONDecodeError, TypeError, AttributeError):
|
||
return Response({'status': 'error', 'message': '承办人信息格式错误,应为字典格式', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
if charge:
|
||
pro.charge = charge
|
||
update_fields_list.append('charge')
|
||
|
||
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')
|
||
|
||
pro.state = "审核中"
|
||
update_fields_list.append('state')
|
||
|
||
# 记录操作前的数据(original_responsiblefor已经是字典格式)
|
||
old_data = {
|
||
'id': pro.id,
|
||
'contract_no': original_ContractNo,
|
||
'type': original_type,
|
||
'responsiblefor': original_responsiblefor if isinstance(original_responsiblefor, dict) else {},
|
||
'times': original_times,
|
||
'charge': original_charge
|
||
}
|
||
|
||
if update_fields_list:
|
||
pro.save(update_fields=update_fields_list)
|
||
|
||
# 记录操作后的数据
|
||
try:
|
||
current_responsiblefor = json.loads(pro.responsiblefor) if pro.responsiblefor else {}
|
||
except:
|
||
current_responsiblefor = pro.responsiblefor if pro.responsiblefor else {}
|
||
|
||
new_data = {
|
||
'id': pro.id,
|
||
'contract_no': pro.ContractNo,
|
||
'type': pro.type,
|
||
'responsiblefor': current_responsiblefor,
|
||
'times': pro.times,
|
||
'charge': pro.charge
|
||
}
|
||
|
||
today = datetime.datetime.now()
|
||
formatted_date = today.strftime("%Y-%m-%d")
|
||
|
||
# 获取承办人名称用于审批记录
|
||
if responsiblefor:
|
||
current_responsiblefor = responsiblefor_dict.get('responsible_person', '')
|
||
# 构建承办人信息描述
|
||
responsible_desc = current_responsiblefor
|
||
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')}"
|
||
else:
|
||
current_responsiblefor = original_responsiblefor.get('responsible_person', '') if isinstance(original_responsiblefor, dict) else ''
|
||
responsible_desc = current_responsiblefor
|
||
|
||
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 ""
|
||
|
||
from User.utils import create_approval_with_team_logic
|
||
team_name = get_team_name_from_responsiblefor(responsiblefor_dict if responsiblefor else original_responsiblefor)
|
||
# 利益冲突检索(与创建立项一致)
|
||
related_records_info = conflict_search(
|
||
client_info=pro.client_info,
|
||
party_info=pro.party_info,
|
||
exclude_project_id=pro.id
|
||
)
|
||
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.get('ProjectName') or '')[:20] 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 ""
|
||
content = current_responsiblefor + "在" + (
|
||
times or original_times) + "办理立项登记,项目类型:" + original_type + ",合同编号:" + original_ContractNo + "描述:" + ",承办人:" + responsible_desc + ",收费情况:" + (charge or original_charge) + ",申请人:" + applicant_name + conflict_text
|
||
approval, approvers_order_json, needs_approval = create_approval_with_team_logic(
|
||
team_name=team_name,
|
||
approvers=approvers,
|
||
title=current_responsiblefor + "立项登记重新编辑",
|
||
content=content,
|
||
approval_type="立项登记",
|
||
user_id=pro.id,
|
||
business_record=pro,
|
||
today=formatted_date,
|
||
applicant=applicant_name
|
||
)
|
||
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)
|
||
|
||
# 记录操作日志
|
||
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}'
|
||
)
|
||
|
||
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
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)
|
||
|
||
try:
|
||
pro = ProjectRegistration.objects.get(id=id, is_deleted=False)
|
||
except ProjectRegistration.DoesNotExist:
|
||
return Response({'status': 'error', 'message': '立项登记不存在', 'code': 1},
|
||
status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# 检查是否已经被案件管理关联
|
||
case_exists = Case.objects.filter(project_id=pro.id, is_deleted=False).exists()
|
||
|
||
if case_exists:
|
||
return Response({'status': 'error', 'message': '该立项已被案件管理关联,无法删除', 'code': 1},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 记录操作前的数据
|
||
old_data = {
|
||
'id': pro.id,
|
||
'contract_no': pro.ContractNo,
|
||
'type': pro.type,
|
||
'responsiblefor': pro.responsiblefor,
|
||
'client_info': pro.client_info
|
||
}
|
||
|
||
# 软删除:更新 is_deleted 字段
|
||
pro.is_deleted = True
|
||
pro.save()
|
||
|
||
# 记录操作日志
|
||
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}'
|
||
)
|
||
|
||
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class BidRegistration(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
投标登记 - 独立创建,不再关联预立案
|
||
: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')
|
||
approvers = request.data.get('approvers') # 审核人列表(可选,多人团队时需要,推荐:用户ID数组如[1,2,3],兼容:用户名数组)
|
||
# 兼容旧接口:如果传了 personincharge,转换为 approvers
|
||
personincharge = request.data.get('personincharge')
|
||
approvers = normalize_approvers_param(approvers, personincharge)
|
||
|
||
if not all([BiddingUnit, ProjectName, times]):
|
||
return Response({'status': 'error', 'message': '缺少必填参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
import datetime
|
||
|
||
# 获取当前用户信息(用于团队信息及申请人)
|
||
token = request.META.get('token')
|
||
team_name = None
|
||
user = None
|
||
try:
|
||
user = User.objects.get(token=token, is_deleted=False)
|
||
team_name = user.team
|
||
except User.DoesNotExist:
|
||
pass
|
||
|
||
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")
|
||
|
||
# 利益冲突检索(与立项相同逻辑):比对预立案、立项、投标三张表
|
||
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 ""
|
||
|
||
# 使用统一的审核流程函数
|
||
from User.utils import create_approval_with_team_logic
|
||
|
||
applicant_name = user.username if user else ""
|
||
content = f"项目名称:{ProjectName},申请日期:{times},招标单位:{BiddingUnit},申请人:{applicant_name}{conflict_text}"
|
||
|
||
approval, approvers_order_json, needs_approval = create_approval_with_team_logic(
|
||
team_name=team_name,
|
||
approvers=approvers,
|
||
title=ProjectName + "投标登记",
|
||
content=content,
|
||
approval_type="投标登记",
|
||
user_id=bib.id,
|
||
business_record=bib,
|
||
today=formatted_date,
|
||
applicant=applicant_name
|
||
)
|
||
|
||
# 如果返回None且需要审核,说明缺少审核人
|
||
if approval is None and needs_approval:
|
||
return Response({
|
||
'status': 'error',
|
||
'message': build_missing_approvers_message(team_name, approvers),
|
||
'code': 1
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
return Response({'message': '登记成功', 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class BidDetail(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
投标登记展示(列表+搜索)
|
||
支持搜索:project_name 项目名称、client_username 招标单位名字、times/end_time 日期范围、keyword 关键词。
|
||
"""
|
||
page = request.data.get('page')
|
||
per_page = request.data.get('per_page')
|
||
times = request.data.get('times')
|
||
end_time = request.data.get('end_time')
|
||
# 项目名称:兼容 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()
|
||
|
||
if not all([page, per_page]):
|
||
return Response({'status': 'error', 'message': '缺少参数 page 或 per_page', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
Q_obj = Q(is_deleted=False)
|
||
if times and end_time:
|
||
Q_obj &= Q(times__gte=times) & Q(times__lte=end_time)
|
||
if project_name:
|
||
Q_obj &= Q(ProjectName__icontains=project_name)
|
||
if client_username:
|
||
Q_obj &= Q(BiddingUnit__icontains=client_username)
|
||
if keyword:
|
||
Q_obj &= (Q(ProjectName__icontains=keyword) | Q(BiddingUnit__icontains=keyword))
|
||
|
||
qs = Bid.objects.filter(Q_obj).order_by('-id')
|
||
total = qs.count()
|
||
|
||
paginator = Paginator(qs, per_page)
|
||
try:
|
||
user_agents_page = paginator.page(int(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,
|
||
"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)
|
||
|
||
|
||
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')
|
||
approvers = request.data.get('approvers')
|
||
personincharge = request.data.get('personincharge')
|
||
approvers = normalize_approvers_param(approvers, personincharge)
|
||
|
||
if not id:
|
||
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
bid = Bid.objects.get(id=id, is_deleted=False)
|
||
except Bid.DoesNotExist:
|
||
return Response({'status': 'error', 'message': '投标登记不存在', 'code': 1},
|
||
status=status.HTTP_404_NOT_FOUND)
|
||
|
||
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'])
|
||
|
||
if approvers:
|
||
import datetime
|
||
today = datetime.datetime.now()
|
||
formatted_date = today.strftime("%Y-%m-%d")
|
||
team_name = None
|
||
edit_user = None
|
||
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
|
||
applicant_name = edit_user.username if edit_user else ""
|
||
# 利益冲突检索(与创建投标一致,按招标单位)
|
||
conflict_result = conflict_search(bidding_unit=bid.BiddingUnit, exclude_bid_id=bid.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 ""
|
||
content_edit = f"项目名称:{bid.ProjectName},申请日期:{times or bid.times},申请人:{applicant_name}{conflict_text}"
|
||
approval, approvers_order_json, needs_approval = create_approval_with_team_logic(
|
||
team_name=team_name,
|
||
approvers=approvers,
|
||
title=bid.ProjectName + "投标登记重新编辑",
|
||
content=content_edit,
|
||
approval_type="投标登记",
|
||
user_id=bid.id,
|
||
business_record=bid,
|
||
today=formatted_date,
|
||
applicant=applicant_name
|
||
)
|
||
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)
|
||
|
||
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)
|
||
|
||
try:
|
||
bid = Bid.objects.get(id=id, is_deleted=False)
|
||
except Bid.DoesNotExist:
|
||
return Response({'status': 'error', 'message': '投标登记不存在', 'code': 1},
|
||
status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# 记录操作前的数据
|
||
old_data = {
|
||
'id': bid.id,
|
||
'project_name': bid.ProjectName,
|
||
'times': bid.times
|
||
}
|
||
|
||
# 软删除:更新 is_deleted 字段
|
||
bid.is_deleted = True
|
||
bid.save()
|
||
|
||
# 记录操作日志
|
||
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}'
|
||
)
|
||
|
||
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class caseManagement(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
补充案件资料(立项审批通过即成案件,本接口用于创建/补充案件材料)
|
||
- 立项审批通过后,用本接口补充合同返还、结案申请等资料。
|
||
- 首次调用(该立项尚无案件):创建案件,立案时间 = 立项审批通过时间(无则用请求 times 或当前日期)。
|
||
- 再次调用(该立项已有案件):仅更新/补充材料,不修改立案时间。
|
||
:param request:
|
||
:param args:
|
||
:param kwargs:
|
||
:return:
|
||
"""
|
||
project_id = request.data.get('project_id') # 立项登记ID
|
||
times = request.data.get('times') # 立案时间(仅当无审批通过时间时使用)
|
||
Contractreturn = request.FILES.getlist('Contractreturn')
|
||
Closingapplication = request.FILES.getlist('Closingapplication')
|
||
invoice_status = request.data.get('invoice_status') # 已开票
|
||
paymentcollection = request.data.get('paymentcollection')
|
||
tag_ids = request.data.get('tag_ids') # 标签ID列表(数组或逗号分隔的字符串)
|
||
|
||
approvers = request.data.get('approvers')
|
||
personincharge = request.data.get('personincharge')
|
||
approvers = normalize_approvers_param(approvers, personincharge)
|
||
|
||
# 检查是否已立项
|
||
if not project_id:
|
||
return Response({'status': 'error', 'message': '缺少参数project_id(立项登记ID)', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 使用事务和行级锁防止并发重复创建
|
||
try:
|
||
with transaction.atomic():
|
||
# 使用 select_for_update 锁定立项登记记录,防止并发问题
|
||
project_registration = ProjectRegistration.objects.select_for_update().get(id=project_id, is_deleted=False)
|
||
|
||
# 再次检查是否已经存在案件(在锁内检查,防止并发重复创建)
|
||
case = Case.objects.filter(project_id=project_id, is_deleted=False).first()
|
||
if not case:
|
||
# 立案时间:优先取立项审批通过时间,否则用请求 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')
|
||
|
||
# 处理材料:上传后存储URL列表(代理合同字段已从案件管理移除,创建时存空)
|
||
contract_return_list = flies(Contractreturn)
|
||
closing_application_list = flies(Closingapplication)
|
||
|
||
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
|
||
# 创建新案件(立案时间 = 审批通过时间)
|
||
case_id = Case.objects.create(
|
||
project_id=project_id,
|
||
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,
|
||
times=filing_time,
|
||
AgencyContract=json.dumps([], ensure_ascii=False), # 代理合同已从案件管理移除,库字段保留
|
||
Contractreturn=json.dumps(contract_return_list, ensure_ascii=False),
|
||
Closingapplication=json.dumps(closing_application_list, ensure_ascii=False),
|
||
ChangeRequest="",
|
||
invoice_status=normalize_amount_value(invoice_status),
|
||
paymentcollection=normalize_amount_value(paymentcollection),
|
||
state="审核中"
|
||
)
|
||
|
||
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="案件管理信息提交",
|
||
content=build_case_approval_content(project_registration, filing_time),
|
||
approval_type="案件管理",
|
||
user_id=case_id.id,
|
||
business_record=case_id,
|
||
today=filing_time
|
||
)
|
||
if approval is None and needs_approval:
|
||
return Response({
|
||
'status': 'error',
|
||
'message': build_missing_approvers_message(team_name, approvers),
|
||
'code': 1
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 处理标签关联
|
||
if 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
|
||
|
||
# 创建成功,直接返回
|
||
return Response({'message': '创建成功', 'code': 0}, status=status.HTTP_200_OK)
|
||
# 如果已存在,事务结束,在事务外处理更新逻辑
|
||
|
||
except ProjectRegistration.DoesNotExist:
|
||
return Response({'status': 'error', 'message': '该立项登记不存在或已删除,请先完成立项登记', 'code': 1},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 如果代码执行到这里,说明案件已存在,需要执行更新逻辑
|
||
# 注意:这里不需要再次锁定,因为更新操作不会造成重复创建问题
|
||
case = Case.objects.filter(project_id=project_id, is_deleted=False).first()
|
||
if not case:
|
||
# 这种情况理论上不应该发生,因为事务内已检查,但为了安全起见
|
||
return Response({'status': 'error', 'message': '案件不存在', 'code': 1},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 执行更新逻辑(代理合同已从案件管理移除,不再接收或更新)
|
||
update_fields_list = []
|
||
if times:
|
||
case.times = times
|
||
update_fields_list.append('times')
|
||
|
||
if Contractreturn:
|
||
case.Contractreturn = json.dumps(flies(Contractreturn), ensure_ascii=False)
|
||
update_fields_list.append('Contractreturn')
|
||
|
||
if Closingapplication:
|
||
case.Closingapplication = json.dumps(flies(Closingapplication), ensure_ascii=False)
|
||
update_fields_list.append('Closingapplication')
|
||
|
||
if paymentcollection is not None:
|
||
case.paymentcollection = normalize_amount_value(paymentcollection)
|
||
update_fields_list.append('paymentcollection')
|
||
|
||
if invoice_status is not None:
|
||
case.invoice_status = normalize_amount_value(invoice_status)
|
||
update_fields_list.append('invoice_status')
|
||
|
||
if update_fields_list:
|
||
case.save(update_fields=update_fields_list)
|
||
|
||
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class CaseAttachmentUpload(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
案件材料上传(合同返还/结案申请)
|
||
通过 type 指定上传类型,返回文件URL列表
|
||
"""
|
||
upload_type = request.data.get('type')
|
||
files = request.FILES.getlist('file') or request.FILES.getlist('files')
|
||
|
||
allowed_types = ["Contractreturn", "Closingapplication"]
|
||
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)
|
||
|
||
|
||
class CaseAttachmentUpdate(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
案件材料更新(合同返还/结案申请)
|
||
通过 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')
|
||
approvers = request.data.get('approvers')
|
||
personincharge = request.data.get('personincharge')
|
||
approvers = normalize_approvers_param(approvers, personincharge)
|
||
|
||
if not case_id:
|
||
return Response({'status': 'error', 'message': '缺少参数case_id', 'code': 1},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
allowed_types = ["Contractreturn", "Closingapplication","AgencyContract"]
|
||
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)
|
||
|
||
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 = []
|
||
|
||
if upload_type == "Contractreturn":
|
||
case.Contractreturn = Contractreturn
|
||
update_fields = ['Contractreturn']
|
||
elif upload_type == "Closingapplication":
|
||
# 结案申请需要审批通过后才更新 Case.Closingapplication 字段
|
||
# 这里不直接更新,文件URL保存在 Schedule.remark 中,审批通过后再更新到 Case
|
||
pass
|
||
elif upload_type == "AgencyContract":
|
||
case.AgencyContract = json.dumps(file_urls, ensure_ascii=False)
|
||
update_fields = ['AgencyContract']
|
||
|
||
if update_fields:
|
||
case.save(update_fields=update_fields)
|
||
|
||
# 结案申请:加入审批流程(独立于“案件管理”审批,避免覆盖 Case.approvers_order / Case.state)
|
||
if upload_type == "Closingapplication": # 审批通过后才更新 Case.Closingapplication 字段
|
||
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,
|
||
today=today,
|
||
applicant=submitter.username if submitter else None # 传入申请人,用于"待查看"流程
|
||
)
|
||
|
||
if approval is None and needs_approval:
|
||
return Response({
|
||
'status': 'error',
|
||
'message': build_missing_approvers_message(team_name, approvers),
|
||
'code': 1
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
except Exception as e:
|
||
# 不影响材料更新主流程,但记录错误信息
|
||
import logging
|
||
logger = logging.getLogger(__name__)
|
||
logger.error(f"结案申请审批创建失败(case_id={case.id}): {str(e)}")
|
||
|
||
return Response({
|
||
'message': '更新成功',
|
||
'code': 0,
|
||
'data': {
|
||
'case_id': case.id,
|
||
'type': upload_type,
|
||
'files': file_urls
|
||
}
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class caseManagementDetail(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
案件管理版块展示
|
||
搜索支持按标签:传 tags(标签名称,模糊匹配)或 tag_ids(标签ID列表/逗号分隔,匹配拥有任一标签的案件),可同时使用。
|
||
:param request: page, per_page 必填;可选 times, end_time, type, contract_no, party_name, client_name, tags, tag_ids
|
||
: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')
|
||
tags = request.data.get('tags') # 标签:传字符串为名称模糊匹配,传数组 [2,3] 为标签ID列表
|
||
tag_ids = request.data.get('tag_ids') # 标签ID列表(与 tags 传 ID 时等效)
|
||
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:
|
||
por_id = ProjectRegistration.objects.filter(type=type, is_deleted=False).values_list('id', flat=True)
|
||
Q_obj &= Q(project_id__in=por_id)
|
||
|
||
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)
|
||
# 按标签搜索:tags 可为字符串(名称模糊)、数组(标签ID)、或 form 传来的字符串 "[2]";tag_ids 为标签ID列表;满足任一标签即匹配
|
||
tag_id_list = []
|
||
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):
|
||
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)
|
||
)
|
||
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
|
||
tag_id_list = list(set(tag_id_list))
|
||
if tag_id_list:
|
||
Q_obj &= Q(tags__in=tag_id_list)
|
||
|
||
pre = Case.objects.filter(Q_obj, is_deleted=False).order_by('-id')
|
||
if tag_id_list:
|
||
pre = pre.distinct()
|
||
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)
|
||
|
||
# 导入财务模型,用于统计开票金额和收入确认金额
|
||
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)
|
||
|
||
# 批量查询收入确认金额(按合同号统计,只统计审核通过的)
|
||
income_amounts = {} # 合同号 -> 总收入确认金额
|
||
if contract_nos:
|
||
# 查询所有未删除且审核通过的收入确认记录(一次性查询,避免N+1问题)
|
||
# 只统计 state="已通过" 的记录
|
||
incomes = Income.objects.filter(
|
||
ContractNo__in=contract_nos,
|
||
is_deleted=False,
|
||
state="已通过" # 只统计审核通过的收入确认
|
||
).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)
|
||
|
||
data = []
|
||
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)
|
||
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)
|
||
|
||
# 解析负责人信息(JSON字符串转字典)
|
||
try:
|
||
responsiblefor_dict = json.loads(responsiblefor_raw) if responsiblefor_raw else {}
|
||
except (json.JSONDecodeError, TypeError):
|
||
responsiblefor_dict = responsiblefor_raw if responsiblefor_raw else {}
|
||
|
||
# 解析结案申请(JSON字符串转列表);代理合同已从案件管理移除,不再返回
|
||
try:
|
||
closing_application_list = json.loads(info.Closingapplication) if info.Closingapplication else []
|
||
except (json.JSONDecodeError, TypeError):
|
||
closing_application_list = []
|
||
|
||
if not closing_application_list:
|
||
closing_application_list = ""
|
||
|
||
contractreturn_str = info.Contractreturn or ""
|
||
if contractreturn_str.strip() in ["[]", ""]:
|
||
contractreturn_str = ""
|
||
|
||
closing_application_str = info.Closingapplication or ""
|
||
if closing_application_str.strip() in ["[]", ""]:
|
||
closing_application_str = ""
|
||
|
||
# 从财务数据动态获取开票金额和收入确认金额
|
||
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)
|
||
|
||
# 获取案件标签
|
||
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
|
||
})
|
||
|
||
# 如果没有标签,返回空字符串而不是空数组
|
||
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 = ""
|
||
|
||
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 or "", # 项目简述(没有数据时返回空字符串)
|
||
"responsiblefor": responsiblefor_value, # 负责人信息(字典格式,没有数据时返回空字符串)
|
||
"charge": charge or "", # 收费情况(没有数据时返回空字符串)
|
||
'times': info.times or "", # 立案时间(没有数据时返回空字符串)
|
||
"Contractreturn": contractreturn_str, # 合同返还(没有数据时返回空字符串)
|
||
"Closingapplication": closing_application_str, # 结案申请(JSON字符串,没有文件时返回空字符串)
|
||
"ClosingapplicationUrls": closing_application_list if closing_application_list else "", # 结案申请(URL列表,没有文件时返回空字符串)
|
||
"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
|
||
})
|
||
|
||
return Response({'message': '展示成功', "total": total, 'data': data, 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
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')
|
||
Contractreturn = request.FILES.getlist('Contractreturn')
|
||
Closingapplication = request.FILES.getlist('Closingapplication')
|
||
invoice_status = request.data.get('invoice_status') # 已开票
|
||
paymentcollection = request.data.get('paymentcollection')
|
||
tag_ids = request.data.get('tag_ids') # 标签ID列表(数组或逗号分隔的字符串)
|
||
approvers = request.data.get('approvers')
|
||
personincharge = request.data.get('personincharge')
|
||
approvers = normalize_approvers_param(approvers, personincharge)
|
||
|
||
# 检查不可修改的参数(前端不会传入,但作为安全措施进行检查)
|
||
forbidden_params = []
|
||
if 'project_id' in request.data:
|
||
forbidden_params.append('project_id')
|
||
|
||
if forbidden_params:
|
||
return Response({
|
||
'status': 'error',
|
||
'message': f'以下参数不允许修改: {", ".join(forbidden_params)}',
|
||
'code': 1
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
if not id:
|
||
return Response({'status': 'error', 'message': '缺少参数id', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
case = Case.objects.get(id=id, is_deleted=False)
|
||
except Case.DoesNotExist:
|
||
return Response({'status': 'error', 'message': '案件管理不存在', 'code': 1},
|
||
status=status.HTTP_404_NOT_FOUND)
|
||
|
||
update_fields_list = []
|
||
|
||
if times:
|
||
case.times = times
|
||
update_fields_list.append('times')
|
||
|
||
if Contractreturn:
|
||
case.Contractreturn = json.dumps(flies(Contractreturn), ensure_ascii=False)
|
||
update_fields_list.append('Contractreturn')
|
||
|
||
if Closingapplication:
|
||
case.Closingapplication = json.dumps(flies(Closingapplication), ensure_ascii=False)
|
||
update_fields_list.append('Closingapplication')
|
||
|
||
if paymentcollection is not None:
|
||
case.paymentcollection = normalize_amount_value(paymentcollection)
|
||
update_fields_list.append('paymentcollection')
|
||
|
||
if invoice_status is not None:
|
||
case.invoice_status = normalize_amount_value(invoice_status)
|
||
update_fields_list.append('invoice_status')
|
||
|
||
if update_fields_list:
|
||
case.save(update_fields=update_fields_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 = []
|
||
|
||
# 验证标签是否存在并更新关联
|
||
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
|
||
|
||
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
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)
|
||
|
||
try:
|
||
case = Case.objects.get(id=id, is_deleted=False)
|
||
except Case.DoesNotExist:
|
||
return Response({'status': 'error', 'message': '案件管理不存在', 'code': 1},
|
||
status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# 记录操作前的数据
|
||
old_data = {
|
||
'id': case.id,
|
||
'times': case.times,
|
||
'state': case.state
|
||
}
|
||
|
||
# 软删除:更新 is_deleted 字段
|
||
case.is_deleted = True
|
||
case.save()
|
||
|
||
# 记录操作日志
|
||
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}'
|
||
)
|
||
|
||
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class Uploadinvoice(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""案件管理发票 - 关联案件"""
|
||
case_id = request.data.get('case_id') # 案件ID
|
||
amount = request.data.get('amount')
|
||
file = request.FILES.getlist('file')
|
||
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)
|
||
|
||
Invoice.objects.create(
|
||
case_id=case_id,
|
||
amount=amount,
|
||
file=json.dumps(flies(file)),
|
||
)
|
||
return Response({'message': '上传成功', 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class InvoiceDetail(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
发票展示 - 关联案件
|
||
:param request:
|
||
:param args:
|
||
:param kwargs:
|
||
:return:
|
||
"""
|
||
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)
|
||
|
||
invoices = Invoice.objects.filter(case_id=case_id, is_deleted=False)
|
||
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)
|
||
|
||
|
||
class Log(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
案件日志 - 关联案件
|
||
"""
|
||
token = request.META.get('token')
|
||
# 明确拒绝 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)
|
||
|
||
case_id = request.data.get('case_id') # 案件ID
|
||
content = request.data.get('content')
|
||
file = request.FILES.getlist('file')
|
||
|
||
if not all([case_id, content]):
|
||
return Response({'status': 'error', 'message': '缺少参数case_id或content', '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)
|
||
|
||
user = User.objects.get(token=token, is_deleted=False)
|
||
now = datetime.now()
|
||
date_str = now.strftime('%Y-%m-%d')
|
||
Caselog.objects.create(
|
||
case_id=case_id,
|
||
content=content,
|
||
file=json.dumps(flies(file)),
|
||
times=date_str,
|
||
username=user.username,
|
||
)
|
||
return Response({'message': '添加成功', 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class LogDetail(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
案件日志展示(按案件ID查询)
|
||
必填参数:case_id(案件ID)、page、per_page
|
||
"""
|
||
page = request.data.get('page')
|
||
per_page = request.data.get('per_page')
|
||
case_id = request.data.get('case_id') # 案件ID(必填)
|
||
if not all([page, per_page]):
|
||
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)
|
||
|
||
# 根据案件ID过滤日志
|
||
pre = Caselog.objects.filter(is_deleted=False, case_id=case_id)
|
||
total = pre.count()
|
||
|
||
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,
|
||
"case_id": info.case_id if info.case else None, # 案件ID
|
||
"content": info.content, # 日志内容
|
||
"times": info.times, # 时间
|
||
"username": info.username, # 提交人(保留兼容性)
|
||
"recorder": info.username, # 记录人
|
||
"file": info.file, # 文件
|
||
})
|
||
|
||
return Response({'message': '展示成功', "total": total, 'data': data, 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class accumulate(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
累加 - 关联案件
|
||
:param request:
|
||
:param args:
|
||
:param kwargs:
|
||
:return:
|
||
"""
|
||
case_id = request.data.get('case_id') # 案件ID
|
||
paymentcollection = request.data.get('paymentcollection')
|
||
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)
|
||
|
||
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)
|
||
case.save(update_fields=['paymentcollection'])
|
||
|
||
return Response({'message': '成功', 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class preFilingLinkedCases(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
立项登记列表接口(用于新增案件)
|
||
权限:管理员可见全部;承办人仅可见自己承办的立项。
|
||
返回可以用于新增案件的立项登记列表,排除已有案件的立项登记
|
||
:param request:
|
||
:param args:
|
||
:param kwargs:
|
||
:return:
|
||
"""
|
||
pre_qs, err = _get_project_registration_queryset_by_permission(request)
|
||
if err is not None:
|
||
return err
|
||
case_project_ids = Case.objects.filter(is_deleted=False).values_list('project_id', flat=True)
|
||
projects = pre_qs.exclude(id__in=case_project_ids)
|
||
|
||
data = []
|
||
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,
|
||
})
|
||
|
||
return Response({'message': '展示成功', 'data': data, 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class ProjectDropdownList(APIView):
|
||
"""获取成功立项的下拉列表接口"""
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
获取已通过审核的立项登记列表(用于下拉选择)
|
||
权限:管理员可见全部;承办人仅可见自己承办的立项。
|
||
返回成功立项的数据,格式简洁,适合下拉列表使用
|
||
:param request:
|
||
:param args:
|
||
:param kwargs:
|
||
:return:
|
||
"""
|
||
# 权限过滤:管理员全部,承办人只看自己承办的
|
||
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')
|
||
|
||
# 可选参数:是否排除已有案件的立项
|
||
exclude_has_case = request.data.get('exclude_has_case', False)
|
||
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:
|
||
# 解析承办人信息(原 responsible_person 即承办人)
|
||
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,
|
||
"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,
|
||
})
|
||
|
||
return Response({
|
||
'message': '查询成功',
|
||
'data': data,
|
||
'total': len(data),
|
||
'code': 0
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class CaseDropdownList(APIView):
|
||
"""获取案件的下拉列表接口"""
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
获取案件列表(用于下拉选择)
|
||
返回案件数据,格式简洁,适合下拉列表使用。
|
||
可选按标签筛选:传 tags(字符串为名称模糊,数组如 [2,3] 为标签ID)或 tag_ids,匹配拥有任一标签的案件。
|
||
:param request: 可选 state, tags, tag_ids
|
||
:return:
|
||
"""
|
||
state_filter = request.data.get('state', None)
|
||
tags = request.data.get('tags') # 字符串=名称模糊,数组=标签ID如 [2,3]
|
||
tag_ids = request.data.get('tag_ids')
|
||
|
||
Q_obj = Q(is_deleted=False)
|
||
if state_filter:
|
||
Q_obj &= Q(state=state_filter)
|
||
|
||
tag_id_list = []
|
||
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):
|
||
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)
|
||
)
|
||
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
|
||
tag_id_list = list(set(tag_id_list))
|
||
if tag_id_list:
|
||
Q_obj &= Q(tags__in=tag_id_list)
|
||
|
||
cases = Case.objects.filter(Q_obj).order_by('-id')
|
||
if tag_id_list:
|
||
cases = cases.distinct()
|
||
|
||
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)
|
||
|
||
|
||
class Application(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
申请用印
|
||
:param request:
|
||
:param args:
|
||
:param kwargs:
|
||
:return:
|
||
"""
|
||
token = request.META.get('token')
|
||
Printingpurpose = request.data.get('Printingpurpose')
|
||
# 案件编号(前端可不传),后端自动同步为“合同号”
|
||
# 兼容:仍支持直接传 CaseNumber(即合同号)
|
||
CaseNumber = request.data.get('CaseNumber')
|
||
case_id = request.data.get('case_id')
|
||
project_id = request.data.get('project_id')
|
||
Reason = request.data.get('Reason')
|
||
seal_number = request.data.get('seal_number')
|
||
seal_type = request.data.get('seal_type')
|
||
file = request.FILES.getlist('file')
|
||
approvers = request.data.get('approvers')
|
||
personincharge = request.data.get('personincharge')
|
||
approvers = normalize_approvers_param(approvers, personincharge)
|
||
|
||
if not all([Printingpurpose, Reason, seal_number, seal_type, file]):
|
||
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
|
||
now = datetime.now()
|
||
date_str = now.strftime('%Y-%m-%d')
|
||
user = User.objects.get(token=token, is_deleted=False)
|
||
|
||
# 自动查询合同号写入 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)
|
||
|
||
sea = SealApplication.objects.create(
|
||
Printingpurpose=Printingpurpose,
|
||
CaseNumber=contract_no,
|
||
Reason=Reason,
|
||
seal_number=seal_number,
|
||
seal_type=seal_type,
|
||
file=json.dumps(flies(file)),
|
||
times=date_str,
|
||
state="审核中",
|
||
username=user.username,
|
||
)
|
||
|
||
team_name = user.team
|
||
from User.utils import create_approval_with_team_logic
|
||
approval, approvers_order_json, needs_approval = create_approval_with_team_logic(
|
||
team_name=team_name,
|
||
approvers=approvers,
|
||
title=user.username + "申请用印",
|
||
content=user.username + "在" + date_str + "申请用印,合同编号:" + str(contract_no) + ",用印事由:" + Reason + ",盖章份数:" + seal_number + "盖着类型:" + seal_type,
|
||
approval_type="申请用印",
|
||
user_id=sea.id,
|
||
business_record=sea,
|
||
today=date_str
|
||
)
|
||
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)
|
||
return Response({'message': '添加成功', 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class ApplicationDetail(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
申请用印展示
|
||
:param request:
|
||
:param args:
|
||
:param kwargs:
|
||
:return:
|
||
"""
|
||
page = request.data.get('page')
|
||
per_page = request.data.get('per_page')
|
||
times = request.data.get('times')
|
||
end_time = request.data.get('end_time')
|
||
seal_type = request.data.get('seal_type')
|
||
CaseNumber = request.data.get('CaseNumber')
|
||
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)
|
||
|
||
seas = SealApplication.objects.filter(Q_obj, is_deleted=False).order_by('-id')
|
||
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)
|
||
|
||
|
||
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')
|
||
# 案件编号(合同号):支持前端直接传,也支持通过 case_id/project_id 自动同步
|
||
CaseNumber = request.data.get('CaseNumber')
|
||
case_id = request.data.get('case_id')
|
||
project_id = request.data.get('project_id')
|
||
Reason = request.data.get('Reason')
|
||
seal_number = request.data.get('seal_number')
|
||
seal_type = request.data.get('seal_type')
|
||
file = request.FILES.getlist('file')
|
||
approvers = request.data.get('approvers')
|
||
personincharge = request.data.get('personincharge')
|
||
approvers = normalize_approvers_param(approvers, personincharge)
|
||
|
||
if not id:
|
||
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
app = SealApplication.objects.get(id=id, is_deleted=False)
|
||
except SealApplication.DoesNotExist:
|
||
return Response({'status': 'error', 'message': '申请用印不存在', 'code': 1},
|
||
status=status.HTTP_404_NOT_FOUND)
|
||
|
||
if Printingpurpose:
|
||
app.Printingpurpose = Printingpurpose
|
||
app.save(update_fields=['Printingpurpose'])
|
||
# 优先通过 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
|
||
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'])
|
||
|
||
if approvers:
|
||
now = datetime.now()
|
||
date_str = now.strftime('%Y-%m-%d')
|
||
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,
|
||
title=app.username + "申请用印重新编辑",
|
||
content=app.username + "在" + date_str + "申请用印,合同编号:" + str(contract_no or app.CaseNumber) + ",用印事由:" + (
|
||
Reason or app.Reason) + ",盖章份数:" + (seal_number or app.seal_number) + "盖着类型:" + (
|
||
seal_type or app.seal_type),
|
||
approval_type="申请用印",
|
||
user_id=app.id,
|
||
business_record=app,
|
||
today=date_str
|
||
)
|
||
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)
|
||
|
||
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)
|
||
|
||
try:
|
||
app = SealApplication.objects.get(id=id, is_deleted=False)
|
||
except SealApplication.DoesNotExist:
|
||
return Response({'status': 'error', 'message': '申请用印不存在', 'code': 1},
|
||
status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# 软删除:更新 is_deleted 字段
|
||
app.is_deleted = True
|
||
app.save()
|
||
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class WarehousingRegistration(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
入库登记
|
||
: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)
|
||
|
||
|
||
class WarehousingDetail(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
入库登记展示
|
||
:param request:
|
||
:param args:
|
||
:param kwargs:
|
||
:return:
|
||
"""
|
||
page = request.data.get('page')
|
||
per_page = request.data.get('per_page')
|
||
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)
|
||
|
||
seas = Warehousing.objects.filter(Q_obj, is_deleted=False).order_by('-id')
|
||
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)
|
||
|
||
|
||
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')
|
||
|
||
if not id:
|
||
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
ware = Warehousing.objects.get(id=id, is_deleted=False)
|
||
except Warehousing.DoesNotExist:
|
||
return Response({'status': 'error', 'message': '入库登记不存在', 'code': 1},
|
||
status=status.HTTP_404_NOT_FOUND)
|
||
|
||
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'])
|
||
|
||
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)
|
||
|
||
try:
|
||
ware = Warehousing.objects.get(id=id, is_deleted=False)
|
||
except Warehousing.DoesNotExist:
|
||
return Response({'status': 'error', 'message': '入库登记不存在', 'code': 1},
|
||
status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# 软删除:更新 is_deleted 字段
|
||
ware.is_deleted = True
|
||
ware.save()
|
||
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class PlatformRegistration(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
注册平台登记
|
||
: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)
|
||
|
||
|
||
class PlatformDetail(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
注册平台登记展示
|
||
:param request:
|
||
:param args:
|
||
:param kwargs:
|
||
:return:
|
||
"""
|
||
page = request.data.get('page')
|
||
per_page = request.data.get('per_page')
|
||
seas = RegisterPlatform.objects.filter(is_deleted=False).order_by('-id')
|
||
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)
|
||
|
||
|
||
class EditPlatformDetail(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
注册平台登记编辑
|
||
: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')
|
||
|
||
if not all([platform, number, password, username, ID]):
|
||
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
|
||
reg = RegisterPlatform.objects.get(id=ID, is_deleted=False)
|
||
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)
|
||
|
||
|
||
class DeletePlatformDetail(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
删除注册平台登记
|
||
:param request:
|
||
:param args:
|
||
:param kwargs:
|
||
:return:
|
||
"""
|
||
ID = request.data.get('id')
|
||
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:
|
||
return Response({'status': 'error', 'message': '注册平台登记不存在', 'code': 1},
|
||
status=status.HTTP_404_NOT_FOUND)
|
||
|
||
|
||
class bulletin(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
公告
|
||
:param request:
|
||
:param args:
|
||
:param kwargs:
|
||
:return:
|
||
"""
|
||
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
|
||
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)
|
||
|
||
|
||
class BulletinDetail(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
公告展示
|
||
:param request:
|
||
:param args:
|
||
:param kwargs:
|
||
:return:
|
||
"""
|
||
page = request.data.get('page')
|
||
per_page = request.data.get('per_page')
|
||
seas = Announcement.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:
|
||
# 解析 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)
|
||
data.append({
|
||
"id": info.id,
|
||
"title": info.title,
|
||
"content": info.content,
|
||
"times": info.times,
|
||
"file": file_result,#file_result,
|
||
"username": info.username,
|
||
"state": info.state,
|
||
})
|
||
|
||
return Response({'message': '展示成功', "total": total, 'data': data, 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
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')
|
||
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:
|
||
ann = Announcement.objects.get(id=ID, is_deleted=False)
|
||
except Announcement.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
|
||
ann.state = bol
|
||
ann.save(update_fields=['state'])
|
||
|
||
# 获取用户信息(如果 token 存在)
|
||
if token:
|
||
try:
|
||
user = User.objects.get(token=token, is_deleted=False).username
|
||
except User.DoesNotExist:
|
||
# 如果用户不存在,使用原有用户名
|
||
user = ann.username
|
||
else:
|
||
# 如果 token 不存在,使用原有用户名
|
||
user = ann.username
|
||
|
||
if title:
|
||
ann.title = title
|
||
ann.save(update_fields=['title'])
|
||
if content:
|
||
ann.content = content
|
||
ann.save(update_fields=['content'])
|
||
if file:
|
||
ann.file = json.dumps(flies(file)) # 使用 flies 函数处理文件
|
||
ann.save(update_fields=['file'])
|
||
|
||
ann.username = user
|
||
ann.times = date_str
|
||
|
||
ann.save(update_fields=['username', 'times'])
|
||
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class deleteBulletin(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
ID = request.data.get('id')
|
||
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)
|
||
|
||
|
||
class Lawyersdocuments(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
token = request.META.get('token')
|
||
file = request.FILES.getlist('file')
|
||
filename = file[0].name
|
||
name_without_extension, extension = os.path.splitext(filename)
|
||
user = User.objects.get(token=token, is_deleted=False).username
|
||
title = name_without_extension + extension
|
||
if not all([file]):
|
||
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,
|
||
remark=user,
|
||
file=json.dumps(flies(file)),
|
||
times=date_str,
|
||
)
|
||
return Response({'message': '文件上传成功', 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class LawyersdocumentsDetail(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
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')
|
||
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):
|
||
def post(self, request, *args, **kwargs):
|
||
id = request.data.get('id')
|
||
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:
|
||
return Response({'status': 'error', 'message': '律师文件不存在', 'code': 1},
|
||
status=status.HTTP_404_NOT_FOUND)
|
||
|
||
|
||
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')
|
||
|
||
if not id:
|
||
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
law = LawyerFlie.objects.get(id=id, is_deleted=False)
|
||
except LawyerFlie.DoesNotExist:
|
||
return Response({'status': 'error', 'message': '律师文件不存在', 'code': 1},
|
||
status=status.HTTP_404_NOT_FOUND)
|
||
|
||
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'])
|
||
|
||
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class CreateSchedule(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
待办创建
|
||
|
||
: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')
|
||
approvers = request.data.get('approvers') # 审核人列表(团队类型时需要,推荐:用户ID数组如[1,2,3],兼容:用户名数组)
|
||
personincharge = request.data.get('personincharge')
|
||
approvers = normalize_approvers_param(approvers, personincharge)
|
||
|
||
if not all([title, tiems, end_time]):
|
||
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 获取当前用户
|
||
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)
|
||
|
||
Schedule.objects.create(
|
||
title=title,
|
||
tiems=tiems,
|
||
end_time=end_time,
|
||
remark=remark,
|
||
state="未完成",
|
||
submit=user.username # 记录提交人
|
||
)
|
||
return Response({'message': '添加成功', 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class DeleteSchedule(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
删除日程
|
||
:param request:
|
||
:param args:
|
||
:param kwargs:
|
||
:return:
|
||
"""
|
||
id = request.data.get('id')
|
||
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)
|
||
|
||
|
||
class ScheduleDetail(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
日程展示(仅展示用户主动创建的日程/待办,不包含结案申请等审批承载的 Schedule)
|
||
:param request:
|
||
:param args:
|
||
:param kwargs:
|
||
:return:
|
||
"""
|
||
title = request.data.get('title')
|
||
obj = Q(is_deleted=False)
|
||
if title:
|
||
obj &= Q(title__contains=title)
|
||
# 排除作为「结案申请」审批承载的 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)
|
||
sches = Schedule.objects.filter(obj).order_by('-id')
|
||
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)
|
||
|
||
|
||
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')
|
||
|
||
if not id:
|
||
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
schedule = Schedule.objects.get(id=id, is_deleted=False)
|
||
except Schedule.DoesNotExist:
|
||
return Response({'status': 'error', 'message': '日程不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
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'])
|
||
|
||
return Response({'message': '编辑成功', 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class handleSchedule(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
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)
|
||
try:
|
||
sc = Schedule.objects.get(id=id, is_deleted=False)
|
||
sc.state = "已完成"
|
||
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)
|
||
|
||
|
||
class AddRermission(APIView):
|
||
"""
|
||
添加权限
|
||
"""
|
||
|
||
def post(self, request, *args, **kwargs):
|
||
permission_name = request.data.get('permission_name')
|
||
permission_logo = request.data.get('permission_logo')
|
||
parent = request.data.get('parent')
|
||
if not all([permission_name, permission_logo, parent]):
|
||
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)
|
||
|
||
|
||
class DisplayRermission(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
权限列表展示
|
||
"""
|
||
permissions = permission.objects.filter(is_deleted=False)
|
||
|
||
# 先将数据转换为字典格式
|
||
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)
|
||
|
||
|
||
class DeleteRermission(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
删除权限
|
||
"""
|
||
ID = request.data.get('id')
|
||
try:
|
||
perm = permission.objects.get(id=ID, is_deleted=False)
|
||
# 记录操作前的数据
|
||
old_data = {
|
||
'id': perm.id,
|
||
'permission_name': perm.permission_name,
|
||
'permission_logo': perm.permission_logo
|
||
}
|
||
# 软删除:更新 is_deleted 字段
|
||
perm.is_deleted = True
|
||
perm.save()
|
||
# 记录操作日志
|
||
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}'
|
||
)
|
||
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)
|
||
|
||
|
||
class EditRermission(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
编辑权限
|
||
"""
|
||
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)
|
||
|
||
|
||
class addRole(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
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)
|
||
|
||
# 检查角色名是否已存在
|
||
if role.objects.filter(RoleName=RoleName).exists():
|
||
return Response({'status': 'error', 'message': '角色名已存在,不能重复', 'code': 1},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
role.objects.create(RoleName=RoleName, remark=remark)
|
||
return Response({'message': '添加成功', 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class DeleteRole(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
id = request.data.get('id')
|
||
try:
|
||
r = role.objects.get(id=id, is_deleted=False)
|
||
# 记录操作前的数据
|
||
old_data = {
|
||
'id': r.id,
|
||
'role_name': r.RoleName,
|
||
'permission_id': r.permissionId
|
||
}
|
||
# 软删除:更新 is_deleted 字段
|
||
r.is_deleted = True
|
||
r.save()
|
||
# 记录操作日志
|
||
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}'
|
||
)
|
||
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)
|
||
|
||
|
||
class EditRole(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
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:
|
||
# 检查角色名是否已被其他角色使用(排除当前角色)
|
||
if role.objects.filter(RoleName=RoleName).exclude(id=id).exists():
|
||
return Response({'status': 'error', 'message': '角色名已存在,不能重复', 'code': 1},
|
||
status=status.HTTP_400_BAD_REQUEST)
|
||
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)
|
||
|
||
|
||
class displayRole(APIView):
|
||
"""
|
||
角色列表展示
|
||
"""
|
||
|
||
def post(self, request, *args, **kwargs):
|
||
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,
|
||
"remark": ro.remark,
|
||
})
|
||
return Response({'message': '展示成功', 'data': data, 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class modifypermissions(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
修改角色权限
|
||
"""
|
||
id = request.data.get('id')
|
||
permissionId = request.data.get('permissionId')
|
||
if not all([id, permissionId]):
|
||
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
|
||
permissionId = ast.literal_eval(permissionId)
|
||
ro = role.objects.get(id=id)
|
||
# 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)
|
||
ro.save(update_fields=['permissionId'])
|
||
|
||
return Response({'message': '权限修改成功', 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class getRolePermissions(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
查询角色权限
|
||
: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)
|
||
|
||
try:
|
||
ro = role.objects.get(id=role_id)
|
||
except role.DoesNotExist:
|
||
return Response({'status': 'error', 'message': '角色不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# 解析权限ID列表
|
||
try:
|
||
if ro.permissionId:
|
||
# 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 = []
|
||
else:
|
||
permission_ids = []
|
||
except (ValueError, SyntaxError):
|
||
# 如果解析失败,返回空列表
|
||
permission_ids = []
|
||
|
||
return Response({'message': '查询成功', 'data': permission_ids, 'code': 0}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class DeleteRegistration(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
删除预立案登记
|
||
:param request:
|
||
:param args:
|
||
:param kwargs:
|
||
:return:
|
||
"""
|
||
id = request.data.get('id')
|
||
if not id:
|
||
return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
pre = PreFiling.objects.get(id=id, is_deleted=False)
|
||
|
||
# 注意:由于预立案和立项登记、投标登记已经拆分,不再需要检查关联
|
||
# 预立案现在可以独立删除(如果不再需要的话)
|
||
# 如果将来需要保留预立案用于其他用途,可以保留此检查逻辑
|
||
|
||
# 软删除:更新 is_deleted 字段
|
||
pre.is_deleted = True
|
||
pre.save()
|
||
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
|
||
except PreFiling.DoesNotExist:
|
||
return Response({'status': 'error', 'message': '预立案登记不存在', 'code': 1},
|
||
status=status.HTTP_404_NOT_FOUND)
|
||
|
||
|
||
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列表(可选,如果不传则转移所有案件)
|
||
project_ids = request.data.get('project_ids') # 要转移的立项ID列表(可选)
|
||
|
||
if not all([old_undertaker, new_undertaker]):
|
||
return Response({
|
||
'status': 'error',
|
||
'message': '缺少参数:原承办人员和新承办人员不能为空',
|
||
'code': 1
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 检查原承办人员是否存在(排除软删除用户)
|
||
try:
|
||
old_user = User.objects.get(username=old_undertaker, is_deleted=False)
|
||
except User.DoesNotExist:
|
||
return Response({
|
||
'status': 'error',
|
||
'message': '原承办人员不存在或已被删除',
|
||
'code': 1
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# 检查新承办人员是否存在(排除软删除用户)
|
||
try:
|
||
new_user = User.objects.get(username=new_undertaker, is_deleted=False)
|
||
except User.DoesNotExist:
|
||
return Response({
|
||
'status': 'error',
|
||
'message': '新承办人员不存在或已被删除',
|
||
'code': 1
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# 查询要转移的预立案
|
||
if case_ids and isinstance(case_ids, list):
|
||
prefiling_cases = PreFiling.objects.filter(Undertaker=old_undertaker, id__in=case_ids)
|
||
else:
|
||
prefiling_cases = PreFiling.objects.filter(Undertaker=old_undertaker)
|
||
|
||
prefiling_count = prefiling_cases.count()
|
||
|
||
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)
|
||
|
||
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:
|
||
return Response({
|
||
'status': 'error',
|
||
'message': '没有找到需要转移的案件',
|
||
'code': 1
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 执行转移
|
||
transferred_prefilings = []
|
||
for case in prefiling_cases:
|
||
case.Undertaker = new_undertaker
|
||
case.save(update_fields=['Undertaker'])
|
||
transferred_prefilings.append({
|
||
'id': case.id,
|
||
'times': case.times,
|
||
'client_username': case.client_username,
|
||
'party_username': case.party_username,
|
||
'description': case.description
|
||
})
|
||
|
||
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
|
||
})
|
||
|
||
return Response({
|
||
'message': f'案件转移成功,共转移{total_count}个案件',
|
||
'code': 0,
|
||
'data': {
|
||
'old_undertaker': old_undertaker,
|
||
'new_undertaker': new_undertaker,
|
||
'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
|
||
}
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class CaseChangeRequestCreate(APIView):
|
||
"""创建案件变更申请(需传入审批人)"""
|
||
def post(self, request, *args, **kwargs):
|
||
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') # 变更协议文件
|
||
approvers = request.data.get('approvers') # 审批人(必填):用户ID数组如[1,2,3],或用户名数组["张三","李四"],或逗号分隔字符串
|
||
personincharge = request.data.get('personincharge') # 兼容旧接口,未传approvers时使用
|
||
approvers = normalize_approvers_param(approvers, personincharge)
|
||
|
||
# 验证必填参数
|
||
if not contract_no:
|
||
return Response({
|
||
'status': 'error',
|
||
'message': '缺少参数contract_no(合同编号)',
|
||
'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)
|
||
|
||
if not approvers:
|
||
return Response({
|
||
'status': 'error',
|
||
'message': '缺少参数approvers(审批人),请传入审批人列表,如用户ID数组[1,2,3]或用户名数组["张三","李四"]',
|
||
'code': 1
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 如果提供了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)
|
||
|
||
# 获取申请人
|
||
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)
|
||
|
||
# 创建变更申请(审批人由入参 approvers 传入,create_approval_with_team_logic 会解析并回写 approvers_order)
|
||
today = datetime.now().strftime("%Y-%m-%d")
|
||
change_request = CaseChangeRequest.objects.create(
|
||
case=case, # 案件关联(可选)
|
||
contract_no=contract_no, # 合同编号(必填)
|
||
change_item=change_item,
|
||
change_reason=change_reason,
|
||
change_agreement=json.dumps(agreement_urls, ensure_ascii=False),
|
||
times=today,
|
||
applicant=applicant_name,
|
||
state='审核中',
|
||
approvers_order=None, # 由 create_approval_with_team_logic 根据 approvers 入参回写
|
||
need_approval=True
|
||
)
|
||
|
||
# 如果提供了case_id,更新案件的change_request字段
|
||
if case:
|
||
case.change_request = change_request
|
||
case.save(update_fields=['change_request'])
|
||
|
||
# 构建审批内容(不含审批流程,由 create_approval_with_team_logic 追加)
|
||
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)}"
|
||
|
||
from User.utils import create_approval_with_team_logic
|
||
|
||
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)
|
||
|
||
return Response({
|
||
'message': '变更申请提交成功',
|
||
'code': 0,
|
||
'data': {
|
||
'change_request_id': change_request.id
|
||
}
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
|
||
class CaseChangeRequestList(APIView):
|
||
"""
|
||
案件变更申请列表查询。
|
||
默认仅展示审批通过的数据(审批通过后才进变更申请表格)。
|
||
我的申请/全部:传 show_all=true 或 state=审核中,待查看,已通过,未通过 可查全部或按状态筛选。
|
||
"""
|
||
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筛选
|
||
state = request.data.get('state') # 可选:按状态筛选;不传且不传 show_all 时默认只返回已通过
|
||
approved_only = request.data.get('approved_only') # 可选:为 true 时仅返回已通过
|
||
show_all = request.data.get('show_all') # 可选:为 true 时返回全部状态(我的申请用),不传则默认只返回已通过
|
||
|
||
# 参数验证和默认值处理
|
||
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):
|
||
return Response({
|
||
'status': 'error',
|
||
'message': '参数page和per_page必须是有效的数字',
|
||
'code': 1
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
if page < 1:
|
||
page = 1
|
||
if per_page < 1:
|
||
per_page = 10
|
||
|
||
Q_obj = Q(is_deleted=False)
|
||
if case_id:
|
||
Q_obj &= Q(case_id=case_id)
|
||
# 变更申请表格:默认只展示审批通过的数据;传 show_all=true 可查全部(我的申请)
|
||
if show_all in (True, 'true', '1', 1):
|
||
pass # 不按状态筛选,返回全部
|
||
elif approved_only in (True, 'true', '1', 1) or state == '已通过':
|
||
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)
|
||
else:
|
||
# 默认:仅展示已通过,数据审批通过后才进变更申请表格
|
||
Q_obj &= Q(state='已通过')
|
||
|
||
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,
|
||
'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字符串)
|
||
'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 = []
|
||
|
||
# 获取案件信息(如果有关联)
|
||
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
|
||
}
|
||
|
||
data = {
|
||
'id': change_request.id,
|
||
'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字符串)
|
||
'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)
|
||
|
||
|
||
def _prefiling_person_to_info(person_json):
|
||
"""从预立案的 client_username/party_username(JSON)中提取第一个人员,转为 conflict_search 可用的字符串。"""
|
||
if not person_json:
|
||
return None
|
||
try:
|
||
data = json.loads(person_json) if isinstance(person_json, str) else person_json
|
||
if isinstance(data, list) and len(data) > 0:
|
||
item = data[0]
|
||
if isinstance(item, dict):
|
||
name = (item.get('name') or item.get('name_original') or '').strip()
|
||
id_num = (item.get('idNumber') or item.get('id_number') or '').strip()
|
||
if name:
|
||
return f"{name},身份证:{id_num}" if id_num else name
|
||
elif isinstance(data, dict):
|
||
name = (data.get('name') or data.get('name_original') or '').strip()
|
||
id_num = (data.get('idNumber') or data.get('id_number') or '').strip()
|
||
if name:
|
||
return f"{name},身份证:{id_num}" if id_num else name
|
||
except (json.JSONDecodeError, TypeError, AttributeError):
|
||
pass
|
||
return None
|
||
|
||
|
||
def _extract_all_person_info(info_val):
|
||
"""
|
||
从 client_info/party_info 中提取所有人员的姓名和身份证号。
|
||
支持:
|
||
- JSON 字符串数组:'[{"name":"张三","idNumber":"111"},{"name":"王五","idNumber":"333"}]'
|
||
- JSON 字符串对象:'{"name":"张三","idNumber":"111"}'
|
||
- list / dict(已解析的 JSON)
|
||
- 普通字符串:"张三(111)"、"张三,身份证号:111"、"张三"
|
||
返回: (names_list, id_numbers_list, originals_list)
|
||
"""
|
||
names = []
|
||
id_numbers = []
|
||
originals = []
|
||
if not info_val:
|
||
return names, id_numbers, originals
|
||
# 尝试 JSON 解析
|
||
data = None
|
||
if isinstance(info_val, str):
|
||
info_val = info_val.strip()
|
||
if not info_val:
|
||
return names, id_numbers, originals
|
||
if info_val.startswith('[') or info_val.startswith('{'):
|
||
try:
|
||
data = json.loads(info_val)
|
||
except (json.JSONDecodeError, TypeError):
|
||
pass
|
||
elif isinstance(info_val, (list, dict)):
|
||
data = info_val
|
||
if data is not None:
|
||
if isinstance(data, dict):
|
||
data = [data]
|
||
if isinstance(data, list):
|
||
for item in data:
|
||
if not isinstance(item, dict):
|
||
continue
|
||
name = (item.get('name') or item.get('name_original') or '').strip()
|
||
id_num = (item.get('idNumber') or item.get('id_number') or '').strip()
|
||
if name:
|
||
names.append(name)
|
||
if id_num:
|
||
id_numbers.append(id_num)
|
||
orig = f"{name}({id_num})" if name and id_num else (name or id_num or '')
|
||
if orig:
|
||
originals.append(orig)
|
||
if names or id_numbers:
|
||
return names, id_numbers, originals
|
||
# 回退到处理普通字符串
|
||
if isinstance(info_val, str):
|
||
# 先尝试匹配多个 "name(id)" 格式(如 "张三(123456),李四(789012)")
|
||
matches = re.findall(r'([^,,((]+?)\s*[((]([^))]*)[))]', info_val)
|
||
if matches:
|
||
for name, id_num in matches:
|
||
name = name.strip()
|
||
id_num = id_num.strip()
|
||
if name:
|
||
names.append(name)
|
||
if id_num:
|
||
id_numbers.append(id_num)
|
||
orig = f"{name}({id_num})" if name and id_num else (name or id_num or '')
|
||
if orig:
|
||
originals.append(orig)
|
||
return names, id_numbers, originals
|
||
# 没有匹配到多人格式,回退到单人解析
|
||
parsed = parse_person_info(info_val)
|
||
if parsed['name']:
|
||
names.append(parsed['name'])
|
||
if parsed['id_number']:
|
||
id_numbers.append(parsed['id_number'])
|
||
if parsed['original']:
|
||
originals.append(parsed['original'])
|
||
return names, id_numbers, originals
|
||
|
||
|
||
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
|
||
|
||
|
||
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
|
||
|
||
def _normalize_id(s):
|
||
"""身份证/统一代码规范化后再精准匹配:去首尾空格、去内部空格与横线,仅当完全一致才匹配。"""
|
||
if not s or not isinstance(s, str):
|
||
return ''
|
||
t = s.strip().replace(' ', '').replace('\u3000', '').replace('-', '')
|
||
return t
|
||
|
||
# 解析委托人和相对方信息(支持 JSON 数组,提取所有人员的姓名和身份证号)
|
||
client_names, client_id_numbers, client_originals = _extract_all_person_info(client_info)
|
||
party_names, party_id_numbers, party_originals = _extract_all_person_info(party_info)
|
||
|
||
# 按「人」成对:每人 (姓名, 规范化身份证)。当同时填了姓名+身份证时,必须同一人两项都一致才命中
|
||
def _person_pairs(names, id_numbers):
|
||
out = []
|
||
for i in range(max(len(names), len(id_numbers))):
|
||
name = (names[i] if i < len(names) else '').strip()
|
||
id_norm = _normalize_id(id_numbers[i]) if i < len(id_numbers) else ''
|
||
if name or id_norm:
|
||
out.append((name, id_norm))
|
||
return out
|
||
|
||
client_persons = _person_pairs(client_names, client_id_numbers)
|
||
party_persons = _person_pairs(party_names, party_id_numbers)
|
||
|
||
def _person_match(record_name, record_id_norm, search_persons):
|
||
"""记录中的一人 (record_name, record_id_norm) 是否与任意搜索人精准匹配(姓名+身份证都一致)。"""
|
||
for sn, si in search_persons:
|
||
if not sn and not si:
|
||
continue
|
||
name_ok = (not sn) or (record_name == sn)
|
||
id_ok = (not si) or (record_id_norm == si)
|
||
if name_ok and id_ok:
|
||
return True
|
||
return False
|
||
|
||
# 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 = []
|
||
|
||
# 解析历史记录的委托人/相对方姓名及身份证号
|
||
pf_client_names = []
|
||
pf_client_ids = []
|
||
try:
|
||
cd = json.loads(pf.client_username) if isinstance(pf.client_username, str) else pf.client_username
|
||
if isinstance(cd, list):
|
||
for item in cd:
|
||
if isinstance(item, dict):
|
||
n = (item.get('name') or '').strip()
|
||
if n:
|
||
pf_client_names.append(n)
|
||
pid = (item.get('idNumber') or item.get('id_number') or '').strip()
|
||
if pid:
|
||
pf_client_ids.append(pid)
|
||
elif isinstance(cd, dict):
|
||
n = (cd.get('name') or '').strip()
|
||
if n:
|
||
pf_client_names.append(n)
|
||
pid = (cd.get('idNumber') or cd.get('id_number') or '').strip()
|
||
if pid:
|
||
pf_client_ids.append(pid)
|
||
except (json.JSONDecodeError, TypeError, AttributeError):
|
||
if pf.client_username:
|
||
pf_client_names = [pf.client_username.strip()]
|
||
|
||
pf_party_names = []
|
||
pf_party_ids = []
|
||
try:
|
||
pd = json.loads(pf.party_username) if isinstance(pf.party_username, str) else pf.party_username
|
||
if isinstance(pd, list):
|
||
for item in pd:
|
||
if isinstance(item, dict):
|
||
n = (item.get('name') or '').strip()
|
||
if n:
|
||
pf_party_names.append(n)
|
||
pid = (item.get('idNumber') or item.get('id_number') or '').strip()
|
||
if pid:
|
||
pf_party_ids.append(pid)
|
||
elif isinstance(pd, dict):
|
||
n = (pd.get('name') or '').strip()
|
||
if n:
|
||
pf_party_names.append(n)
|
||
pid = (pd.get('idNumber') or pd.get('id_number') or '').strip()
|
||
if pid:
|
||
pf_party_ids.append(pid)
|
||
except (json.JSONDecodeError, TypeError, AttributeError):
|
||
if pf.party_username:
|
||
pf_party_names = [pf.party_username.strip()]
|
||
|
||
# 冲突规则:按「人」精准匹配,同一人必须姓名+身份证都一致才命中
|
||
pf_client_persons = _person_pairs(pf_client_names, [_normalize_id(x) for x in pf_client_ids])
|
||
pf_party_persons = _person_pairs(pf_party_names, [_normalize_id(x) for x in pf_party_ids])
|
||
if client_persons and pf_client_persons:
|
||
for rn, ri in pf_client_persons:
|
||
if _person_match(rn, ri, client_persons):
|
||
is_match = True
|
||
match_reason.append(f"委托人匹配:{rn or ri or '身份证'}")
|
||
break
|
||
if party_persons and pf_party_persons:
|
||
for rn, ri in pf_party_persons:
|
||
if _person_match(rn, ri, party_persons):
|
||
is_match = True
|
||
match_reason.append(f"相对方匹配:{rn or ri or '身份证'}")
|
||
break
|
||
|
||
# 承办人员精准匹配
|
||
if undertaker and pf.Undertaker:
|
||
if undertaker.strip() == pf.Undertaker.strip():
|
||
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,
|
||
'match_reason': normalize_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 = []
|
||
|
||
# 冲突规则:按「人」精准匹配,同一人必须姓名+身份证都一致才命中
|
||
if client_persons and pro.client_info:
|
||
pro_client_names, pro_client_ids, _ = _extract_all_person_info(pro.client_info)
|
||
pro_client_persons = _person_pairs(pro_client_names, pro_client_ids)
|
||
for rn, ri in pro_client_persons:
|
||
if _person_match(rn, ri, client_persons):
|
||
is_match = True
|
||
match_reason.append("委托人匹配")
|
||
break
|
||
if party_persons and pro.party_info:
|
||
pro_party_names, pro_party_ids, _ = _extract_all_person_info(pro.party_info)
|
||
pro_party_persons = _person_pairs(pro_party_names, pro_party_ids)
|
||
for rn, ri in pro_party_persons:
|
||
if _person_match(rn, ri, party_persons):
|
||
is_match = True
|
||
match_reason.append("相对方匹配")
|
||
break
|
||
|
||
# 负责人精准匹配(与承办人字段完全一致才算匹配)
|
||
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') or '').strip()
|
||
main_lawyer = (responsiblefor_data.get('main_lawyer') or '').strip()
|
||
assistant_lawyer = (responsiblefor_data.get('assistant_lawyer') or '').strip()
|
||
case_manager_lawyer = (responsiblefor_data.get('case_manager_lawyer') or '').strip()
|
||
ut = undertaker.strip()
|
||
if ut == responsible_person or ut == main_lawyer or ut == assistant_lawyer or ut == case_manager_lawyer:
|
||
is_match = True
|
||
match_reason.append("负责人匹配")
|
||
except (json.JSONDecodeError, TypeError):
|
||
if undertaker.strip() == (pro.responsiblefor or '').strip():
|
||
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': normalize_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 = []
|
||
|
||
# 从招标单位解析出人员列表,按「人」精准匹配(姓名+身份证都一致才命中)
|
||
bid_names, bid_ids, _ = _extract_all_person_info(bid.BiddingUnit) if bid.BiddingUnit else ([], [], [])
|
||
bid_persons = _person_pairs(bid_names, bid_ids)
|
||
|
||
# 招标单位精准匹配:查询的招标单位与记录中某一人完全一致(姓名或证件号单独查时按原逻辑)
|
||
if bidding_unit and bid.BiddingUnit:
|
||
ut = bidding_unit.strip()
|
||
ut_norm = _normalize_id(bidding_unit)
|
||
for rn, ri in bid_persons:
|
||
if ut == rn or (ut_norm and ut_norm == ri):
|
||
is_match = True
|
||
match_reason.append("招标单位匹配")
|
||
break
|
||
|
||
# 委托人/相对方与招标单位:同一人姓名+身份证都一致才命中
|
||
if client_persons and bid_persons:
|
||
for rn, ri in bid_persons:
|
||
if _person_match(rn, ri, client_persons):
|
||
is_match = True
|
||
match_reason.append("委托人匹配招标单位")
|
||
break
|
||
if party_persons and bid_persons:
|
||
for rn, ri in bid_persons:
|
||
if _person_match(rn, ri, party_persons):
|
||
is_match = True
|
||
match_reason.append("相对方匹配招标单位")
|
||
break
|
||
|
||
if is_match:
|
||
bid_list.append({
|
||
'id': bid.id,
|
||
'ProjectName': bid.ProjectName,
|
||
'times': bid.times,
|
||
'BiddingUnit': bid.BiddingUnit,
|
||
'state': bid.state,
|
||
'match_reason': normalize_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):
|
||
"""
|
||
利益冲突检索
|
||
|
||
请求参数(至少提供一个):
|
||
- client_info: 委托人身份信息(可选),支持 JSON 字符串或普通字符串:
|
||
* JSON 字符串:'[{"name":"张三","idNumber":"111"}]' 或 '{"name":"张三","idNumber":"111"}'
|
||
* 普通字符串:"张三(3263452342342342)"、"张三,身份证号:3263452342342342"、"张三"
|
||
- party_info: 相对方身份信息(可选),格式同上
|
||
- undertaker: 承办人员(可选)
|
||
- bidding_unit: 招标单位(可选)
|
||
- exclude_prefiling_id: 要排除的预立案ID(可选)
|
||
- exclude_project_id: 要排除的立项ID(可选)
|
||
- exclude_bid_id: 要排除的投标ID(可选)
|
||
"""
|
||
# client_info / party_info 支持 JSON 字符串(如 '[{"name":"张三","idNumber":"111"}]')或普通字符串
|
||
client_info = request.data.get('client_info') or None
|
||
party_info = request.data.get('party_info') or None
|
||
# 如果是字符串,做 strip;如果是 list/dict 保持原样(conflict_search 内部会用 _extract_all_person_info 解析)
|
||
if isinstance(client_info, str):
|
||
client_info = client_info.strip() or None
|
||
if isinstance(party_info, str):
|
||
party_info = party_info.strip() or 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
|
||
)
|
||
# 归一化冲突记录为前端期望格式 [{ index, name, idNumber }, ...](预立案用 client_username/party_username,立项用 client_info/party_info,投标用 BiddingUnit)
|
||
def _normalize_person_list(val):
|
||
"""
|
||
归一化人员信息,始终返回 list[dict] 或 None。
|
||
支持:
|
||
- JSON 数组字符串:'[{"name":"张三","idNumber":"111"}]'
|
||
- JSON 对象字符串:'{"name":"张三","idNumber":"111"}'
|
||
- list / dict(已解析的 JSON)
|
||
- 纯字符串:"张三(111)"、"张三(111),李四(222)"
|
||
"""
|
||
if not val:
|
||
return []
|
||
try:
|
||
lst = json.loads(val) if isinstance(val, str) else val
|
||
if isinstance(lst, dict):
|
||
lst = [lst]
|
||
if not isinstance(lst, list):
|
||
raise ValueError("not a list")
|
||
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):
|
||
pass
|
||
|
||
# 回退:解析纯字符串格式(如 "张三(123456)" 或 "张三(123),李四(456)")
|
||
if isinstance(val, str):
|
||
# 尝试匹配多个 "name(id)" 格式
|
||
matches = re.findall(r'([^,,((]+?)\s*[((]([^))]*)[))]', val)
|
||
if matches:
|
||
out = []
|
||
for i, (name, id_num) in enumerate(matches):
|
||
out.append({
|
||
'index': i + 1,
|
||
'name': name.strip(),
|
||
'idNumber': id_num.strip()
|
||
})
|
||
return out
|
||
# 没有括号格式,尝试用 parse_person_info 解析
|
||
parsed = parse_person_info(val)
|
||
if parsed['name'] or parsed['id_number']:
|
||
return [{
|
||
'index': 1,
|
||
'name': parsed['name'],
|
||
'idNumber': parsed['id_number']
|
||
}]
|
||
return None
|
||
|
||
def _normalize_conflict_records(records):
|
||
"""
|
||
归一化冲突记录,将人员信息字段统一转为 JSON 字符串格式:
|
||
'[{"index":1,"name":"张三","idNumber":"123456"}]'
|
||
确保前端可以使用 isValidFormat() + JSON.parse() 正确展示。
|
||
"""
|
||
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:
|
||
# 转为 JSON 字符串,兼容前端 JSON.parse() 展示
|
||
new_record[key] = json.dumps(normalized, ensure_ascii=False)
|
||
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'])
|
||
# 统计冲突数量
|
||
total_conflicts = len(prefiling) + len(project) + len(bid)
|
||
return Response({
|
||
'message': '检索成功',
|
||
'code': 0,
|
||
'data': {
|
||
'total_conflicts': total_conflicts,
|
||
'prefiling_conflicts_count': len(prefiling),
|
||
'project_conflicts_count': len(project),
|
||
'bid_conflicts_count': len(bid),
|
||
'prefiling_conflicts': prefiling,
|
||
'project_conflicts': project,
|
||
'bid_conflicts': bid
|
||
}
|
||
}, 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)
|
||
|
||
|
||
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)
|
||
|
||
|
||
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 {}
|
||
|
||
# 解析结案申请;代理合同已从案件管理移除,不再返回
|
||
try:
|
||
closing_application_list = json.loads(info.Closingapplication) if info.Closingapplication else []
|
||
except (json.JSONDecodeError, TypeError):
|
||
closing_application_list = []
|
||
|
||
if not closing_application_list:
|
||
closing_application_list = ""
|
||
|
||
contractreturn_str = info.Contractreturn or ""
|
||
if contractreturn_str.strip() in ["[]", ""]:
|
||
contractreturn_str = ""
|
||
|
||
closing_application_str = info.Closingapplication or ""
|
||
if closing_application_str.strip() in ["[]", ""]:
|
||
closing_application_str = ""
|
||
|
||
# 处理已开票/已收款字段
|
||
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,
|
||
"Contractreturn": contractreturn_str, # 合同返还(没有数据时返回空字符串)
|
||
"Closingapplication": closing_application_str, # 结案申请(JSON字符串,没有文件时返回空字符串)
|
||
"ClosingapplicationUrls": closing_application_list if closing_application_list else "", # 结案申请(URL列表,没有文件时返回空字符串)
|
||
"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
|
||
}, 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:
|
||
# 解析 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:
|
||
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
|
||
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:
|
||
# 如果用户不存在,使用原有用户名
|
||
user = system_record.username
|
||
else:
|
||
# 如果 token 不存在,使用原有用户名
|
||
user = system_record.username
|
||
|
||
if title:
|
||
system_record.title = title
|
||
system_record.save(update_fields=['title'])
|
||
if content:
|
||
system_record.content = content
|
||
system_record.save(update_fields=['content'])
|
||
if file:
|
||
system_record.file = json.dumps(flies(file)) # 使用 flies 函数处理文件
|
||
system_record.save(update_fields=['file'])
|
||
|
||
system_record.username = user
|
||
system_record.times = date_str
|
||
|
||
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:
|
||
system_record = System.objects.get(id=ID, is_deleted=False)
|
||
# 软删除:更新 is_deleted 字段
|
||
system_record.is_deleted = True
|
||
system_record.save()
|
||
return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK)
|
||
except System.DoesNotExist:
|
||
return Response({'status': 'error', 'message': '系统公告不存在', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
|
||
class ExportCaseLogExcel(APIView):
|
||
def post(self, request, *args, **kwargs):
|
||
"""
|
||
导出指定案件的日志到Excel,上传到OSS并返回下载链接
|
||
必填参数:case_id(案件ID)
|
||
可选参数:start_time(开始时间)、end_time(结束时间)
|
||
返回:下载链接
|
||
"""
|
||
import pandas as pd
|
||
import oss2
|
||
import uuid
|
||
from io import BytesIO
|
||
|
||
case_id = request.data.get('case_id')
|
||
start_time = request.data.get('start_time')
|
||
end_time = request.data.get('end_time')
|
||
|
||
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)
|
||
|
||
# 构建查询条件
|
||
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)
|
||
|
||
# 查询日志
|
||
logs = Caselog.objects.filter(Q_obj).order_by('-times', '-id')
|
||
|
||
if not logs.exists():
|
||
return Response({'status': 'error', 'message': '该案件没有日志记录', 'code': 1}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# 构建Excel数据(只保留:记录人、记录时间、工作内容)
|
||
excel_data = []
|
||
for log in logs:
|
||
excel_data.append({
|
||
'记录人': log.username or '',
|
||
'记录时间': log.times or '',
|
||
'工作内容': log.content or '',
|
||
})
|
||
|
||
# 生成Excel文件
|
||
df = pd.DataFrame(excel_data)
|
||
output = BytesIO()
|
||
with pd.ExcelWriter(output, engine='openpyxl') as writer:
|
||
df.to_excel(writer, index=False, sheet_name='案件日志')
|
||
ws = writer.sheets['案件日志']
|
||
for idx, col in enumerate(df.columns):
|
||
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
|
||
|
||
output.seek(0)
|
||
|
||
# 上传到阿里云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:
|
||
# 生成下载链接
|
||
download_url = f'http://{bucket_name}.{endpoint.replace("https://", "")}/{filename}'
|
||
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)
|
||
|