""" BusinessObject API ViewSet """ from rest_framework import viewsets, filters, status from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.pagination import LimitOffsetPagination from django_filters.rest_framework import DjangoFilterBackend from django_filters import rest_framework as django_filters from stateflow import models, services from stateflow.serializers import ( BusinessObjectListSerializer, BusinessObjectDetailSerializer, BusinessObjectCreateUpdateSerializer, StateListSerializer, ) class BusinessObjectFilterSet(django_filters.FilterSet): """业务对象过滤器""" name = django_filters.CharFilter(lookup_expr='icontains') process_name = django_filters.CharFilter(field_name='process__name', lookup_expr='icontains') overall_status = django_filters.ChoiceFilter( choices=[ ('not_started', '未开始'), ('in_progress', '进行中'), ('completed', '已完成') ], method='filter_overall_status' ) content_type_str = django_filters.CharFilter(method='filter_content_type') has_content_object = django_filters.BooleanFilter(method='filter_has_content_object') def filter_overall_status(self, queryset, name, value): """过滤整体状态""" business_object_ids = [] for obj in queryset: if services.get_overall_status(obj) == value: business_object_ids.append(obj.id) return queryset.filter(id__in=business_object_ids) def filter_content_type(self, queryset, name, value): """通过 app_label.model 字符串过滤""" try: from django.contrib.contenttypes.models import ContentType app_label, model = value.split('.') content_type = ContentType.objects.get(app_label=app_label, model=model) return queryset.filter(content_type=content_type) except: return queryset.none() def filter_has_content_object(self, queryset, name, value): """过滤是否有关联对象""" if value: return queryset.exclude(content_type__isnull=True) else: return queryset.filter(content_type__isnull=True) class Meta: model = models.BusinessObject fields = ['name', 'process', 'process_name', 'overall_status', 'content_type_str', 'has_content_object'] class BusinessObjectViewSet(viewsets.ModelViewSet): """ 业务对象 CRUD 接口 业务对象是流程的实例,可以选择性地关联到实际的业务模型(如订单、工单等) list: 获取业务对象列表 retrieve: 获取业务对象详情(包含时间线和日志) create: 创建业务对象 update: 更新业务对象 partial_update: 部分更新业务对象 destroy: 删除业务对象 查询参数: - name: 按名称模糊查询 - process: 按流程ID过滤 - process_name: 按流程名称模糊查询 - overall_status: 按整体状态过滤 (not_started/in_progress/completed) - content_type_str: 按关联对象类型过滤,格式: app_label.model - has_content_object: 是否有关联对象 (true/false) - search: 全文搜索(名称和描述) - ordering: 排序字段 """ queryset = models.BusinessObject.objects.all() pagination_class = LimitOffsetPagination filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] filterset_class = BusinessObjectFilterSet search_fields = ['name', 'description'] ordering_fields = ['id', 'name', 'created_at', 'updated_at'] ordering = ['-created_at'] def get_serializer_class(self): """根据动作选择序列化器""" if self.action == 'list': return BusinessObjectListSerializer elif self.action in ['create', 'update', 'partial_update']: return BusinessObjectCreateUpdateSerializer else: # retrieve return BusinessObjectDetailSerializer def get_queryset(self): """优化查询""" queryset = super().get_queryset() if self.action == 'list': queryset = queryset.select_related('process', 'content_type') elif self.action == 'retrieve': queryset = queryset.select_related('process', 'content_type').prefetch_related( 'state_logs__state', 'state_logs__completed_by', 'process__process_nodes__state' ) return queryset @action(detail=True, methods=['post']) def advance(self, request, pk=None): """ 推进到下一个状态 请求体: { "parameters": { "temperature": "25.5", "humidity": "60%", "operator": "张三" } } 参数可选,但如果下一个状态有必填参数,则必须提供 """ business_object = self.get_object() user = request.user parameters = request.data.get('parameters', {}) success, message, state_log = services.advance_to_next_state(business_object, user, **parameters) if success: from stateflow.serializers import StateFlowRecordWithParametersSerializer return Response({ 'success': True, 'message': message, 'state_log': StateFlowRecordWithParametersSerializer(state_log).data if state_log else None, 'business_object': BusinessObjectDetailSerializer(business_object).data }) else: return Response({ 'success': False, 'message': message }, status=status.HTTP_400_BAD_REQUEST) @action(detail=True, methods=['post']) def step_back(self, request, pk=None): """回退一步(撤销最后一次完成的状态)""" business_object = self.get_object() user = request.user success, message = services.step_back_one_state(business_object, user) if success: return Response({ 'success': True, 'message': message, 'business_object': BusinessObjectDetailSerializer(business_object).data }) else: return Response({ 'success': False, 'message': message }, status=status.HTTP_400_BAD_REQUEST) @action(detail=True, methods=['post']) def reset(self, request, pk=None): """重置进度""" business_object = self.get_object() services.reset_business_object_progress(business_object) return Response({ 'success': True, 'message': '进度已重置', 'business_object': BusinessObjectDetailSerializer(business_object).data }) @action(detail=True, methods=['get']) def timeline(self, request, pk=None): """获取状态时间线""" business_object = self.get_object() timeline = services.get_business_object_state_timeline(business_object) timeline_data = [ { 'state': StateListSerializer(item['state']).data, 'status': item['status'], 'order': item['order'], 'completed_at': item['completed_at'], 'completed_by': item['completed_by'].username if item['completed_by'] else None, 'cancelled_at': item['cancelled_at'], 'is_cancelled': item['is_cancelled'], } for item in timeline ] return Response(timeline_data) @action(detail=True, methods=['get']) def next_pending_state(self, request, pk=None): """ 获取最近一个待执行节点 查询参数: - include_parameters: 是否包含参数列表,默认 true (可选值: true, false) """ business_object = self.get_object() include_parameters = request.query_params.get('include_parameters', 'true').lower() == 'true' next_state = services.get_next_pending_state(business_object, include_parameters=include_parameters) if next_state is None: return Response({ 'next_state': None, 'message': '没有待执行节点(未开始或已完成)' }) from stateflow.serializers import StateParameterSerializer result = { 'state': StateListSerializer(next_state['state']).data, 'order': next_state['order'], } if 'parameters' in next_state: result['parameters'] = StateParameterSerializer(next_state['parameters'], many=True).data return Response(result) @action(detail=True, methods=['get']) def pending_states(self, request, pk=None): """获取所有待执行节点列表(不包含参数)""" business_object = self.get_object() pending_states = services.get_all_pending_states(business_object) result = [ { 'state': StateListSerializer(item['state']).data, 'order': item['order'], } for item in pending_states ] return Response({ 'count': len(result), 'pending_states': result }) @action(detail=True, methods=['get'], url_path='current_state_parameters') def current_state_parameters(self, request, pk=None): """ 获取当前状态(最后完成的状态)的参数列表 查询参数: - required_only: 是否只返回必填参数,默认 false (可选值: true, false) 注意:current_state 表示最后完成的状态,如果未开始则返回空 """ business_object = self.get_object() required_only = request.query_params.get('required_only', 'false').lower() == 'true' current_state = services.get_business_object_current_state(business_object) if current_state is None: return Response({ 'parameters': [], 'message': '尚未完成任何状态' }) from stateflow.serializers import StateParameterSerializer parameters = services.get_state_parameters(current_state, required_only=required_only) return Response({ 'state': StateListSerializer(current_state).data, 'parameters': StateParameterSerializer(parameters, many=True).data, 'count': len(parameters) }) @action(detail=True, methods=['post'], url_path='state-logs/(?P[^/.]+)/add-parameters') def add_parameters_to_log(self, request, pk=None, log_id=None): """ 为状态流转记录补充参数 POST /api/v1/stateflow/business-objects/{id}/state-logs/{log_id}/add-parameters/ 请求体: { "parameters": { "additional_note": "发现轻微瑕疵", "temperature": "26.0" }, "remark": "质检员补充" } """ business_object = self.get_object() try: state_log = business_object.state_logs.get(id=log_id) except models.StateFlowRecord.DoesNotExist: return Response({ 'success': False, 'message': '状态流转记录不存在' }, status=status.HTTP_404_NOT_FOUND) parameters = request.data.get('parameters', {}) remark = request.data.get('remark', '') if not parameters: return Response({ 'success': False, 'message': '参数不能为空' }, status=status.HTTP_400_BAD_REQUEST) from stateflow.serializers import StateLogParameterRecordSerializer record = services.add_parameters_to_state_log(state_log, remark=remark, **parameters) return Response({ 'success': True, 'parameter_record': StateLogParameterRecordSerializer(record).data }) @action(detail=True, methods=['get'], url_path='state-logs/(?P[^/.]+)/parameters') def get_log_parameters(self, request, pk=None, log_id=None): """ 获取状态流转记录的所有参数记录 GET /api/v1/stateflow/business-objects/{id}/state-logs/{log_id}/parameters/ GET /api/v1/stateflow/business-objects/{id}/state-logs/{log_id}/parameters/?key=temperature GET /api/v1/stateflow/business-objects/{id}/state-logs/{log_id}/parameters/?include_cancelled=true 查询参数: - key: 指定参数键,返回该参数的历史(可选) - include_cancelled: 是否包含已撤销状态的参数,默认 false(可选) """ business_object = self.get_object() try: state_log = business_object.state_logs.get(id=log_id) except models.StateFlowRecord.DoesNotExist: return Response({ 'success': False, 'message': '状态流转记录不存在' }, status=status.HTTP_404_NOT_FOUND) key = request.query_params.get('key') include_cancelled = request.query_params.get('include_cancelled', 'false').lower() == 'true' if key: # 返回指定参数的历史 history = state_log.get_parameter_history(key, include_cancelled=include_cancelled) return Response({ 'state_log_id': state_log.id, 'key': key, 'is_cancelled': state_log.is_cancelled, 'history': history }) else: # 返回所有参数记录 from stateflow.serializers import StateLogParameterRecordSerializer records = state_log.parameter_records.all() return Response({ 'state_log_id': state_log.id, 'is_cancelled': state_log.is_cancelled, 'count': records.count(), 'summary': state_log.get_all_parameters_summary(include_cancelled=include_cancelled), 'records': StateLogParameterRecordSerializer(records, many=True).data if include_cancelled or not state_log.is_cancelled else [] })