diff --git a/User/views.py b/User/views.py index ca0aec8..04f2f3d 100644 --- a/User/views.py +++ b/User/views.py @@ -15,7 +15,7 @@ from django.contrib.sessions.backends.db import SessionStore from django.db.models import Count, Q from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from collections import defaultdict -from .utils import is_department_id, log_operation +from .utils import is_department_id, log_operation, get_approvers_from_record class CreateUserView(APIView): @@ -850,9 +850,10 @@ class roxyExhibition(APIView): # 排除关联的业务记录已被删除的审批记录 exclude_conditions = Q() - # 排除 type="案件管理" 且关联的案件已被删除的审批记录 + # 排除 type="案件管理" 或 "案件变更" 且关联的案件已被删除的审批记录 if deleted_case_ids_str: exclude_conditions |= Q(type="案件管理") & Q(user_id__in=deleted_case_ids_str) + exclude_conditions |= Q(type="案件变更") & Q(user_id__in=deleted_case_ids_str) # 排除 type="投标登记" 且关联的投标登记已被删除的审批记录 if deleted_bid_ids_str: @@ -1321,6 +1322,61 @@ class approvalProcessing(APIView): return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK) + if type == "案件变更": + 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) + + try: + case = Case.objects.get(id=approval.user_id, is_deleted=False) + except Case.DoesNotExist: + return Response({'status': 'error', 'message': '案件记录不存在或已被删除', 'code': 1}, status=status.HTTP_404_NOT_FOUND) + + current_approver = approval.personincharge + + if state == "未通过": + approval.state = "未通过" + case.state = "变更未通过" + approval.save(update_fields=['state']) + case.save(update_fields=['state']) + return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK) + + approvers_list = get_approvers_from_record(case, approval=approval) + if not approvers_list: + approval.state = "已通过" + case.state = "变更完成" + approval.save(update_fields=['state']) + case.save(update_fields=['state']) + return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK) + + try: + current_index = approvers_list.index(current_approver) + except ValueError: + approval.state = "已通过" + case.state = "变更完成" + approval.save(update_fields=['state']) + case.save(update_fields=['state']) + return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK) + + if current_index < len(approvers_list) - 1: + next_approver = approvers_list[current_index + 1] + approval.personincharge = next_approver + approval.state = "审核中" + if "当前审批人:" in approval.content: + approval.content = approval.content.replace( + f"当前审批人:{current_approver}", + f"当前审批人:{next_approver}" + ) + approval.save(update_fields=['state', 'personincharge', 'content']) + return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK) + + approval.state = "已通过" + case.state = "变更完成" + approval.save(update_fields=['state']) + case.save(update_fields=['state']) + return Response({'message': '处理成功', 'code': 0}, status=status.HTTP_200_OK) + if type == "申请用印": try: seal_app = SealApplication.objects.get(id=approval.user_id, is_deleted=False) diff --git a/business/migrations/0006_add_case_change_fields.py b/business/migrations/0006_add_case_change_fields.py new file mode 100644 index 0000000..5ef44f3 --- /dev/null +++ b/business/migrations/0006_add_case_change_fields.py @@ -0,0 +1,26 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("business", "0005_add_case_project_snapshot_fields"), + ] + + operations = [ + migrations.AddField( + model_name="case", + name="ChangeItem", + field=models.TextField(blank=True, null=True), + ), + migrations.AddField( + model_name="case", + name="ChangeReason", + field=models.TextField(blank=True, null=True), + ), + migrations.AddField( + model_name="case", + name="ChangeAgreement", + field=models.TextField(blank=True, null=True), + ), + ] diff --git a/business/models.py b/business/models.py index c8a5999..9ce45bd 100644 --- a/business/models.py +++ b/business/models.py @@ -54,6 +54,9 @@ class Case(models.Model): Contractreturn = models.TextField() # 合同返还 Closingapplication = models.TextField() # 结案申请 ChangeRequest = models.CharField(max_length=100) # 变更申请 + ChangeItem = models.TextField(null=True, blank=True) # 变更事项 + ChangeReason = models.TextField(null=True, blank=True) # 变更原因 + ChangeAgreement = models.TextField(null=True, blank=True) # 变更协议(URL列表) invoice_status = models.CharField(max_length=100, default='未开票') # 已开票 paymentcollection = models.CharField(max_length=100) # 已收款 state = models.CharField(max_length=100) # 状态 diff --git a/business/views.py b/business/views.py index d95fff2..a737ca2 100644 --- a/business/views.py +++ b/business/views.py @@ -3,7 +3,7 @@ from rest_framework.response import Response from rest_framework import status import json import ast -from User.models import User +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 @@ -37,6 +37,34 @@ def get_team_name_from_responsiblefor(responsiblefor): return None +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}提交了一份案件信息"] @@ -987,6 +1015,12 @@ class caseManagement(APIView): Contractreturn = request.FILES.getlist('Contractreturn') Closingapplication = request.FILES.getlist('Closingapplication') ChangeRequest = request.data.get('ChangeRequest') + ChangeItem = request.data.get('change_item') or request.data.get('ChangeItem') or ChangeRequest + ChangeReason = request.data.get('change_reason') or request.data.get('ChangeReason') + ChangeAgreement = request.FILES.getlist('ChangeAgreement') or request.FILES.getlist('change_agreement') + ChangeItem = request.data.get('change_item') or request.data.get('ChangeItem') or ChangeRequest + ChangeReason = request.data.get('change_reason') or request.data.get('ChangeReason') + ChangeAgreement = request.FILES.getlist('ChangeAgreement') or request.FILES.getlist('change_agreement') invoice_status = request.data.get('invoice_status') # 已开票 paymentcollection = request.data.get('paymentcollection') @@ -1020,6 +1054,7 @@ class caseManagement(APIView): project_responsiblefor = project_registration.responsiblefor if project_registration else None project_charge = project_registration.charge if project_registration else None # 创建新案件(在事务内创建,防止并发重复创建) + agreement_urls = flies(ChangeAgreement) if ChangeAgreement else [] case_id = Case.objects.create( project_id=project_id, contract_no=project_contract_no, @@ -1033,30 +1068,73 @@ class caseManagement(APIView): AgencyContract=json.dumps(agency_contract_list, ensure_ascii=False), Contractreturn=json.dumps(contract_return_list, ensure_ascii=False), Closingapplication=json.dumps(closing_application_list, ensure_ascii=False), - ChangeRequest=ChangeRequest, + ChangeRequest=ChangeItem or ChangeRequest or "", + ChangeItem=ChangeItem, + ChangeReason=ChangeReason, + ChangeAgreement=json.dumps(agreement_urls, ensure_ascii=False) if agreement_urls else "", invoice_status=invoice_status or '未开票', paymentcollection=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, times), - approval_type="案件管理", - user_id=case_id.id, - business_record=case_id, - today=times - ) - 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 ChangeItem or ChangeReason or ChangeAgreement: + if not all([ChangeItem, ChangeReason, ChangeAgreement]): + return Response({ + 'status': 'error', + 'message': '变更申请需要填写变更事项、变更原因并上传变更协议', + 'code': 1 + }, status=status.HTTP_400_BAD_REQUEST) + approvers_list = get_change_approvers() + if not approvers_list: + return Response({ + 'status': 'error', + 'message': '未找到管委会或行政部审批人,请先配置审批人', + 'code': 1 + }, status=status.HTTP_400_BAD_REQUEST) + 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 + approvers_str = " → ".join(approvers_list) + content = build_change_approval_content( + applicant_name, case_id, ChangeItem, ChangeReason, agreement_urls + ) + content = f"{content},审批流程:{approvers_str}(按顺序审批),当前审批人:{approvers_list[0]}" + Approval.objects.create( + title="案件变更申请", + content=content, + times=times, + personincharge=approvers_list[0], + state="审核中", + type="案件变更", + user_id=str(case_id.id) + ) + case_id.approvers_order = json.dumps(approvers_list, ensure_ascii=False) + case_id.state = "变更审核中" + case_id.save(update_fields=['approvers_order', 'state']) + else: + 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, times), + approval_type="案件管理", + user_id=case_id.id, + business_record=case_id, + today=times + ) + 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) # 如果已存在,事务结束,在事务外处理更新逻辑 @@ -1091,7 +1169,52 @@ class caseManagement(APIView): case.Closingapplication = json.dumps(flies(Closingapplication), ensure_ascii=False) update_fields_list.append('Closingapplication') - if ChangeRequest: + if ChangeItem or ChangeReason or ChangeAgreement: + if not all([ChangeItem, ChangeReason, ChangeAgreement]): + return Response({ + 'status': 'error', + 'message': '变更申请需要填写变更事项、变更原因并上传变更协议', + 'code': 1 + }, status=status.HTTP_400_BAD_REQUEST) + case.ChangeRequest = ChangeItem or ChangeRequest or "" + case.ChangeItem = ChangeItem + case.ChangeReason = ChangeReason + agreement_urls = flies(ChangeAgreement) + case.ChangeAgreement = json.dumps(agreement_urls, ensure_ascii=False) + case.state = "变更审核中" + update_fields_list.extend(['ChangeRequest', 'ChangeItem', 'ChangeReason', 'ChangeAgreement', 'state']) + approvers_list = get_change_approvers() + if not approvers_list: + return Response({ + 'status': 'error', + 'message': '未找到管委会或行政部审批人,请先配置审批人', + 'code': 1 + }, status=status.HTTP_400_BAD_REQUEST) + 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 + approvers_str = " → ".join(approvers_list) + content = build_change_approval_content( + applicant_name, case, ChangeItem, ChangeReason, agreement_urls + ) + content = f"{content},审批流程:{approvers_str}(按顺序审批),当前审批人:{approvers_list[0]}" + Approval.objects.create( + title="案件变更申请", + content=content, + times=times or case.times, + personincharge=approvers_list[0], + state="审核中", + type="案件变更", + user_id=str(case.id) + ) + case.approvers_order = json.dumps(approvers_list, ensure_ascii=False) + update_fields_list.append('approvers_order') + elif ChangeRequest: case.ChangeRequest = ChangeRequest case.state = "审核中" update_fields_list.extend(['ChangeRequest', 'state']) @@ -1289,7 +1412,10 @@ class caseManagementDetail(APIView): "AgencyContract": info.AgencyContract, # 代理合同 "Contractreturn": info.Contractreturn, # 合同返还 "Closingapplication": info.Closingapplication, # 结案申请 - "ChangeRequest": info.ChangeRequest, # 变更申请 + "ChangeRequest": info.ChangeRequest, # 变更申请(兼容旧字段) + "ChangeItem": info.ChangeItem, + "ChangeReason": info.ChangeReason, + "ChangeAgreement": info.ChangeAgreement, "invoice_status": info.invoice_status, # 已开票 "paymentcollection": info.paymentcollection, # 已收款 "state": info.state, @@ -1359,7 +1485,53 @@ class EditCase(APIView): case.Closingapplication = json.dumps(flies(Closingapplication), ensure_ascii=False) update_fields_list.append('Closingapplication') - if ChangeRequest: + if ChangeItem or ChangeReason or ChangeAgreement: + if not all([ChangeItem, ChangeReason, ChangeAgreement]): + return Response({ + 'status': 'error', + 'message': '变更申请需要填写变更事项、变更原因并上传变更协议', + 'code': 1 + }, status=status.HTTP_400_BAD_REQUEST) + agreement_urls = flies(ChangeAgreement) + case.ChangeRequest = ChangeItem if ChangeItem else ChangeRequest + case.ChangeItem = ChangeItem + case.ChangeReason = ChangeReason + case.ChangeAgreement = json.dumps(agreement_urls, ensure_ascii=False) + case.state = "变更审核中" + update_fields_list.extend(['ChangeRequest', 'ChangeItem', 'ChangeReason', 'ChangeAgreement', 'state']) + + approvers_list = get_change_approvers() + if not approvers_list: + return Response({ + 'status': 'error', + 'message': '未找到管委会或行政部审批人,请先配置审批人', + 'code': 1 + }, status=status.HTTP_400_BAD_REQUEST) + 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 + approvers_str = " → ".join(approvers_list) + content = build_change_approval_content( + applicant_name, case, ChangeItem, ChangeReason, agreement_urls + ) + content = f"{content},审批流程:{approvers_str}(按顺序审批),当前审批人:{approvers_list[0]}" + Approval.objects.create( + title="案件变更申请", + content=content, + times=times or case.times, + personincharge=approvers_list[0], + state="审核中", + type="案件变更", + user_id=str(case.id) + ) + case.approvers_order = json.dumps(approvers_list, ensure_ascii=False) + update_fields_list.append('approvers_order') + elif ChangeRequest: case.ChangeRequest = ChangeRequest case.state = "审核中" update_fields_list.extend(['ChangeRequest', 'state']) diff --git a/案件管理模块接口文档.md b/案件管理模块接口文档.md index 23b5577..ee4fa66 100644 --- a/案件管理模块接口文档.md +++ b/案件管理模块接口文档.md @@ -231,7 +231,7 @@ token: {用户token} **请求头:** ``` -Content-Type: application/json +Content-Type: multipart/form-data token: {用户token} ``` @@ -789,18 +789,23 @@ token: {用户token} | AgencyContract | File[] | 否 | 代理合同文件 | | Contractreturn | File[] | 否 | 合同返还文件 | | Closingapplication | File[] | 否 | 结案申请文件 | -| ChangeRequest | String | 否 | 变更申请(如果传入会触发审核流程) | +| change_item / ChangeItem | String | 否 | 变更事项(律师申请时必填) | +| change_reason / ChangeReason | String | 否 | 变更原因(律师申请时必填) | +| ChangeAgreement | File[] | 否 | 变更协议文件(律师申请时必填) | | invoice_status | String | 否 | 已开票状态(默认:未开票) | | paymentcollection | String | 否 | 已收款金额 | | approvers | Array/String | 否 | 审核人列表(团队类型时需要,推荐ID数组,兼容用户名数组/逗号字符串) | | personincharge | String | 否 | 审核人(兼容旧接口,会转为approvers) | +**说明:** 若提交变更申请,需同时填写变更事项、变更原因并上传变更协议。 + **请求示例:** ```json { "project_id": 1, "times": "2024-01-15", - "ChangeRequest": "需要变更代理方案", + "change_item": "代理方案变更", + "change_reason": "客户要求调整代理范围", "invoice_status": "已开票", "paymentcollection": "50000" } @@ -979,7 +984,10 @@ token: {用户token} "AgencyContract": "[\"http://example.com/agency.pdf\"]", "Contractreturn": "[\"http://example.com/return.pdf\"]", "Closingapplication": "[\"http://example.com/closing.pdf\"]", - "ChangeRequest": "需要变更代理方案", + "ChangeRequest": "代理方案变更", + "ChangeItem": "代理方案变更", + "ChangeReason": "客户要求调整代理范围", + "ChangeAgreement": "[\"http://example.com/change-agreement.pdf\"]", "invoice_status": "已开票", "paymentcollection": "50000", "state": "审核中", @@ -1013,17 +1021,22 @@ token: {用户token} | AgencyContractUrls / agency_contract_urls | String/Array | 否 | 代理合同URL列表(JSON数组字符串/逗号分隔/数组) | | ContractreturnUrls / contractreturn_urls | String/Array | 否 | 合同返还URL列表(JSON数组字符串/逗号分隔/数组) | | ClosingapplicationUrls / closingapplication_urls | String/Array | 否 | 结案申请URL列表(JSON数组字符串/逗号分隔/数组) | -| ChangeRequest | String | 否 | 变更申请(如果传入会触发审核流程) | +| change_item / ChangeItem | String | 否 | 变更事项(律师申请时必填) | +| change_reason / ChangeReason | String | 否 | 变更原因(律师申请时必填) | +| ChangeAgreement | File[] | 否 | 变更协议文件(律师申请时必填) | | invoice_status | String | 否 | 已开票状态 | | paymentcollection | String | 否 | 已收款金额 | | approvers | Array/String | 否 | 审核人列表(团队类型时需要,推荐ID数组,兼容用户名数组/逗号字符串) | | personincharge | String | 否 | 审核人(兼容旧接口,会转为approvers) | +**说明:** 若提交变更申请,需同时填写变更事项、变更原因并上传变更协议。 + **请求示例:** ```json { "id": 1, - "ClosingapplicationUrls": "[\"http://example.com/closing.pdf\"]", + "change_item": "代理方案变更", + "change_reason": "客户要求调整代理范围", "invoice_status": "已开票", "paymentcollection": "60000" } @@ -1395,7 +1408,7 @@ token: {用户token} 3. **合同编号必须唯一**,不能重复 4. **案件创建后会自动关联立项登记**,无法修改关联关系 5. **删除立项登记前**,需要先删除关联的案件 -6. **变更申请会触发审核流程**,需要提供审核人 +6. **变更申请会触发审核流程**:由管委会或行政部审批,同意后完成变更;需填写变更事项、变更原因并上传变更协议 7. **文件上传支持多文件**,返回的是JSON字符串格式的URL数组 8. **投标登记创建时会触发审核流程**,团队类型需要提供审核人 9. **负责人信息格式**: