加入案件设置标签的接口

This commit is contained in:
ddrwode
2026-01-19 19:42:08 +08:00
parent 5f4e3dd840
commit e782d3a2fa
3 changed files with 388 additions and 101 deletions

View File

@@ -1,4 +1,4 @@
from .views import UserRegister,UnregisteredUserList,RegisteredUserList,UserDeparture,UserDepartureDetail,EditUserDeparture,DeleteUserDeparture,issueAnInvoice,issueAnInvoiceDetail,confirm,loan,PaymentRequest,reimbursement,confirmdisplay,loandisplay,PaymentDisplay,reimbursementdetail,Change,ChangeDetail,EditInvoice,DeleteInvoice,EditIncome,DeleteIncome,EditAccounts,DeleteAccounts,EditPayment,DeletePayment,EditReimbursement,DeleteReimbursement,EditBonusChange,DeleteBonusChange,GetCaseListForInvoice,SearchCaseByContractNo,AddInvoice
from .views import UserRegister,UnregisteredUserList,RegisteredUserList,UserDeparture,UserDepartureDetail,EditUserDeparture,DeleteUserDeparture,issueAnInvoice,issueAnInvoiceDetail,confirm,loan,PaymentRequest,reimbursement,confirmdisplay,loandisplay,PaymentDisplay,reimbursementdetail,Change,ChangeDetail,EditInvoice,DeleteInvoice,EditIncome,DeleteIncome,EditAccounts,DeleteAccounts,EditPayment,DeletePayment,EditReimbursement,DeleteReimbursement,EditBonusChange,DeleteBonusChange,GetCaseListForInvoice,SearchCaseByContractNo,AddInvoice,GetApproversListForIncomeConfirm
from django.urls import path
urlpatterns = [
@@ -18,6 +18,7 @@ urlpatterns = [
path('editInvoice', EditInvoice.as_view(), name="editInvoice/"),
path('deleteInvoice', DeleteInvoice.as_view(), name="deleteInvoice/"),
path('confirm', confirm.as_view(), name="confirm/"),
path('approvers-list-for-income-confirm', GetApproversListForIncomeConfirm.as_view(), name="approvers-list-for-income-confirm/"),
path('confirmdisplay', confirmdisplay.as_view(), name="confirmdisplay/"),
path('editIncome', EditIncome.as_view(), name="editIncome/"),
path('deleteIncome', DeleteIncome.as_view(), name="deleteIncome/"),

View File

@@ -871,27 +871,30 @@ class confirm(APIView):
"""
收入确认
优化后:
1. 通过合同号搜索选择案件case_id或project_id
1. 通过合同号关联案件ContractNo必填
2. 从案件管理中同步:客户名称、负责人
3. 手动填写:收款日期、收款金额
4. 财务填写好后直接抄送给案件负责人
5. 负责人在待办中填写收入分配后,再抄送给财务负责人
4. 按照团队审核逻辑处理审核流程(与其他审核待办一致)
:param request:
:param args:
:param kwargs:
:return:
"""
# 案件选择(二选一)
case_id = request.data.get('case_id') # 案件管理ID
project_id = request.data.get('project_id') # 立项登记ID
# 必填参数:合同号
ContractNo = request.data.get('ContractNo') # 合同号(必填,用于关联案件)
# 手动填写信息
times = request.data.get('times') # 收款日期
amount = request.data.get('amount') # 收款金额
# 可选字段(如果提供了case_id或project_id,将从案件管理中同步)
ContractNo = request.data.get('ContractNo') # 合同号(可选,如果提供了case_id或project_id则自动同步)
CustomerID = request.data.get('CustomerID') # 客户名称可选如果提供了case_id或project_id则自动同步
# 可选字段(如果通过合同号找到了案件,将从案件管理中同步)
CustomerID = request.data.get('CustomerID') # 客户名称(可选,如果通过合同号找到案件则自动同步)
# 审核人列表可选多人团队时需要推荐用户ID数组如[1,2,3],兼容:用户名数组)
approvers = request.data.get('approvers')
# 兼容旧接口:如果传了 personincharge转换为 approvers
personincharge = request.data.get('personincharge')
approvers = normalize_approvers_param(approvers, personincharge)
allocate = request.data.get('allocate') # 收入分配(财务提交时不填写,由负责人填写)
token = request.META.get('token')
@@ -908,8 +911,8 @@ class confirm(APIView):
missing_params.append('times(收款日期)')
if not amount:
missing_params.append('amount(收款金额)')
if not case_id and not project_id:
missing_params.append('case_id或project_id(请选择案件)')
if not ContractNo:
missing_params.append('ContractNo(合同号)')
if missing_params:
return Response({
@@ -917,8 +920,18 @@ class confirm(APIView):
'message': f'缺少必填参数: {", ".join(missing_params)}',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 获取提交人的团队信息(用于审核流程)
from User.models import Team
team_name = user.team
team = None
if team_name:
try:
team = Team.objects.get(name=team_name, is_deleted=False)
except Team.DoesNotExist:
pass
# 从案件管理中提取信息
# 通过合同号从案件管理中提取信息
from business.models import Case, ProjectRegistration
import json
@@ -926,66 +939,53 @@ class confirm(APIView):
responsible_person_username = None # 负责人用户名(用于抄送)
case_info = None
if case_id:
# 从Case案件管理中获取信息
# 优先从Case案件管理中查找
try:
case = Case.objects.select_related('project').get(contract_no=ContractNo, is_deleted=False)
# 获取客户名称
CustomerID = CustomerID or case.client_name
# 获取负责人信息
responsiblefor = case.responsiblefor
# 如果案件信息不完整从关联的ProjectRegistration获取
if case.project:
project = case.project
CustomerID = CustomerID or project.client_info
responsiblefor = responsiblefor or project.responsiblefor
# 解析负责人信息
try:
case = Case.objects.select_related('project').get(id=case_id, is_deleted=False)
# 获取合同号
ContractNo = ContractNo or case.contract_no
# 获取客户名称
CustomerID = CustomerID or case.client_name
# 获取负责人信息
responsiblefor = case.responsiblefor
# 如果案件信息不完整从关联的ProjectRegistration获取
if case.project:
project = case.project
ContractNo = ContractNo or project.ContractNo
CustomerID = CustomerID or project.client_info
responsiblefor = responsiblefor or project.responsiblefor
# 解析负责人信息
try:
if responsiblefor:
if isinstance(responsiblefor, str):
responsiblefor_dict = json.loads(responsiblefor)
else:
responsiblefor_dict = responsiblefor
responsible_person = responsiblefor_dict.get('responsible_person', '')
# 查找负责人用户名
if responsible_person:
try:
responsible_user = User.objects.get(username=responsible_person, is_deleted=False)
responsible_person_username = responsible_user.username
except User.DoesNotExist:
pass
except (json.JSONDecodeError, TypeError, AttributeError):
responsible_person = str(responsiblefor) if responsiblefor else ''
case_info = {
'id': case.id,
'type': 'case',
'contract_no': ContractNo,
'customer_name': CustomerID,
'responsible_person': responsible_person
}
except Case.DoesNotExist:
return Response({
'status': 'error',
'message': '案件不存在或已被删除',
'code': 1
}, status=status.HTTP_404_NOT_FOUND)
elif project_id:
# 从ProjectRegistration立项登记中获取信息
if responsiblefor:
if isinstance(responsiblefor, str):
responsiblefor_dict = json.loads(responsiblefor)
else:
responsiblefor_dict = responsiblefor
responsible_person = responsiblefor_dict.get('responsible_person', '')
# 查找负责人用户名
if responsible_person:
try:
responsible_user = User.objects.get(username=responsible_person, is_deleted=False)
responsible_person_username = responsible_user.username
except User.DoesNotExist:
pass
except (json.JSONDecodeError, TypeError, AttributeError):
responsible_person = str(responsiblefor) if responsiblefor else ''
case_info = {
'id': case.id,
'type': 'case',
'contract_no': ContractNo,
'customer_name': CustomerID,
'responsible_person': responsible_person
}
except Case.DoesNotExist:
# 如果Case中找不到尝试从ProjectRegistration立项登记中查找
try:
project = ProjectRegistration.objects.get(id=project_id, is_deleted=False)
project = ProjectRegistration.objects.get(ContractNo=ContractNo, is_deleted=False)
ContractNo = ContractNo or project.ContractNo
CustomerID = CustomerID or project.client_info
responsiblefor = project.responsiblefor
@@ -1018,11 +1018,11 @@ class confirm(APIView):
except ProjectRegistration.DoesNotExist:
return Response({
'status': 'error',
'message': '立项登记不存在或已被删除',
'message': f'未找到合同号为"{ContractNo}"的案件或立项登记,请检查合同号是否正确',
'code': 1
}, status=status.HTTP_404_NOT_FOUND)
# 验证合同号、客户名称和负责人是否获取成功
# 验证合同号、客户名称是否获取成功
if not ContractNo:
return Response({
'status': 'error',
@@ -1036,19 +1036,16 @@ class confirm(APIView):
'message': '无法从案件管理中获取客户名称,请检查案件信息',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
if not responsible_person_username:
return Response({
'status': 'error',
'message': f'无法找到负责人"{responsible_person}"的用户信息,请检查负责人是否正确',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
from datetime import datetime
now = datetime.now()
date_string = now.strftime("%Y-%m-%d")
# 创建收入确认记录allocate为"待负责人指定"
# 根据团队类型决定初始状态
# 如果是团队类型且需要审核,状态为"审核中";否则为"待财务处理"
initial_state = "审核中" if (team and team.team_type == 'team') else "待财务处理"
# 创建收入确认记录
income = Income.objects.create(
times=times,
ContractNo=ContractNo,
@@ -1057,12 +1054,11 @@ class confirm(APIView):
allocate=allocate if allocate else "待负责人指定", # 如果未提供,设为"待负责人指定"
submit=user.username,
submit_tiem=date_string,
state="待负责人填写分配" # 新状态:等待负责人填写收入分配
state=initial_state
)
# 直接抄送给案件负责人(不经过团队审批流程
from User.models import Approval
from User.utils import get_finance_personincharge_value
# 使用统一的审核流程函数(与其他审核待办一致
from User.utils import create_approval_with_team_logic
# 构建审批内容
content_parts = [
@@ -1072,18 +1068,40 @@ class confirm(APIView):
f"收款金额:{amount}",
f"收入分配:待负责人指定"
]
if responsible_person:
content_parts.append(f"案件负责人:{responsible_person}")
content = "".join(content_parts)
# 创建审批记录,直接抄送给负责人
approval = Approval.objects.create(
title=user.username + "提交收入确认",
content=content,
times=date_string,
personincharge=responsible_person_username, # 直接抄送给案件负责人
state="审核中", # 负责人需要填写收入分配
type="收入确认",
user_id=str(income.id)
)
# 使用统一的审核流程函数
try:
approval, approvers_order_json, needs_approval = create_approval_with_team_logic(
team_name=team_name,
approvers=approvers,
title=user.username + "提交收入确认",
content=content,
approval_type="收入确认",
user_id=str(income.id),
business_record=income,
today=date_string
)
except Exception as e:
# 记录异常并返回错误信息
import logging
logger = logging.getLogger(__name__)
logger.error(f"创建收入确认审批失败: {str(e)}", exc_info=True)
return Response({
'status': 'error',
'message': f'创建审批流程失败: {str(e)},请检查团队配置和审核人信息',
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 如果返回None且需要审核说明缺少审核人
if approval is None and needs_approval:
return Response({
'status': 'error',
'message': build_missing_approvers_message(team_name, approvers),
'code': 1
}, status=status.HTTP_400_BAD_REQUEST)
# 记录操作日志
new_data = {
@@ -1092,9 +1110,10 @@ class confirm(APIView):
'CustomerID': income.CustomerID,
'amount': income.amount,
'allocate': income.allocate,
'case_id': case_id if case_id else None,
'project_id': project_id if project_id else None,
'responsible_person': responsible_person_username
'case_info': case_info,
'responsible_person': responsible_person,
'team_type': team.team_type if team else 'unknown',
'approvers': approvers if (team and team.team_type == 'team') else None
}
log_operation(
request=request,
@@ -1105,23 +1124,71 @@ class confirm(APIView):
target_id=income.id,
target_name=f'{income.ContractNo} - {income.CustomerID}',
new_data=new_data,
remark=f'新增收入确认:合同号 {income.ContractNo},金额 {income.amount}已抄送给负责人 {responsible_person_username}'
remark=f'新增收入确认:合同号 {income.ContractNo},金额 {income.amount}团队类型 {team.team_type if team else "未知"}'
)
return Response({
'message': '提交成功,已抄送给负责人',
'message': '提交成功',
'code': 0,
'data': {
'id': income.id,
'ContractNo': income.ContractNo,
'CustomerID': income.CustomerID,
'responsible_person': responsible_person_username,
'responsible_person': responsible_person,
'state': income.state,
'approval_id': approval.id
'approval_id': approval.id if approval else None,
'needs_approval': team is None or team.team_type != 'personal'
}
}, status=status.HTTP_200_OK)
class GetApproversListForIncomeConfirm(APIView):
"""获取收入确认的审核人列表接口"""
def post(self, request, *args, **kwargs):
"""
获取收入确认时可选的审核人列表
返回所有未删除的用户列表,格式简洁,适合下拉选择
:param request:
:param args:
:param kwargs:
:return:
"""
# 可选参数:用户名搜索
username = request.data.get('username', '') # 用户名搜索(可选)
# 查询条件:只查询未软删除的用户
Q_obj = Q(is_deleted=False)
# 排除admin用户超级管理员
Q_obj &= ~Q(username='admin')
Q_obj &= ~Q(account='admin')
# 用户名搜索(模糊匹配)
if username:
Q_obj &= Q(username__icontains=username)
# 查询用户,按用户名排序
users = User.objects.filter(Q_obj).order_by('username')
# 构建返回数据(格式简洁,适合下拉选择)
data = []
for user in users:
data.append({
'id': user.id,
'username': user.username, # 用户名(用于显示和选择)
'account': user.account, # 账号(辅助信息)
'position': user.position if user.position else '', # 岗位(辅助信息)
})
return Response({
'message': '获取审核人列表成功',
'code': 0,
'total': len(data),
'data': data
}, status=status.HTTP_200_OK)
class confirmdisplay(APIView):
def post(self, request, *args, **kwargs):
"""

View File

@@ -0,0 +1,219 @@
# 接口重复功能分析报告
## 一、明确的重复接口
### 1. 财务模块 - 未入职用户列表接口重复
**重复的接口:**
- `POST /finance/user-register-detail``UnregisteredUserList`
- `POST /finance/unregistered-user-list``UnregisteredUserList`
**问题:**
- 两个URL路径指向同一个视图类
- `user-register-detail` 在注释中标注为"向后兼容保留旧URL"
**建议:**
- 保留 `unregistered-user-list`更符合RESTful命名规范
- 删除 `user-register-detail`(旧接口,已标注为兼容)
**位置:** `finance/urls.py` 第7行和第6行
---
## 二、功能重叠的接口
### 2. 获取案件列表接口(开票相关)
**接口1** `GetCaseListForInvoice`
- 路径:`POST /finance/case-list-for-invoice`
- 功能获取所有案件列表仅从ProjectRegistration获取
- 返回案件ID、合同号、负责人、项目类型、立项时间
**接口2** `SearchCaseByContractNo`
- 路径:`POST /finance/search-case-by-contract-no`
- 功能通过合同号搜索案件从Case和ProjectRegistration搜索
- 返回:更详细的案件信息,包括客户名称、相对方等
**分析:**
- `GetCaseListForInvoice` 功能较简单,只返回所有案件
- `SearchCaseByContractNo` 功能更强大,支持搜索且返回信息更完整
- 两者功能有重叠,但使用场景不同
**建议:**
- 可以合并:在 `GetCaseListForInvoice` 中添加搜索参数,使其支持按合同号搜索
- 或者保留两个接口,但明确使用场景:
- `GetCaseListForInvoice`:用于下拉选择(返回所有案件)
- `SearchCaseByContractNo`:用于搜索(支持模糊搜索)
---
### 3. 开票申请接口
**接口1** `issueAnInvoice`
- 路径:`POST /finance/issue-invoice`
- 功能:财务开票申请
- 特点:
- 支持通过case_id选择案件
- 需要审批流程state="审核中"
- 必须提供token验证
**接口2** `AddInvoice`
- 路径:`POST /finance/add-invoice`
- 功能:增开票申请
- 特点:
- 支持通过case_id或project_id选择案件
- 直接抄送财务state="待财务处理"
- 不需要审批流程
**分析:**
- 两个接口功能相似,都是创建开票申请
- 主要区别在于审批流程不同
- `AddInvoice` 功能更完善支持project_id直接抄送财务
**建议:**
- 可以合并为一个接口,通过参数控制是否需要审批
- 或者保留两个接口,但明确使用场景:
- `issueAnInvoice`:普通开票(需要审批)
- `AddInvoice`:增开票(直接抄送财务)
---
### 4. 案件标签设置接口
**接口1** `EditCase`
- 路径:`POST /business/editCase`
- 功能:编辑案件信息(包括设置标签)
- 特点:
- 可以编辑案件的各种信息(时间、文件、金额等)
- 支持通过 `tag_ids` 参数设置标签
- 需要提供案件ID和其他可选字段
**接口2** `SetCaseTags`
- 路径:`POST /business/set-case-tags`
- 功能:专门用于设置案件标签
- 特点:
- 只设置标签,不修改其他信息
- 更简洁只需提供case_id和tag_ids
- 有更详细的标签验证和错误提示
**分析:**
- `EditCase` 功能更全面,但设置标签只是其功能之一
- `SetCaseTags` 更专注,专门用于标签管理
- 两者功能有重叠,但使用场景不同
**建议:**
- 保留两个接口,但明确使用场景:
- `EditCase`:编辑案件信息时顺便设置标签
- `SetCaseTags`:专门用于批量设置或只设置标签的场景
---
## 三、命名容易混淆的接口
### 5. 日程相关接口命名
**接口1** `DeleteSchedule`但URL是 `scheduledetail`
- 路径:`POST /business/scheduledetail`
- 功能:删除日程
- 问题URL名称 `scheduledetail` 暗示是详情接口,但实际是删除功能
**接口2** `ScheduleDetail`
- 路径:`POST /business/ScheduleDetail`
- 功能:日程展示(列表)
**建议:**
-`scheduledetail` 改为 `delete-schedule``schedule-delete`
- 或者将 `DeleteSchedule` 类名改为更明确的名称
---
### 6. 律师文件相关接口命名
**接口1** `LawyersdocumentsDetail`
- 路径:`POST /business/lawdisplay`
- 功能:律师文件列表展示
**接口2** `LwaDetail`
- 路径:`POST /business/LwaDetail`
- 功能:删除律师文件(软删除)
- 问题类名和URL都暗示是详情接口但实际是删除功能
**建议:**
-`LwaDetail` 改为 `DeleteLawyerFile``LawyerFileDelete`
- 将URL `LwaDetail` 改为 `delete-lawyer-file`
---
## 四、功能相似但用途不同的接口
### 7. 下拉列表接口
**接口1** `ProjectDropdownList`
- 路径:`POST /business/project-dropdown-list`
- 功能:获取立项登记下拉列表
**接口2** `CaseDropdownList`
- 路径:`POST /business/case-dropdown-list`
- 功能:获取案件下拉列表
**分析:**
- 两个接口功能相似,都是获取下拉列表
- 但数据源不同ProjectRegistration vs Case
- 属于正常的功能分离,不是重复
**建议:**
- 保持现状,这是合理的接口设计
---
## 五、总结和建议
### 需要立即处理的重复接口:
1. **删除旧接口:** `user-register-detail`finance/urls.py 第7行
- 已有新接口 `unregistered-user-list` 替代
### 建议优化的接口:
2. **合并或明确分工:**
- `GetCaseListForInvoice``SearchCaseByContractNo`:建议合并或明确使用场景
- `issueAnInvoice``AddInvoice`:建议合并或明确使用场景
3. **重命名接口:**
- `scheduledetail``delete-schedule``schedule-delete`
- `LwaDetail``delete-lawyer-file`
### 可以保留的接口:
4. **功能重叠但使用场景不同:**
- `EditCase``SetCaseTags`:保留两个接口,但明确使用场景
---
## 六、接口统计
- **business模块** 约60个接口
- **finance模块** 约30个接口
- **User模块** 约20个接口
- **总计:** 约110个接口
**发现的重复/问题接口:** 6组
---
## 七、建议的优化方案
### 方案1删除明确的重复接口
- 删除 `user-register-detail` 路由
### 方案2统一接口命名规范
- 使用RESTful风格`resource-action` 格式
- 例如:`delete-schedule``delete-lawyer-file`
### 方案3合并功能相似的接口
-`GetCaseListForInvoice` 中添加搜索参数
-`issueAnInvoice` 中添加参数控制审批流程
### 方案4完善接口文档
- 明确每个接口的使用场景
- 标注接口的优先级(推荐使用/兼容保留)