From b8ec7897e6e14d058463a9081be0e1b486e6e223 Mon Sep 17 00:00:00 2001 From: 27942 Date: Tue, 20 Jan 2026 14:39:10 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=A1=88=E4=BB=B6=E6=A8=A1?= =?UTF-8?q?=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- User/urls.py | 3 +- User/views.py | 126 +++++++++- 财务查看功能使用说明.md | 513 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 636 insertions(+), 6 deletions(-) create mode 100644 财务查看功能使用说明.md diff --git a/User/urls.py b/User/urls.py index 8d6dc9f..306b48d 100644 --- a/User/urls.py +++ b/User/urls.py @@ -1,5 +1,5 @@ from django.urls import path -from .views import CreateUserView,LoginView,EditorialStaffView,PersonnelDetailsView,DepartmentView,PersonnelListView,AddDepartment,DeleteDepartment,Personlist,roxyExhibition,approvalProcessing,personneldisplay,DeleteUser,OperationLogView,TeamView,TeamListView,AddTeam,EditTeam,DeleteTeam,ChangePasswordView +from .views import CreateUserView,LoginView,EditorialStaffView,PersonnelDetailsView,DepartmentView,PersonnelListView,AddDepartment,DeleteDepartment,Personlist,roxyExhibition,approvalProcessing,personneldisplay,DeleteUser,OperationLogView,TeamView,TeamListView,AddTeam,EditTeam,DeleteTeam,ChangePasswordView,ApprovalStatusCheck urlpatterns = [ path('create-user',CreateUserView.as_view(),name='create-user'), path('login',LoginView.as_view(),name='login'), @@ -12,6 +12,7 @@ urlpatterns = [ path('personlist',Personlist.as_view(),name='Personlist'), path('roxyexhibition',roxyExhibition.as_view(),name='roxyExhibition'), path('approval_processing',approvalProcessing.as_view(),name='approval_processing'), + path('approval-status-check',ApprovalStatusCheck.as_view(),name='approval-status-check'), path('personneldisplay',personneldisplay.as_view(),name='personneldisplay'), path('deleteUser',DeleteUser.as_view(),name='deleteUser'), path('operation-log',OperationLogView.as_view(),name='operation-log'), diff --git a/User/views.py b/User/views.py index 4658479..2fd76b0 100644 --- a/User/views.py +++ b/User/views.py @@ -1048,6 +1048,7 @@ class approvalProcessing(APIView): def post(self, request, *args, **kwargs): """ 消除代办 + 财务查看时,只需要传type和id,不需要传state(默认为查看通过) :param request: :param args: :param kwargs: @@ -1057,14 +1058,21 @@ class approvalProcessing(APIView): type = request.data.get('type') id = request.data.get('id') - if not all([state, type, id]): - return Response({'status': 'error', 'message': '缺少参数', 'code': 1}, status=status.HTTP_400_BAD_REQUEST) + if not all([type, id]): + return Response({'status': 'error', 'message': '缺少参数type或id', 'code': 1}, status=status.HTTP_400_BAD_REQUEST) try: approval = Approval.objects.get(id=id, is_deleted=False) except Approval.DoesNotExist: return Response({'status': 'error', 'message': '审批记录不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND) + # 财务查看逻辑:如果只传了type和id,没有传state,且当前是财务审核,则默认为查看通过 + from User.utils import is_finance_personincharge + is_finance_view = False + if not state and is_finance_personincharge(approval.personincharge) and approval.state == "已抄送财务": + state = "已通过" # 财务查看默认通过 + is_finance_view = True + if type == "入职财务登记": try: user = User.objects.get(id=approval.user_id, is_deleted=False) @@ -1073,7 +1081,10 @@ class approvalProcessing(APIView): # 检查当前是否已经是财务审核 if is_finance_personincharge(approval.personincharge) and approval.state == "已抄送财务": - # 财务部审核逻辑:财务部只需要一个人审核完即可完成 + # 财务部审核逻辑:如果只传了type和id,不传state,则默认为"已通过" + if not state: + state = "已通过" + if state == "已通过": approval.state = "已通过" user.state = "在职" @@ -1085,6 +1096,10 @@ class approvalProcessing(APIView): return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK) # 使用统一的审核流程处理函数 + # 非财务查看时,state参数是必填的 + if not state: + return Response({'status': 'error', 'message': '缺少参数state(审核状态:已通过/未通过)', 'code': 1}, status=status.HTTP_400_BAD_REQUEST) + from User.utils import process_approval_flow import logging logger = logging.getLogger(__name__) @@ -1124,7 +1139,10 @@ class approvalProcessing(APIView): # 检查当前是否已经是财务审核 if is_finance_personincharge(approval.personincharge) and approval.state == "已抄送财务": - # 财务部审核逻辑:财务部只需要一个人审核完即可完成 + # 财务部审核逻辑:如果只传了type和id,不传state,则默认为"已通过" + if not state: + state = "已通过" + if state == "已通过": approval.state = "已通过" invoice.state = "已通过" @@ -1136,6 +1154,10 @@ class approvalProcessing(APIView): return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK) # 使用统一的审核流程处理函数 + # 非财务查看时,state参数是必填的 + if not state: + return Response({'status': 'error', 'message': '缺少参数state(审核状态:已通过/未通过)', 'code': 1}, status=status.HTTP_400_BAD_REQUEST) + from User.utils import process_approval_flow current_approver = approval.personincharge is_completed, error = process_approval_flow( @@ -1169,7 +1191,11 @@ class approvalProcessing(APIView): approval.save(update_fields=['state']) return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK) else: - # 财务查看通过,状态保持"已通过"(已经是"已通过") + # 财务查看通过(默认或不传state),状态保持"已通过"(已经是"已通过") + # 如果审批记录状态不是"已通过",更新为"已通过"(表示财务已查看) + if approval.state != "已通过": + approval.state = "已通过" + approval.save(update_fields=['state']) return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK) # 负责人填写收入分配 @@ -1229,6 +1255,10 @@ class approvalProcessing(APIView): income.save(update_fields=['allocate']) # 使用统一的审核流程处理函数(兼容旧流程) + # 非财务查看时,state参数是必填的 + if not state: + return Response({'status': 'error', 'message': '缺少参数state(审核状态:已通过/未通过)', 'code': 1}, status=status.HTTP_400_BAD_REQUEST) + from User.utils import process_approval_flow current_approver = approval.personincharge is_completed, error = process_approval_flow( @@ -2017,6 +2047,92 @@ class DeleteTeam(APIView): return Response({'message': '删除成功', 'code': 0}, status=status.HTTP_200_OK) +class ApprovalStatusCheck(APIView): + """查询待办是否已经审核完全通过""" + + def post(self, request, *args, **kwargs): + """ + 查询待办的审核状态,判断是否已经审核完全通过 + :param request: + :param args: + :param kwargs: + :return: + """ + type = request.data.get('type') + id = request.data.get('id') + + if not all([type, id]): + return Response({'status': 'error', 'message': '缺少参数type或id', 'code': 1}, status=status.HTTP_400_BAD_REQUEST) + + try: + approval = Approval.objects.get(id=id, is_deleted=False) + except Approval.DoesNotExist: + return Response({'status': 'error', 'message': '审批记录不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND) + + # 获取业务记录状态 + business_state = None + is_approved = False + + try: + if type == "收入确认": + from finance.models import Income + try: + income = Income.objects.get(id=approval.user_id, is_deleted=False) + business_state = income.state + is_approved = (income.state == "已通过") + except Income.DoesNotExist: + pass + elif type == "开票": + from finance.models import Invoice + try: + invoice = Invoice.objects.get(id=approval.user_id, is_deleted=False) + business_state = invoice.state + is_approved = (invoice.state == "已通过") + except Invoice.DoesNotExist: + pass + elif type == "付款申请": + from finance.models import Payment + try: + payment = Payment.objects.get(id=approval.user_id, is_deleted=False) + business_state = payment.state + is_approved = (payment.state == "已通过") + except Payment.DoesNotExist: + pass + elif type == "报销申请": + from finance.models import Reimbursement + try: + reimbursement = Reimbursement.objects.get(id=approval.user_id, is_deleted=False) + business_state = reimbursement.state + is_approved = (reimbursement.state == "已完成") + except Reimbursement.DoesNotExist: + pass + elif type == "案件管理": + from business.models import Case + try: + case = Case.objects.get(id=approval.user_id, is_deleted=False) + business_state = case.state + is_approved = (case.state == "已通过") + except Case.DoesNotExist: + pass + # 可以根据需要添加其他类型 + except Exception as e: + import logging + logger = logging.getLogger(__name__) + logger.error(f"查询业务记录状态失败: {str(e)}") + + return Response({ + 'message': '查询成功', + 'code': 0, + 'data': { + 'approval_id': approval.id, + 'approval_state': approval.state, + 'business_state': business_state, + 'is_approved': is_approved, # 是否已经审核完全通过 + 'type': type + } + }, status=status.HTTP_200_OK) + + class ChangePasswordView(APIView): """修改密码接口""" diff --git a/财务查看功能使用说明.md b/财务查看功能使用说明.md new file mode 100644 index 0000000..f3a6db8 --- /dev/null +++ b/财务查看功能使用说明.md @@ -0,0 +1,513 @@ +# 财务查看功能使用说明 + +## 概述 + +收入确认在抄送财务时,状态已经是"已通过",财务只是查看,不需要再次审核。如果财务发现问题,可以标记为"未通过"。 + +## 审批接口 + +**接口地址:** `POST /user/approval_processing` + +**接口描述:** 处理审批待办,支持审核通过、审核未通过等操作。 + +### 请求参数 + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| token | String | 是 | 用户认证token(请求头) | +| id | Integer | 是 | 审批记录ID(从待办列表获取) | +| type | String | 是 | 审批类型,收入确认为 `"收入确认"` | +| state | String | 否 | 审核状态:`"已通过"` 或 `"未通过"`
**财务查看时可不传**(默认为"已通过")
**非财务审核时必填** | +| allocate | String | 否 | 收入分配(仅当状态为"待负责人填写分配"时必填) | + +### 请求示例 + +#### 1. 财务查看收入确认(默认通过,推荐方式) + +```json +{ + "id": 10, + "type": "收入确认" +} +``` + +**说明:** +- 财务查看时,只需要传 `id` 和 `type`,不需要传 `state` +- 系统会自动判断:如果是财务查看(`personincharge` 为财务且 `state` 为"已抄送财务"),默认 `state` 为 `"已通过"` +- 接口会直接返回成功,状态保持"已通过"(因为已经是"已通过") + +#### 2. 财务标记为未通过(发现问题) + +```json +{ + "id": 10, + "type": "收入确认", + "state": "未通过" +} +``` + +**说明:** +- 如果财务发现问题,需要传 `state: "未通过"` +- 接口会将收入确认状态更新为"未通过" +- 审批记录状态也会更新为"未通过" + +#### 3. 负责人填写收入分配 + +```json +{ + "id": 10, + "type": "收入确认", + "state": "已通过", + "allocate": "50%分配给A,50%分配给B" +} +``` + +**说明:** +- 当收入确认状态为"待负责人填写分配"时,负责人必须填写 `allocate` 字段 +- 填写后,状态自动变为"已通过",并抄送财务 +- 财务收到时状态已经是"已通过",财务只是查看 + +### 返回数据 + +**成功响应:** +```json +{ + "message": "处理成功", + "code": 0 +} +``` + +**失败响应:** +```json +{ + "status": "error", + "message": "收入确认记录不存在或已被删除", + "code": 1 +} +``` + +--- + +## 查询待办审核状态 + +**接口地址:** `POST /user/approval-status-check` + +**接口描述:** 查询待办是否已经审核完全通过,用于前端判断待办状态。 + +### 请求参数 + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| token | String | 是 | 用户认证token(请求头) | +| id | Integer | 是 | 审批记录ID | +| type | String | 是 | 审批类型,如 `"收入确认"`、`"开票"` 等 | + +### 请求示例 + +```json +{ + "id": 10, + "type": "收入确认" +} +``` + +### 返回数据 + +**成功响应:** +```json +{ + "message": "查询成功", + "code": 0, + "data": { + "approval_id": 10, + "approval_state": "已抄送财务", + "business_state": "已通过", + "is_approved": true, + "type": "收入确认" + } +} +``` + +### 字段说明 + +- **approval_id**:审批记录ID +- **approval_state**:审批记录状态(审核中/已抄送财务/已通过/未通过) +- **business_state**:业务记录状态(如收入确认的状态) +- **is_approved**:是否已经审核完全通过 + - `true`:业务记录状态为"已通过"(或"已完成") + - `false`:业务记录状态不是"已通过" +- **type**:审批类型 + +--- + +## 获取待办列表 + +**接口地址:** `POST /user/roxyexhibition` + +**接口描述:** 获取当前用户的待办列表,包括收入确认的待办事项。 + +### 请求参数 + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| token | String | 是 | 用户认证token(请求头) | +| page | Integer | 是 | 页码(从1开始) | +| per_page | Integer | 是 | 每页数量 | +| type | String | 否 | 筛选类型,如 `"收入确认"` | + +### 请求示例 + +```json +{ + "page": 1, + "per_page": 10, + "type": "收入确认" +} +``` + +### 返回数据 + +**成功响应:** +```json +{ + "message": "展示成功", + "total": 5, + "code": 0, + "data": [ + { + "id": 10, + "title": "张三提交收入确认", + "content": "张三在2024-01-15提交了收入确认,合同编号:HT2024001,客户名称:某某公司,收款金额:10000,收入分配:待负责人指定,已抄送财务部", + "times": "2024-01-15", + "personincharge": "财务", + "state": "已抄送财务", + "type": "收入确认", + "user_id": "1" + } + ] +} +``` + +### 字段说明 + +- **id**:审批记录ID,用于调用审批处理接口 +- **type**:审批类型,收入确认为 `"收入确认"` +- **state**:审批状态 + - `"审核中"`:正在审核中 + - `"已抄送财务"`:已抄送财务,财务可以查看 + - `"已通过"`:审核通过 + - `"未通过"`:审核未通过 +- **personincharge**:当前负责人 + - 如果是财务部门ID或"财务",表示已抄送财务 +- **user_id**:关联的业务记录ID(收入确认记录ID) + +--- + +## 财务查看流程 + +### 场景1:个人团队提交的收入确认 + +1. **提交阶段**: + - 用户提交收入确认 + - 状态直接设为 `"已通过"` + - 直接抄送财务,审批状态为 `"已抄送财务"` + +2. **财务查看**: + - 财务在待办列表中看到该收入确认 + - 调用审批接口,`state` 传 `"已通过"`(表示查看通过) + - 接口直接返回成功,状态保持 `"已通过"` + +3. **财务发现问题**: + - 如果财务发现问题,调用审批接口,`state` 传 `"未通过"` + - 收入确认状态更新为 `"未通过"` + - 审批记录状态也更新为 `"未通过"` + +### 场景2:团队类型提交的收入确认 + +1. **提交阶段**: + - 用户提交收入确认 + - 状态为 `"审核中"` + - 创建待办事项,第一个审核人审核 + +2. **审核阶段**: + - 审核人依次审核(按顺序) + - 所有审核人通过后,状态变为 `"待负责人填写分配"` + +3. **负责人填写分配**: + - 负责人调用审批接口,填写 `allocate` 字段 + - 状态自动变为 `"已通过"` + - 抄送财务,审批状态为 `"已抄送财务"` + +4. **财务查看**: + - 财务在待办列表中看到该收入确认 + - 调用审批接口,`state` 传 `"已通过"`(表示查看通过) + - 接口直接返回成功,状态保持 `"已通过"` + +--- + +## 代码示例 + +### 前端调用示例(JavaScript) + +```javascript +// 1. 获取待办列表 +async function getTodoList(page = 1, perPage = 10) { + const response = await fetch('/user/roxyexhibition', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'token': localStorage.getItem('token') + }, + body: JSON.stringify({ + page: page, + per_page: perPage, + type: '收入确认' // 可选,筛选收入确认类型 + }) + }); + const data = await response.json(); + return data; +} + +// 2. 查询待办审核状态(判断是否已审核通过) +async function checkApprovalStatus(approvalId, type = '收入确认') { + const response = await fetch('/user/approval-status-check', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'token': localStorage.getItem('token') + }, + body: JSON.stringify({ + id: approvalId, + type: type + }) + }); + const data = await response.json(); + return data; +} + +// 3. 财务查看通过(默认操作,推荐方式) +async function financeViewPass(approvalId, type = '收入确认') { + const response = await fetch('/user/approval_processing', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'token': localStorage.getItem('token') + }, + body: JSON.stringify({ + id: approvalId, + type: type + // 不传state,财务查看时默认为"已通过" + }) + }); + const data = await response.json(); + return data; +} + +// 4. 财务标记为未通过(发现问题) +async function financeMarkReject(approvalId, type = '收入确认') { + const response = await fetch('/user/approval_processing', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'token': localStorage.getItem('token') + }, + body: JSON.stringify({ + id: approvalId, + type: type, + state: '未通过' // 财务发现问题,标记为未通过 + }) + }); + const data = await response.json(); + return data; +} + +// 5. 负责人填写收入分配 +async function fillAllocate(approvalId, allocate) { + const response = await fetch('/user/approval_processing', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'token': localStorage.getItem('token') + }, + body: JSON.stringify({ + id: approvalId, + type: '收入确认', + state: '已通过', + allocate: allocate // 收入分配,如:"50%分配给A,50%分配给B" + }) + }); + const data = await response.json(); + return data; +} +``` + +--- + +## 状态说明 + +### 收入确认状态(Income.state) + +| 状态 | 说明 | 何时出现 | +|------|------|----------| +| `"审核中"` | 正在审核中 | 团队类型,审核人正在审核 | +| `"待负责人填写分配"` | 待负责人填写收入分配 | 所有审核人通过后 | +| `"已通过"` | 审核通过 | 抄送财务时就已经是"已通过",财务只是查看 | +| `"未通过"` | 审核未通过 | 审核人未通过或财务发现问题 | + +### 审批记录状态(Approval.state) + +| 状态 | 说明 | 何时出现 | +|------|------|----------| +| `"审核中"` | 正在审核中 | 审核人正在审核 | +| `"已抄送财务"` | 已抄送财务 | 抄送财务时(收入确认状态已经是"已通过") | +| `"已通过"` | 审批通过 | 财务查看通过或审批完成 | +| `"未通过"` | 审批未通过 | 审核人未通过或财务标记为未通过 | + +--- + +## 注意事项 + +1. **财务查看逻辑**: + - 抄送财务时,收入确认状态已经是 `"已通过"` + - 财务调用审批接口时,`state` 传 `"已通过"` 表示查看通过,不会改变状态 + - 只有财务标记为 `"未通过"` 时,才会改变状态 + +2. **权限判断**: + - 接口会自动判断当前用户是否是财务部门 + - 只有财务部门的人员才能看到 `personincharge` 为财务的待办 + +3. **状态流转**: + - 个人团队:提交 → `"已通过"` → 抄送财务 + - 团队类型:提交 → `"审核中"` → `"待负责人填写分配"` → `"已通过"` → 抄送财务 + +4. **金额统计**: + - 只有状态为 `"已通过"` 的收入确认才会计入金额统计 + - 抄送财务时已经是 `"已通过"`,所以会立即计入统计 + +--- + +## 完整流程示例 + +### 示例1:个人团队提交收入确认 + +```javascript +// 1. 用户提交收入确认 +const submitResponse = await fetch('/finance/confirm', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'token': userToken + }, + body: JSON.stringify({ + case_id: 123, + times: '2024-01-15', + amount: '10000' + }) +}); + +// 返回:{ state: "已通过", approval_id: 10 } + +// 2. 财务获取待办列表 +const todoList = await getTodoList(1, 10); +// 财务看到:{ id: 10, type: "收入确认", state: "已抄送财务", personincharge: "财务" } + +// 3. 财务查看通过 +const viewResponse = await financeViewPass(10); +// 返回:{ message: "处理成功", code: 0 } +// 收入确认状态保持 "已通过" +``` + +### 示例2:团队类型提交收入确认 + +```javascript +// 1. 用户提交收入确认 +const submitResponse = await fetch('/finance/confirm', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'token': userToken + }, + body: JSON.stringify({ + case_id: 123, + times: '2024-01-15', + amount: '10000', + approvers: [1, 2, 3] // 审核人列表 + }) +}); + +// 返回:{ state: "审核中", approval_id: 10 } + +// 2. 审核人1审核通过 +await fetch('/user/approval_processing', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'token': approver1Token + }, + body: JSON.stringify({ + id: 10, + type: '收入确认', + state: '已通过' + }) +}); + +// 3. 审核人2审核通过 +// ...(同上) + +// 4. 审核人3审核通过后,状态变为 "待负责人填写分配" + +// 5. 负责人填写收入分配 +await fillAllocate(10, '50%分配给A,50%分配给B'); +// 返回:{ state: "已通过" },已抄送财务 + +// 6. 查询待办审核状态 +const statusResponse = await checkApprovalStatus(10, '收入确认'); +// 返回:{ is_approved: true, business_state: "已通过" } + +// 7. 财务查看通过(只需要传id和type) +await financeViewPass(10, '收入确认'); +// 返回:{ message: "处理成功", code: 0 } +``` + +--- + +## 接口地址汇总 + +| 接口 | 地址 | 说明 | +|------|------|------| +| 获取待办列表 | `POST /user/roxyexhibition` | 获取当前用户的待办事项 | +| 查询待办审核状态 | `POST /user/approval-status-check` | 查询待办是否已经审核完全通过 | +| 审批处理 | `POST /user/approval_processing` | 处理审批待办(审核通过/未通过)
**财务查看时只需传id和type** | +| 新增收入确认 | `POST /finance/confirm` | 创建收入确认记录 | +| 收入确认列表 | `POST /finance/confirmdisplay` | 查询收入确认列表 | + +--- + +## 常见问题 + +### Q1: 财务如何知道哪些收入确认需要查看? + +**A:** 财务在待办列表接口中,筛选 `type="收入确认"` 且 `state="已抄送财务"` 的记录。 + +### Q2: 财务查看时,`state` 参数应该传什么? + +**A:** +- **推荐方式**:不传 `state` 参数,只传 `id` 和 `type`,系统会自动判断为财务查看通过 +- 如果发现问题,传 `state: "未通过"`,状态会更新为 `"未通过"` + +### Q3: 收入确认什么时候状态变为"已通过"? + +**A:** +- 个人团队:提交时就是 `"已通过"` +- 团队类型:负责人填写收入分配后,状态变为 `"已通过"`,然后抄送财务 + +### Q4: 财务可以修改收入确认的金额吗? + +**A:** 不可以。财务只能查看,如果发现问题,可以标记为 `"未通过"`。如果需要修改,应该联系提交人重新提交。 + +### Q5: 如何判断当前用户是否是财务? + +**A:** 系统会自动判断: +- 用户的部门包含"财务部" +- 用户的角色包含"财务部" +- 审批记录的 `personincharge` 是财务部门ID或"财务"字符串