You've already forked opc-backend
488 lines
21 KiB
Python
488 lines
21 KiB
Python
|
|
from django.db import models
|
|||
|
|
from rest_framework import viewsets, permissions, status
|
|||
|
|
from rest_framework.response import Response
|
|||
|
|
from rest_framework.decorators import action
|
|||
|
|
from .models import Task, TaskApplication, TaskInvitation, TaskStatus, ApplyStatus, InvitationStatus
|
|||
|
|
from .serializers import TaskSerializer, TaskApplicationSerializer, TaskInvitationSerializer
|
|||
|
|
|
|||
|
|
class TaskInvitationViewSet(viewsets.ModelViewSet):
|
|||
|
|
"""
|
|||
|
|
@author: xujl
|
|||
|
|
Api说明: 任务主动邀请接口视图。企业用户可主动向OPC专家发送定向任务合作邀请,专家可接受或拒绝该邀请。
|
|||
|
|
"""
|
|||
|
|
serializer_class = TaskInvitationSerializer
|
|||
|
|
permission_classes = [permissions.IsAuthenticated]
|
|||
|
|
|
|||
|
|
def get_queryset(self):
|
|||
|
|
user = self.request.user
|
|||
|
|
enterprise, _ = _get_user_enterprise(user)
|
|||
|
|
if enterprise:
|
|||
|
|
# Experts see invitations sent to them
|
|||
|
|
# Enterprises see invitations they sent for their tasks
|
|||
|
|
return TaskInvitation.objects.filter(
|
|||
|
|
models.Q(expert=user) | models.Q(task__enterprise=enterprise)
|
|||
|
|
).distinct().order_by('-created_at')
|
|||
|
|
else:
|
|||
|
|
return TaskInvitation.objects.filter(
|
|||
|
|
models.Q(expert=user) | models.Q(task__publisher=user)
|
|||
|
|
).distinct().order_by('-created_at')
|
|||
|
|
|
|||
|
|
def perform_create(self, serializer):
|
|||
|
|
task = serializer.validated_data['task']
|
|||
|
|
expert = serializer.validated_data['expert']
|
|||
|
|
if TaskInvitation.objects.filter(task=task, expert=expert).exists():
|
|||
|
|
from rest_framework.exceptions import ValidationError
|
|||
|
|
raise ValidationError('已经向该专家发送过邀约')
|
|||
|
|
serializer.save()
|
|||
|
|
|
|||
|
|
|
|||
|
|
@action(detail=True, methods=['post'])
|
|||
|
|
def accept(self, request, pk=None):
|
|||
|
|
invitation = self.get_object()
|
|||
|
|
if invitation.status == InvitationStatus.ACCEPTED:
|
|||
|
|
return Response({'detail': '您已接受过此邀约'}, status=status.HTTP_400_BAD_REQUEST)
|
|||
|
|
|
|||
|
|
task = invitation.task
|
|||
|
|
invitation.status = InvitationStatus.ACCEPTED
|
|||
|
|
invitation.save()
|
|||
|
|
|
|||
|
|
from django.utils import timezone
|
|||
|
|
|
|||
|
|
history = []
|
|||
|
|
if invitation.message:
|
|||
|
|
history.append({
|
|||
|
|
'sender_id': str(task.publisher.id),
|
|||
|
|
'sender_name': task.publisher.nickname or task.publisher.username,
|
|||
|
|
'sender_avatar': task.publisher.avatar_url,
|
|||
|
|
'sender_role': 'ENTERPRISE',
|
|||
|
|
'content': invitation.message,
|
|||
|
|
'created_at': invitation.created_at.isoformat()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if isinstance(invitation.messages, list):
|
|||
|
|
history.extend(invitation.messages)
|
|||
|
|
history.append({
|
|||
|
|
'sender_id': str(request.user.id),
|
|||
|
|
'sender_name': 'System',
|
|||
|
|
'sender_avatar': '',
|
|||
|
|
'sender_role': 'SYSTEM',
|
|||
|
|
'content': f'【系统记录】专家 {request.user.nickname or request.user.username} 已同意承接并加入了项目',
|
|||
|
|
'created_at': timezone.now().isoformat()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
# Create an application record so the enterprise can track this accepted expert
|
|||
|
|
application, created = TaskApplication.objects.get_or_create(
|
|||
|
|
task=task,
|
|||
|
|
applicant=request.user,
|
|||
|
|
defaults={
|
|||
|
|
'status': ApplyStatus.APPROVED,
|
|||
|
|
'cover_letter': '接受了企业发出的定向邀约',
|
|||
|
|
'expected_price': task.budget_max or 0,
|
|||
|
|
'expected_days': 0,
|
|||
|
|
'negotiation_history': history
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
if not created:
|
|||
|
|
if application.status != ApplyStatus.APPROVED:
|
|||
|
|
application.status = ApplyStatus.APPROVED
|
|||
|
|
# If the application existed but was PENDING or something, we should still update its negotiation history
|
|||
|
|
application.negotiation_history = history
|
|||
|
|
application.save()
|
|||
|
|
|
|||
|
|
if task.status in [TaskStatus.OPEN, TaskStatus.DRAFT]:
|
|||
|
|
task.status = TaskStatus.IN_PROGRESS
|
|||
|
|
task.save()
|
|||
|
|
|
|||
|
|
return Response({'status': '已接受邀约,项目进入进行中状态'})
|
|||
|
|
|
|||
|
|
@action(detail=True, methods=['post'])
|
|||
|
|
def reject(self, request, pk=None):
|
|||
|
|
invitation = self.get_object()
|
|||
|
|
invitation.status = InvitationStatus.REJECTED
|
|||
|
|
invitation.save()
|
|||
|
|
return Response({'status': '已拒绝邀约'})
|
|||
|
|
|
|||
|
|
@action(detail=True, methods=['post'])
|
|||
|
|
def discuss(self, request, pk=None):
|
|||
|
|
invitation = self.get_object()
|
|||
|
|
user = request.user
|
|||
|
|
|
|||
|
|
# Verify user is either the expert or enterprise admin/publisher
|
|||
|
|
enterprise, role = _get_user_enterprise(user)
|
|||
|
|
is_enterprise = False
|
|||
|
|
if user == invitation.expert:
|
|||
|
|
sender_role = 'EXPERT'
|
|||
|
|
elif user == invitation.task.publisher or (enterprise and invitation.task.enterprise == enterprise):
|
|||
|
|
sender_role = 'ENTERPRISE'
|
|||
|
|
is_enterprise = True
|
|||
|
|
else:
|
|||
|
|
from rest_framework.exceptions import PermissionDenied
|
|||
|
|
raise PermissionDenied('无权参与此洽谈')
|
|||
|
|
|
|||
|
|
content = request.data.get('message', '').strip()
|
|||
|
|
if not content:
|
|||
|
|
from rest_framework.exceptions import ValidationError
|
|||
|
|
raise ValidationError('回复内容不能为空')
|
|||
|
|
|
|||
|
|
from django.utils import timezone
|
|||
|
|
message_obj = {
|
|||
|
|
'sender_id': str(user.id),
|
|||
|
|
'sender_name': user.nickname or user.username,
|
|||
|
|
'sender_avatar': user.avatar_url,
|
|||
|
|
'sender_role': sender_role,
|
|||
|
|
'content': content,
|
|||
|
|
'created_at': timezone.now().isoformat()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Initialize messages if None somehow
|
|||
|
|
if not isinstance(invitation.messages, list):
|
|||
|
|
invitation.messages = []
|
|||
|
|
|
|||
|
|
invitation.messages.append(message_obj)
|
|||
|
|
|
|||
|
|
# Ensure status transitions to DISCUSSING unless already ACCEPTED or REJECTED
|
|||
|
|
if invitation.status == InvitationStatus.PENDING:
|
|||
|
|
invitation.status = InvitationStatus.DISCUSSING
|
|||
|
|
|
|||
|
|
invitation.save()
|
|||
|
|
return Response({'status': '回复已发送', 'message': message_obj})
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _get_user_enterprise(user):
|
|||
|
|
from users.models import EnterpriseMember
|
|||
|
|
if hasattr(user, 'enterprise') and user.enterprise:
|
|||
|
|
return user.enterprise, 'ADMIN' # Owner is effectively ADMIN
|
|||
|
|
membership = EnterpriseMember.objects.filter(user=user).first()
|
|||
|
|
if membership:
|
|||
|
|
return membership.enterprise, membership.role
|
|||
|
|
return None, None
|
|||
|
|
|
|||
|
|
class TaskViewSet(viewsets.ModelViewSet):
|
|||
|
|
"""
|
|||
|
|
@author: xujl
|
|||
|
|
Api说明: 主线任务广场接口视图。提供企业发布任务、编辑任务信息,以及用户在大厅分页筛选、搜索开放任务的功能。
|
|||
|
|
"""
|
|||
|
|
def get_queryset(self):
|
|||
|
|
user = self.request.user
|
|||
|
|
queryset = Task.objects.filter(is_deleted=False).order_by('-is_recommended', '-recommend_priority', '-created_at')
|
|||
|
|
|
|||
|
|
status_param = self.request.query_params.get('status')
|
|||
|
|
if status_param:
|
|||
|
|
queryset = queryset.filter(status=status_param)
|
|||
|
|
|
|||
|
|
enterprise_param = self.request.query_params.get('enterprise')
|
|||
|
|
if enterprise_param:
|
|||
|
|
queryset = queryset.filter(enterprise_id=enterprise_param)
|
|||
|
|
|
|||
|
|
# If global admin, show all tasks
|
|||
|
|
if user.is_staff or user.is_superuser or 'ADMIN' in getattr(user, 'roles', []):
|
|||
|
|
return queryset
|
|||
|
|
|
|||
|
|
# If enterprise user, show their enterprise's tasks
|
|||
|
|
enterprise, role = _get_user_enterprise(user)
|
|||
|
|
if enterprise and not enterprise_param:
|
|||
|
|
return queryset.filter(enterprise=enterprise)
|
|||
|
|
|
|||
|
|
# Regular users (OPC experts, etc.) can see all OPEN tasks + tasks they're involved in
|
|||
|
|
return queryset
|
|||
|
|
|
|||
|
|
|
|||
|
|
serializer_class = TaskSerializer
|
|||
|
|
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
|
|||
|
|
|
|||
|
|
def perform_create(self, serializer):
|
|||
|
|
user = self.request.user
|
|||
|
|
# Admins can publish tasks directly (platform-level tasks)
|
|||
|
|
if user.is_staff or user.is_superuser:
|
|||
|
|
enterprise, _ = _get_user_enterprise(user)
|
|||
|
|
serializer.save(publisher=user, enterprise=enterprise)
|
|||
|
|
return
|
|||
|
|
enterprise, _ = _get_user_enterprise(user)
|
|||
|
|
if not enterprise:
|
|||
|
|
from rest_framework.exceptions import PermissionDenied
|
|||
|
|
raise PermissionDenied('必须关联企业后才能发布任务')
|
|||
|
|
if enterprise.status != 'VERIFIED':
|
|||
|
|
from rest_framework.exceptions import PermissionDenied
|
|||
|
|
raise PermissionDenied('企业尚未通过认证,暂无权发布任务')
|
|||
|
|
serializer.save(publisher=user, enterprise=enterprise)
|
|||
|
|
|
|||
|
|
def _check_admin_or_publisher(self, task, user):
|
|||
|
|
if user.is_staff or user.is_superuser:
|
|||
|
|
return True
|
|||
|
|
if 'ADMIN' in getattr(user, 'roles', []):
|
|||
|
|
return True
|
|||
|
|
if task.publisher == user:
|
|||
|
|
return True
|
|||
|
|
enterprise, role = _get_user_enterprise(user)
|
|||
|
|
if enterprise and task.enterprise == enterprise and role == 'ADMIN':
|
|||
|
|
return True
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
def perform_update(self, serializer):
|
|||
|
|
task = self.get_object()
|
|||
|
|
if not self._check_admin_or_publisher(task, self.request.user):
|
|||
|
|
from rest_framework.exceptions import PermissionDenied
|
|||
|
|
raise PermissionDenied('无权修改此任务')
|
|||
|
|
serializer.save()
|
|||
|
|
|
|||
|
|
def perform_destroy(self, instance):
|
|||
|
|
if not self._check_admin_or_publisher(instance, self.request.user):
|
|||
|
|
from rest_framework.exceptions import PermissionDenied
|
|||
|
|
raise PermissionDenied('无权删除此任务')
|
|||
|
|
instance.is_deleted = True
|
|||
|
|
instance.save()
|
|||
|
|
|
|||
|
|
@action(detail=True, methods=['post'], permission_classes=[permissions.IsAuthenticated])
|
|||
|
|
def toggle_recommend(self, request, pk=None):
|
|||
|
|
task = self.get_object()
|
|||
|
|
task.is_recommended = request.data.get('is_recommended', not task.is_recommended)
|
|||
|
|
task.recommend_priority = request.data.get('recommend_priority', task.recommend_priority)
|
|||
|
|
task.save(update_fields=['is_recommended', 'recommend_priority'])
|
|||
|
|
return Response({'is_recommended': task.is_recommended, 'recommend_priority': task.recommend_priority})
|
|||
|
|
|
|||
|
|
@action(detail=True, methods=['post'], permission_classes=[permissions.IsAuthenticated])
|
|||
|
|
def publish(self, request, pk=None):
|
|||
|
|
task = self.get_object()
|
|||
|
|
if not self._check_admin_or_publisher(task, request.user):
|
|||
|
|
return Response({'detail': '无权操作'}, status=status.HTTP_403_FORBIDDEN)
|
|||
|
|
task.status = TaskStatus.OPEN
|
|||
|
|
task.save()
|
|||
|
|
return Response({'status': '任务已发布'})
|
|||
|
|
|
|||
|
|
@action(detail=True, methods=['post'], permission_classes=[permissions.IsAuthenticated])
|
|||
|
|
def cancel_task(self, request, pk=None):
|
|||
|
|
task = self.get_object()
|
|||
|
|
if not self._check_admin_or_publisher(task, request.user):
|
|||
|
|
from rest_framework.exceptions import PermissionDenied
|
|||
|
|
raise PermissionDenied('无权取消此任务')
|
|||
|
|
|
|||
|
|
batch_reject = request.data.get('batch_reject', False)
|
|||
|
|
# Any application that is PENDING, APPROVED, or DELIVERED should be cancelled.
|
|||
|
|
active_apps = task.applications.exclude(status__in=[ApplyStatus.REJECTED, ApplyStatus.COMPLETED, ApplyStatus.WITHDRAWN])
|
|||
|
|
|
|||
|
|
if active_apps.exists():
|
|||
|
|
if not batch_reject:
|
|||
|
|
return Response({
|
|||
|
|
'detail': '该任务有正在进行中的专家或待审核的申请,禁止直接取消。请选择强制批量中止。',
|
|||
|
|
'requires_batch_reject': True
|
|||
|
|
}, status=status.HTTP_400_BAD_REQUEST)
|
|||
|
|
else:
|
|||
|
|
# Batch reject
|
|||
|
|
active_apps.update(status=ApplyStatus.REJECTED, reject_reason='任务已被系统或发布方强制中止/取消')
|
|||
|
|
|
|||
|
|
task.status = TaskStatus.CANCELLED
|
|||
|
|
from django.utils import timezone
|
|||
|
|
task.cancelled_at = timezone.now()
|
|||
|
|
task.cancel_reason = request.data.get('cancel_reason', '管理员操作下架')
|
|||
|
|
task.save()
|
|||
|
|
return Response({'status': '任务已下架'})
|
|||
|
|
|
|||
|
|
@action(detail=True, methods=['post'], permission_classes=[permissions.IsAuthenticated])
|
|||
|
|
def complete_task(self, request, pk=None):
|
|||
|
|
task = self.get_object()
|
|||
|
|
if not self._check_admin_or_publisher(task, request.user):
|
|||
|
|
from rest_framework.exceptions import PermissionDenied
|
|||
|
|
raise PermissionDenied('无权操作')
|
|||
|
|
|
|||
|
|
from .models import ApplyStatus, DeliveryStatus
|
|||
|
|
|
|||
|
|
# Check for experts who haven't delivered
|
|||
|
|
working_apps = task.applications.filter(status=ApplyStatus.APPROVED)
|
|||
|
|
if working_apps.exists():
|
|||
|
|
return Response({'detail': '尚有专家未提交交付物,无法结项。如需结项,请先取消未完成的录用。'}, status=status.HTTP_400_BAD_REQUEST)
|
|||
|
|
|
|||
|
|
# Check for experts who delivered but haven't been approved
|
|||
|
|
delivered_apps = task.applications.filter(status=ApplyStatus.DELIVERED)
|
|||
|
|
for app in delivered_apps:
|
|||
|
|
latest_record = app.delivery_records.order_by('-created_at').first()
|
|||
|
|
if not latest_record or latest_record.status != DeliveryStatus.APPROVED:
|
|||
|
|
return Response({'detail': '尚有专家的交付成果未验收,无法结项。'}, status=status.HTTP_400_BAD_REQUEST)
|
|||
|
|
|
|||
|
|
task.status = TaskStatus.COMPLETED
|
|||
|
|
from django.utils import timezone
|
|||
|
|
task.completed_at = timezone.now()
|
|||
|
|
task.save()
|
|||
|
|
|
|||
|
|
for app in delivered_apps:
|
|||
|
|
app.status = ApplyStatus.COMPLETED
|
|||
|
|
app.reviewed_at = timezone.now()
|
|||
|
|
app.save()
|
|||
|
|
expert = app.applicant
|
|||
|
|
if expert:
|
|||
|
|
expert.completed_tasks = (expert.completed_tasks or 0) + 1
|
|||
|
|
expert.save()
|
|||
|
|
|
|||
|
|
return Response({'status': '任务已成功结项'})
|
|||
|
|
|
|||
|
|
class TaskApplicationViewSet(viewsets.ModelViewSet):
|
|||
|
|
"""
|
|||
|
|
@author: xujl
|
|||
|
|
Api说明: 任务申请投递接口视图。普通用户/专家对感兴趣的任务进行接单投递(包含附件、报价),企业方审批(approve/reject)后即缔结合作。
|
|||
|
|
"""
|
|||
|
|
queryset = TaskApplication.objects.all()
|
|||
|
|
serializer_class = TaskApplicationSerializer
|
|||
|
|
permission_classes = [permissions.IsAuthenticated]
|
|||
|
|
|
|||
|
|
def get_queryset(self):
|
|||
|
|
user = self.request.user
|
|||
|
|
|
|||
|
|
# Admins can see all applications
|
|||
|
|
if user.is_staff or user.is_superuser or 'ADMIN' in getattr(user, 'roles', []):
|
|||
|
|
qs = TaskApplication.objects.all().order_by('-created_at')
|
|||
|
|
else:
|
|||
|
|
enterprise, _ = _get_user_enterprise(user)
|
|||
|
|
|
|||
|
|
if enterprise:
|
|||
|
|
# Publishers/Admins see applications for their enterprise's tasks
|
|||
|
|
qs = TaskApplication.objects.filter(
|
|||
|
|
models.Q(applicant=user) | models.Q(task__enterprise=enterprise)
|
|||
|
|
).distinct().order_by('-created_at')
|
|||
|
|
else:
|
|||
|
|
qs = TaskApplication.objects.filter(
|
|||
|
|
models.Q(applicant=user) | models.Q(task__publisher=user)
|
|||
|
|
).distinct().order_by('-created_at')
|
|||
|
|
|
|||
|
|
task_id = self.request.query_params.get('task')
|
|||
|
|
if task_id:
|
|||
|
|
qs = qs.filter(task_id=task_id)
|
|||
|
|
|
|||
|
|
return qs
|
|||
|
|
|
|||
|
|
|
|||
|
|
def perform_create(self, serializer):
|
|||
|
|
user = self.request.user
|
|||
|
|
|
|||
|
|
if 'OPC_USER' not in getattr(user, 'roles', []):
|
|||
|
|
# Check db if property is missing
|
|||
|
|
from users.models import UserRole
|
|||
|
|
if not UserRole.objects.filter(user=user, role__code='OPC_USER').exists():
|
|||
|
|
from rest_framework.exceptions import PermissionDenied
|
|||
|
|
raise PermissionDenied('必须是已认证的 OPC 专家才能承接任务')
|
|||
|
|
|
|||
|
|
from opc_cert.models import OpcCertification
|
|||
|
|
cert = OpcCertification.objects.filter(user=user, status='APPROVED').exists()
|
|||
|
|
if not cert:
|
|||
|
|
from rest_framework.exceptions import PermissionDenied
|
|||
|
|
raise PermissionDenied('您的 OPC 专家认证未通过审核,无法承接任务')
|
|||
|
|
|
|||
|
|
serializer.save(applicant=user)
|
|||
|
|
|
|||
|
|
def _check_admin_or_publisher(self, task, user):
|
|||
|
|
if user.is_staff or user.is_superuser:
|
|||
|
|
return True
|
|||
|
|
if 'ADMIN' in getattr(user, 'roles', []):
|
|||
|
|
return True
|
|||
|
|
if task.publisher == user:
|
|||
|
|
return True
|
|||
|
|
enterprise, role = _get_user_enterprise(user)
|
|||
|
|
if enterprise and task.enterprise == enterprise:
|
|||
|
|
return True
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
@action(detail=True, methods=['post'], permission_classes=[permissions.IsAuthenticated])
|
|||
|
|
def approve(self, request, pk=None):
|
|||
|
|
application = self.get_object()
|
|||
|
|
if not self._check_admin_or_publisher(application.task, request.user):
|
|||
|
|
return Response({'detail': '无权操作'}, status=status.HTTP_403_FORBIDDEN)
|
|||
|
|
|
|||
|
|
application.status = ApplyStatus.APPROVED
|
|||
|
|
application.reviewed_by = request.user
|
|||
|
|
application.save()
|
|||
|
|
|
|||
|
|
task = application.task
|
|||
|
|
if task.status in [TaskStatus.OPEN, TaskStatus.DRAFT]:
|
|||
|
|
task.status = TaskStatus.IN_PROGRESS
|
|||
|
|
task.save()
|
|||
|
|
|
|||
|
|
return Response({'status': '申请已通过,专家可开始执行'})
|
|||
|
|
|
|||
|
|
@action(detail=True, methods=['post'], permission_classes=[permissions.IsAuthenticated])
|
|||
|
|
def reject(self, request, pk=None):
|
|||
|
|
application = self.get_object()
|
|||
|
|
if not self._check_admin_or_publisher(application.task, request.user):
|
|||
|
|
return Response({'detail': '无权操作'}, status=status.HTTP_403_FORBIDDEN)
|
|||
|
|
|
|||
|
|
application.status = ApplyStatus.REJECTED
|
|||
|
|
application.reject_reason = request.data.get('reason', '')
|
|||
|
|
application.reviewed_by = request.user
|
|||
|
|
from django.utils import timezone
|
|||
|
|
application.reviewed_at = timezone.now()
|
|||
|
|
application.save()
|
|||
|
|
|
|||
|
|
return Response({'status': '申请已驳回'})
|
|||
|
|
|
|||
|
|
@action(detail=True, methods=['post'], permission_classes=[permissions.IsAuthenticated])
|
|||
|
|
def submit_deliverables(self, request, pk=None):
|
|||
|
|
application = self.get_object()
|
|||
|
|
if application.applicant != request.user:
|
|||
|
|
return Response({'detail': '只有承接人自己才能提交成果'}, status=status.HTTP_403_FORBIDDEN)
|
|||
|
|
|
|||
|
|
deliverables = request.data.get('deliverables', [])
|
|||
|
|
completion_note = request.data.get('completion_note', '')
|
|||
|
|
|
|||
|
|
from .models import DeliveryRecord, DeliveryStatus
|
|||
|
|
DeliveryRecord.objects.create(
|
|||
|
|
application=application,
|
|||
|
|
note=completion_note,
|
|||
|
|
files=deliverables,
|
|||
|
|
status=DeliveryStatus.PENDING
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
application.status = ApplyStatus.DELIVERED
|
|||
|
|
application.save()
|
|||
|
|
return Response({'status': '成果已提交,等待企业验收'})
|
|||
|
|
|
|||
|
|
@action(detail=True, methods=['post'], permission_classes=[permissions.IsAuthenticated])
|
|||
|
|
def approve_delivery(self, request, pk=None):
|
|||
|
|
application = self.get_object()
|
|||
|
|
if not self._check_admin_or_publisher(application.task, request.user):
|
|||
|
|
from rest_framework.exceptions import PermissionDenied
|
|||
|
|
raise PermissionDenied('无权验收此成果')
|
|||
|
|
|
|||
|
|
record_id = request.data.get('record_id')
|
|||
|
|
from .models import DeliveryRecord, DeliveryStatus
|
|||
|
|
|
|||
|
|
if record_id:
|
|||
|
|
record = application.delivery_records.filter(id=record_id).first()
|
|||
|
|
else:
|
|||
|
|
record = application.delivery_records.filter(status=DeliveryStatus.PENDING).first()
|
|||
|
|
|
|||
|
|
if not record:
|
|||
|
|
return Response({'detail': '找不到待验收的交付记录'}, status=status.HTTP_404_NOT_FOUND)
|
|||
|
|
|
|||
|
|
record.status = DeliveryStatus.APPROVED
|
|||
|
|
record.save()
|
|||
|
|
|
|||
|
|
# Note: We do NOT change application.status to COMPLETED here.
|
|||
|
|
# It remains DELIVERED until the enterprise explicitly closes the task via complete_task.
|
|||
|
|
# This ensures the expert's side doesn't show as fully completed before the project is officially wrapped up.
|
|||
|
|
|
|||
|
|
return Response({'status': '该专家的成果已验收通过'})
|
|||
|
|
|
|||
|
|
@action(detail=True, methods=['post'], permission_classes=[permissions.IsAuthenticated])
|
|||
|
|
def reject_delivery(self, request, pk=None):
|
|||
|
|
application = self.get_object()
|
|||
|
|
if not self._check_admin_or_publisher(application.task, request.user):
|
|||
|
|
from rest_framework.exceptions import PermissionDenied
|
|||
|
|
raise PermissionDenied('无权操作')
|
|||
|
|
|
|||
|
|
record_id = request.data.get('record_id')
|
|||
|
|
from .models import DeliveryRecord, DeliveryStatus
|
|||
|
|
if record_id:
|
|||
|
|
record = application.delivery_records.filter(id=record_id).first()
|
|||
|
|
else:
|
|||
|
|
record = application.delivery_records.filter(status=DeliveryStatus.PENDING).first()
|
|||
|
|
|
|||
|
|
if not record:
|
|||
|
|
return Response({'detail': '找不到待驳回的交付记录'}, status=status.HTTP_404_NOT_FOUND)
|
|||
|
|
|
|||
|
|
record.status = DeliveryStatus.REJECTED
|
|||
|
|
record.save()
|
|||
|
|
|
|||
|
|
# If all records are rejected, application goes back to APPROVED (IN_PROGRESS)
|
|||
|
|
if not application.delivery_records.filter(status=DeliveryStatus.PENDING).exists():
|
|||
|
|
application.status = ApplyStatus.APPROVED
|
|||
|
|
application.save()
|
|||
|
|
|
|||
|
|
return Response({'status': '该次交付已驳回'})
|