工序返回指导书嵌套字段

This commit is contained in:
caoqianming 2021-08-26 10:01:16 +08:00
parent 4bdf6382d5
commit cd37d53459
8 changed files with 176 additions and 47 deletions

View File

@ -1,6 +1,7 @@
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from .models import Material, Process, Step from .models import Material, Process, Step
from apps.system.serializers import FileSimpleSerializer
class MaterialSerializer(ModelSerializer): class MaterialSerializer(ModelSerializer):
@ -9,9 +10,11 @@ class MaterialSerializer(ModelSerializer):
fields = '__all__' fields = '__all__'
class ProcessSerializer(ModelSerializer): class ProcessSerializer(ModelSerializer):
instruction_ = FileSimpleSerializer(source='instruction', read_only=True)
class Meta: class Meta:
model = Process model = Process
fields = '__all__' fields = '__all__'
class StepSerializer(ModelSerializer): class StepSerializer(ModelSerializer):
class Meta: class Meta:

View File

@ -29,7 +29,7 @@ class ProcessViewSet(CreateUpdateModelAMixin, ModelViewSet):
""" """
perms_map = {'get': '*', 'post': 'process_create', perms_map = {'get': '*', 'post': 'process_create',
'put': 'process_update', 'delete': 'process_delete'} 'put': 'process_update', 'delete': 'process_delete'}
queryset = Process.objects.all() queryset = Process.objects.select_related('instruction').all()
serializer_class = ProcessSerializer serializer_class = ProcessSerializer
search_fields = ['name', 'number'] search_fields = ['name', 'number']
filterset_fields = ['number'] filterset_fields = ['number']

View File

@ -49,6 +49,11 @@ class PTaskSerializer(serializers.ModelSerializer):
return 'crontab' return 'crontab'
return 'interval' return 'interval'
class FileSimpleSerializer(serializers.ModelSerializer):
class Meta:
model =File
fields = ['id', 'name', 'file', 'path']
class FileSerializer(serializers.ModelSerializer): class FileSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = File model = File

View File

@ -0,0 +1,23 @@
# Generated by Django 3.2.6 on 2021-08-25 07:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('wf', '0005_auto_20210823_1548'),
]
operations = [
migrations.AddField(
model_name='state',
name='state_fields',
field=models.JSONField(default=dict, help_text='json格式字典存储,包括读写属性1只读2必填3可选. 示例:{"created_at":1,"title":2, "sn":1}, 内置特殊字段participant_info.participant_name:当前处理人信息(部门名称、角色名称)state.state_name:当前状态的状态名,workflow.workflow_name:工作流名称', verbose_name='表单字段'),
),
migrations.AlterField(
model_name='state',
name='type',
field=models.IntegerField(choices=[(0, '普通类型'), (1, 1), (2, 2)], default=0, help_text='0.普通类型 1.初始状态(用于新建工单时,获取对应的字段必填及transition信息) 2.结束状态(此状态下的工单不得再处理即没有对应的transition)', verbose_name='状态类型'),
),
]

View File

@ -26,20 +26,28 @@ class State(CommonAModel):
STATE_TYPE_START = 1 STATE_TYPE_START = 1
STATE_TYPE_END = 2 STATE_TYPE_END = 2
type_choices = ( type_choices = (
(0, '普通类型'), (0, '普通'),
(1, STATE_TYPE_START), (STATE_TYPE_START, '开始'),
(2, STATE_TYPE_END) (STATE_TYPE_END, '结束')
) )
PARTICIPANT_TYPE_PERSONAL = 1
PARTICIPANT_TYPE_MULTI = 2
PARTICIPANT_TYPE_DEPT = 3
PARTICIPANT_TYPE_ROLE = 4
PARTICIPANT_TYPE_VARIABLE = 5
PARTICIPANT_TYPE_ROBOT = 6
PARTICIPANT_TYPE_FIELD = 7
PARTICIPANT_TYPE_PARENT_FIELD = 8
type2_choices = ( type2_choices = (
(0, '无处理人'), (0, '无处理人'),
(1, '个人'), (PARTICIPANT_TYPE_PERSONAL, '个人'),
(2, '多人'), (PARTICIPANT_TYPE_MULTI, '多人'),
(3, '部门'), (PARTICIPANT_TYPE_DEPT, '部门'),
(4, '角色'), (PARTICIPANT_TYPE_ROLE, '角色'),
(5, '变量'), (PARTICIPANT_TYPE_VARIABLE, '变量'),
(6, '脚本'), (PARTICIPANT_TYPE_ROBOT, '脚本'),
(7, '工单的字段'), (PARTICIPANT_TYPE_FIELD, '工单的字段'),
(8, '父工单的字段') (PARTICIPANT_TYPE_PARENT_FIELD, '父工单的字段')
) )
name = models.CharField('名称', max_length=50) name = models.CharField('名称', max_length=50)
workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='所属工作流') workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='所属工作流')
@ -48,7 +56,7 @@ class State(CommonAModel):
type = models.IntegerField('状态类型', default=0, choices=type_choices, help_text='0.普通类型 1.初始状态(用于新建工单时,获取对应的字段必填及transition信息) 2.结束状态(此状态下的工单不得再处理即没有对应的transition)') type = models.IntegerField('状态类型', default=0, choices=type_choices, help_text='0.普通类型 1.初始状态(用于新建工单时,获取对应的字段必填及transition信息) 2.结束状态(此状态下的工单不得再处理即没有对应的transition)')
enable_retreat = models.BooleanField('允许撤回', default=False, help_text='开启后允许工单创建人在此状态直接撤回工单到初始状态') enable_retreat = models.BooleanField('允许撤回', default=False, help_text='开启后允许工单创建人在此状态直接撤回工单到初始状态')
participant_type = models.IntegerField('参与者类型', choices=type2_choices, default=1, blank=True, help_text='0.无处理人,1.个人,2.多人,3.部门,4.角色,5.变量(支持工单创建人,创建人的leader),6.脚本,7.工单的字段内容(如表单中的"测试负责人",需要为用户名或者逗号隔开的多个用户名),8.父工单的字段内容。 初始状态请选择类型5参与人填creator') participant_type = models.IntegerField('参与者类型', choices=type2_choices, default=1, blank=True, help_text='0.无处理人,1.个人,2.多人,3.部门,4.角色,5.变量(支持工单创建人,创建人的leader),6.脚本,7.工单的字段内容(如表单中的"测试负责人",需要为用户名或者逗号隔开的多个用户名),8.父工单的字段内容。 初始状态请选择类型5参与人填creator')
state_fields = models.JSONField('表单字段', default=dict, help_text='json格式字典存储,包括读写属性1只读2必填3可选. 示例:{"created_at":1,"title":2, "sn":1}, 内置特殊字段participant_info.participant_name:当前处理人信息(部门名称、角色名称)state.state_name:当前状态的状态名,workflow.workflow_name:工作流名称') # json格式存储,包括读写属性1只读2必填3可选4不显示, 字典的字典
class Transition(CommonAModel): class Transition(CommonAModel):
""" """
@ -116,6 +124,9 @@ class Ticket(CommonAModel):
in_add_node = models.BooleanField('加签状态中', default=False, help_text='是否处于加签状态下') in_add_node = models.BooleanField('加签状态中', default=False, help_text='是否处于加签状态下')
add_node_man = models.CharField('加签人', max_length=50, default='', blank=True, help_text='加签操作的人,工单当前处理人处理完成后会回到该处理人,当处于加签状态下才有效') add_node_man = models.CharField('加签人', max_length=50, default='', blank=True, help_text='加签操作的人,工单当前处理人处理完成后会回到该处理人,当处于加签状态下才有效')
participant_type = models.IntegerField('当前处理人类型', default=0, help_text='0.无处理人,1.个人,2.多人,3.部门,4.角色', choices=State.type2_choices)
participant = models.CharField('当前处理人', max_length=1000, default='', blank=True, help_text='可以为空(无处理人的情况,如结束状态)、username\多个username(以,隔开)\部门id\角色id\脚本文件名等')
class TicketFlow(BaseModel): class TicketFlow(BaseModel):
""" """
工单流转日志 工单流转日志

View File

@ -1,29 +1,30 @@
from rest_framework.serializers import ModelSerializer, SerializerMethodField, CharField import rest_framework
from rest_framework import serializers
from .models import State, Ticket, Workflow, Transition, CustomField from .models import State, Ticket, Workflow, Transition, CustomField
class WorkflowSerializer(ModelSerializer): class WorkflowSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Workflow model = Workflow
fields = '__all__' fields = '__all__'
class StateSerializer(ModelSerializer): class StateSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = State model = State
fields = '__all__' fields = '__all__'
class WorkflowSimpleSerializer(ModelSerializer): class WorkflowSimpleSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Workflow model = Workflow
fields = ['id', 'name'] fields = ['id', 'name']
class StateSimpleSerializer(ModelSerializer): class StateSimpleSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = State model = State
fields = ['id', 'name'] fields = ['id', 'name']
class TransitionSerializer(ModelSerializer): class TransitionSerializer(serializers.ModelSerializer):
source_state_ = StateSimpleSerializer(source='source_state', read_only=True) source_state_ = StateSimpleSerializer(source='source_state', read_only=True)
destination_state_ = StateSimpleSerializer(source='destination_state', read_only=True) destination_state_ = StateSimpleSerializer(source='destination_state', read_only=True)
class Meta: class Meta:
@ -36,18 +37,19 @@ class TransitionSerializer(ModelSerializer):
return queryset return queryset
class CustomFieldSerializer(ModelSerializer): class CustomFieldSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = CustomField model = CustomField
fields = '__all__' fields = '__all__'
class TicketCreateSerializer(ModelSerializer): class TicketCreateSerializer(serializers.ModelSerializer):
transition = serializers.IntegerField(label='流转ID')
class Meta: class Meta:
model=Ticket model=Ticket
fields=['title','workflow','ticket_data'] fields=['title','workflow','ticket_data', 'transition']
class TicketSerializer(ModelSerializer): class TicketSerializer(serializers.ModelSerializer):
workflow_ = WorkflowSimpleSerializer(source='workflow', read_only=True) workflow_ = WorkflowSimpleSerializer(source='workflow', read_only=True)
state_ = StateSimpleSerializer(source='state', read_only=True) state_ = StateSimpleSerializer(source='state', read_only=True)

View File

@ -1,31 +1,38 @@
from apps.wf.models import State, Ticket, Transition, Workflow from apps.wf.models import CustomField, State, Ticket, Transition, Workflow
from rest_framework.exceptions import APIException from rest_framework.exceptions import APIException
class WfService(object): class WfService(object):
@staticmethod @staticmethod
def get_wf_states(wf:Workflow): def get_worlflow_states(workflow:Workflow):
""" """
获取工作流状态列表 获取工作流状态列表
""" """
return State.objects.filter(workflow=wf, is_deleted=False).order_by('sort') return State.objects.filter(workflow=workflow, is_deleted=False).order_by('sort')
@staticmethod @staticmethod
def get_wf_transitions(wf:Workflow): def get_workflow_transitions(workflow:Workflow):
""" """
获取工作流流转列表 获取工作流流转列表
""" """
return Transition.objects.filter(workflow=wf, is_deleted=False) return Transition.objects.filter(workflow=workflow, is_deleted=False)
@staticmethod @staticmethod
def get_wf_start_state(wf:Workflow): def get_workflow_start_state(workflow:Workflow):
""" """
获取工作流初始状态 获取工作流初始状态
""" """
try: try:
wf_state_obj = State.objects.get(workflow=wf, type=State.STATE_TYPE_START, is_deleted=False) wf_state_obj = State.objects.get(workflow=workflow, type=State.STATE_TYPE_START, is_deleted=False)
return wf_state_obj return wf_state_obj
except: except:
raise Exception('工作流初始状态配置错误') raise Exception('工作流状态配置错误')
@staticmethod
def get_workflow_custom_fields(workflow:Workflow):
"""
获取工单字段
"""
return CustomField.objects.filter(is_deleted=False, workflow=workflow).order_by('sort')
@classmethod @classmethod
def get_ticket_transitions(cls, ticket:Ticket): def get_ticket_transitions(cls, ticket:Ticket):
""" """
@ -41,14 +48,57 @@ class WfService(object):
return Transition.objects.filter(is_deleted=False, source_state=state).all() return Transition.objects.filter(is_deleted=False, source_state=state).all()
@classmethod @classmethod
def get_ticket_next_state(cls, ticket:Ticket)->object: def get_ticket_steps(cls, ticket:Ticket):
transitions = Transition.objects.filter(source_state=ticket.state, is_deleted=False) steps = cls.get_worlflow_states(ticket.workflow)
count = transitions.count() for i in steps:
if count == 0: if ticket.state.is_hidden and ticket.state != i:
raise Exception('未配置流转条件') steps.remove(i)
elif count == 1: return steps
return transitions.first()
else: @classmethod
for i in transitions: def get_ticket_transitions(cls, ticket:Ticket):
pass """
获取工单可执行的操作
"""
return cls.get_state_transitions(ticket.state)
@classmethod
def get_transition_by_args(cls, kwargs:dict):
"""
查询并获取流转
"""
kwargs['is_deleted'] = False
return Transition.objects.filter(**kwargs).all()
@classmethod
def get_next_state_id_by_transition_and_ticket_info(cls, ticket:Ticket, transition: Transition, workflow:Workflow = None)->object:
"""
获取下个节点状态
"""
if ticket: # 如果是新建工单
source_state = ticket.state
else:
source_state = cls.get_workflow_start_state(workflow)
if transition.source_state != source_state:
raise APIException('流转错误')
destination_state = transition.destination_state
if transition.condition_expression:
pass
return destination_state
@classmethod
def get_ticket_state_participant_info(cls, state:State, ticket:Ticket, ticket_data:dict):
"""
获取工单目标状态实际的处理人
"""
if state.type == State.STATE_TYPE_START:
"""
回到初始状态
"""
elif state.type == State.STATE_TYPE_END:
"""
到达结束状态
"""

View File

@ -4,10 +4,11 @@ from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModel
from apps.wf.serializers import CustomFieldSerializer, StateSerializer, TicketCreateSerializer, TicketSerializer, TransitionSerializer, WorkflowSerializer from apps.wf.serializers import CustomFieldSerializer, StateSerializer, TicketCreateSerializer, TicketSerializer, TransitionSerializer, WorkflowSerializer
from django.shortcuts import render from django.shortcuts import render
from rest_framework.viewsets import GenericViewSet, ModelViewSet from rest_framework.viewsets import GenericViewSet, ModelViewSet
from rest_framework.decorators import action from rest_framework.decorators import action, api_view
from apps.wf.models import CustomField, Ticket, Workflow, State, Transition from apps.wf.models import CustomField, Ticket, Workflow, State, Transition
from apps.system.mixins import CreateUpdateCustomMixin, CreateUpdateModelAMixin, OptimizationMixin from apps.system.mixins import CreateUpdateCustomMixin, CreateUpdateModelAMixin, OptimizationMixin
from apps.wf.services import WfService from apps.wf.services import WfService
from rest_framework.exceptions import APIException
# Create your views here. # Create your views here.
class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet): class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
@ -26,7 +27,7 @@ class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
工作流下的状态节点 工作流下的状态节点
""" """
wf = self.get_object() wf = self.get_object()
serializer = self.serializer_class(instance=WfService.get_wf_states(wf), many=True) serializer = self.serializer_class(instance=WfService.get_worlflow_states(wf), many=True)
return Response(serializer.data) return Response(serializer.data)
@action(methods=['get'], detail=True, perms_map={'get':'workflow_update'}, pagination_class=None, serializer_class=TransitionSerializer) @action(methods=['get'], detail=True, perms_map={'get':'workflow_update'}, pagination_class=None, serializer_class=TransitionSerializer)
@ -35,7 +36,7 @@ class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
工作流下的流转规则 工作流下的流转规则
""" """
wf = self.get_object() wf = self.get_object()
serializer = self.serializer_class(instance=WfService.get_wf_transitions(wf), many=True) serializer = self.serializer_class(instance=WfService.get_workflow_transitions(wf), many=True)
return Response(serializer.data) return Response(serializer.data)
@action(methods=['get'], detail=True, perms_map={'get':'workflow_update'}, pagination_class=None, serializer_class=CustomFieldSerializer) @action(methods=['get'], detail=True, perms_map={'get':'workflow_update'}, pagination_class=None, serializer_class=CustomFieldSerializer)
@ -54,9 +55,12 @@ class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
""" """
ret={} ret={}
wf = self.get_object() wf = self.get_object()
start_state = WfService.get_wf_start_state(wf) start_state = WfService.get_workflow_start_state(wf)
transitions = WfService.get_state_transitions(start_state) transitions = WfService.get_state_transitions(start_state)
ret['workflow'] = pk ret['workflow'] = pk
ret['transitions'] = TransitionSerializer(instance=transitions, many=True).data
field_list = CustomFieldSerializer(instance=WfService.get_workflow_custom_fields(wf), many=True).data
ret['field_list'] = field_list
return Response(ret) return Response(ret)
class StateViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet): class StateViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet):
@ -97,4 +101,35 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
return super().get_serializer_class() return super().get_serializer_class()
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
return super().create(request, *args, **kwargs) """
新建工单
"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
start_state = WfService.get_workflow_start_state(serializer.data['workflow'])
transition = Transition.objects.get(pk=serializer.data['transition'])
ticket_data = serializer.data['ticket_data']
if transition.field_require_check:
for key, value in start_state.state_fields.items(): #校验必填项
if value == 2:
if key not in ticket_data or not ticket_data[key]:
raise APIException('字段{}必填'.format(key))
next_state = WfService.get_next_state_id_by_transition_and_ticket_info(ticket=None, transition=transition, workflow=serializer.data['workflow'])
@action(methods=['get'], detail=True, perms_map={'get':'*'})
def flowsteps(self, request, pk=None):
"""
工单流转step, 用于显示当前状态的step图(线性结构)
"""
ticket = self.get_object()
steps = WfService.get_ticket_steps(ticket)
return Response(StateSerializer(instance=steps, many=True).data)
@action(methods=['get'], detail=True, perms_map={'get':'*'})
def transitions(self, request, pk=None):
"""
获取工单可执行的操作
"""
ticket = self.get_object()
transitions = WfService.get_ticket_transitions(ticket)
return Response(TransitionSerializer(instance=transitions, many=True).data)