1
0
forked from erp-dev/erp
Files
erpnew/api_v1/views/stateflow/business_object.py
2026-01-16 17:25:21 +08:00

479 lines
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
BusinessObject API ViewSet
"""
from rest_framework import viewsets, filters, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
from django_filters import rest_framework as django_filters
from django.db.models import Exists, OuterRef
from flower.viewsets import LimitedModelViewSet
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=[
('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')
# 按“已完成的 state_id + 参数 key/value”反查 BusinessObject
#
# 说明:
# - stateflow 的工艺参数保存在 StateLogParameterRecord.parameters(JSONField),并挂在某次 StateFlowRecord完成某节点之下
# - 本过滤器只匹配“未撤销”的 StateFlowRecordis_cancelled=False
# - 为避免意外放宽查询范围:当提供了 state_id 但缺少 param_key/param_value 时,返回空结果
state_id = django_filters.NumberFilter(method='filter_completed_state_param')
param_key = django_filters.CharFilter(method='noop')
param_value = django_filters.CharFilter(method='noop')
def noop(self, queryset, name, value):
"""占位param_key/param_value 仅作为 state_id 组合过滤的输入,不单独过滤"""
return queryset
def filter_completed_state_param(self, queryset, name, value):
"""
过滤:存在未撤销的 StateFlowRecord(state_id=value),且该 state_log 下存在参数记录满足 {param_key: param_value}
"""
if value in (None, ''):
return queryset
param_key = (self.data.get('param_key') or '').strip()
param_value = (self.data.get('param_value') or '').strip()
if not param_key or not param_value:
return queryset.none()
matching_logs = models.StateFlowRecord.objects.filter(
business_object_id=OuterRef('pk'),
state_id=value,
is_cancelled=False,
parameter_records__parameters__contains={param_key: param_value},
)
return queryset.annotate(_has_state_param=Exists(matching_logs)).filter(_has_state_param=True)
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',
'state_id',
'param_key',
'param_value',
]
class BusinessObjectViewSet(LimitedModelViewSet):
"""
业务对象 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()
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=['get'], url_path='process-nodes')
def process_nodes(self, request, pk=None):
"""
获取业务对象所属流程的所有状态节点
返回流程中所有节点的列表,按顺序排列
"""
business_object = self.get_object()
nodes = services.get_process_nodes(business_object)
return Response({
'count': len(nodes),
'nodes': [
{
'id': node['id'],
'state_id': node['state_id'],
'state_name': node['state_name'],
'order': node['order']
}
for node in nodes
]
})
@action(detail=True, methods=['post'], url_path='state-logs/(?P<log_id>[^/.]+)/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')
def get_state_logs(self, request, pk=None):
"""
获取业务对象的所有状态流转记录列表
GET /api/v1/stateflow/business-objects/{id}/state-logs/
GET /api/v1/stateflow/business-objects/{id}/state-logs/?include_parameters=false
GET /api/v1/stateflow/business-objects/{id}/state-logs/?include_cancelled=true
查询参数:
- include_parameters: 是否包含工艺参数,默认 true可选值: true, false
- include_cancelled: 是否包含已撤销的记录,默认 true可选值: true, false
"""
business_object = self.get_object()
include_parameters = request.query_params.get('include_parameters', 'true').lower() == 'true'
include_cancelled = request.query_params.get('include_cancelled', 'true').lower() == 'true'
# 获取状态流转记录
state_logs = business_object.state_logs.select_related('state', 'completed_by').order_by('completed_at')
if not include_cancelled:
state_logs = state_logs.filter(is_cancelled=False)
# 序列化
if include_parameters:
from stateflow.serializers import StateFlowRecordWithParametersSerializer
serializer = StateFlowRecordWithParametersSerializer(state_logs, many=True)
else:
from stateflow.serializers import StateFlowRecordSerializer
serializer = StateFlowRecordSerializer(state_logs, many=True)
return Response({
'count': state_logs.count(),
'state_logs': serializer.data
})
@action(detail=True, methods=['get'], url_path='state-logs/(?P<log_id>[^/.]+)/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 []
})