From dbf4560423527316992c9d5bded785b77480da60 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Thu, 26 Aug 2021 17:08:45 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E6=B5=81=E6=96=B0=E5=BB=BA?= =?UTF-8?q?=E5=B7=A5=E5=8D=95=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hb_server/apps/wf/models.py | 39 ++++++++++++++++++++--------- hb_server/apps/wf/services.py | 46 +++++++++++++++++++++++++++-------- hb_server/apps/wf/views.py | 12 ++++++++- 3 files changed, 75 insertions(+), 22 deletions(-) diff --git a/hb_server/apps/wf/models.py b/hb_server/apps/wf/models.py index 1f043ff..bf60399 100644 --- a/hb_server/apps/wf/models.py +++ b/hb_server/apps/wf/models.py @@ -12,6 +12,7 @@ class Workflow(CommonAModel): 工作流 """ name = models.CharField('名称', max_length=50) + sn_prefix = models.CharField('流水号前缀', max_length=50) description = models.CharField('描述', max_length=200) view_permission_check = models.BooleanField('查看权限校验', default=True, help_text='开启后,只允许工单的关联人(创建人、曾经的处理人)有权限查看工单') limit_expression = models.JSONField('限制表达式', max_length=1000, default=dict, blank=True, help_text='限制周期({"period":24} 24小时), 限制次数({"count":1}在限制周期内只允许提交1次), 限制级别({"level":1} 针对(1单个用户 2全局)限制周期限制次数,默认特定用户);允许特定人员提交({"allow_persons":"zhangsan,lisi"}只允许张三提交工单,{"allow_depts":"1,2"}只允许部门id为1和2的用户提交工单,{"allow_roles":"1,2"}只允许角色id为1和2的用户提交工单)') @@ -49,6 +50,16 @@ class State(CommonAModel): (PARTICIPANT_TYPE_FIELD, '工单的字段'), (PARTICIPANT_TYPE_PARENT_FIELD, '父工单的字段') ) + STATE_DISTRIBUTE_TYPE_ACTIVE = 1 # 主动接单 + STATE_DISTRIBUTE_TYPE_DIRECT = 2 # 直接处理(当前为多人的情况,都可以处理,而不需要先接单) + STATE_DISTRIBUTE_TYPE_RANDOM = 3 # 随机分配 + STATE_DISTRIBUTE_TYPE_ALL = 4 # 全部处理 + state_distribute_choices=( + (STATE_DISTRIBUTE_TYPE_ACTIVE, '主动接单'), + (STATE_DISTRIBUTE_TYPE_DIRECT, '直接处理'), + (STATE_DISTRIBUTE_TYPE_RANDOM, '随机分配'), + (STATE_DISTRIBUTE_TYPE_ALL, '全部处理'), + ) name = models.CharField('名称', max_length=50) workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='所属工作流') is_hidden = models.BooleanField('是否隐藏', default=False, help_text='设置为True时,获取工单步骤api中不显示此状态(当前处于此状态时除外)') @@ -56,8 +67,9 @@ class State(CommonAModel): type = models.IntegerField('状态类型', default=0, choices=type_choices, help_text='0.普通类型 1.初始状态(用于新建工单时,获取对应的字段必填及transition信息) 2.结束状态(此状态下的工单不得再处理,即没有对应的transition)') 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,参与人填create_by') - participant = models.CharField('参与者', default='', blank=True, max_length=1000, help_text='可以为空(无处理人的情况,如结束状态)、username\多个username(以,隔开)\部门id\角色id\变量(create_by,create_by_tl)\脚本记录的id等,包含子工作流的需要设置处理人为loonrobot') + participant = models.JSONField('参与者', default=list, blank=True, help_text='可以为空(无处理人的情况,如结束状态)、userid、userid列表\部门id\角色id\变量(create_by,create_by_tl)\脚本记录的id等,包含子工作流的需要设置处理人为loonrobot') 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:不显示, 字典的字典 + distribute_type = models.IntegerField('分配方式', default=1, help_text='1.主动接单(如果当前处理人实际为多人的时候,需要先接单才能处理) 2.直接处理(即使当前处理人实际为多人,也可以直接处理) 3.随机分配(如果实际为多人,则系统会随机分配给其中一个人) 4.全部处理(要求所有参与人都要处理一遍,才能进入下一步)') class Transition(CommonAModel): """ @@ -115,15 +127,20 @@ class Ticket(CommonAModel): """ 工单 """ - STATE_DISTRIBUTE_TYPE_ACTIVE = 1 - STATE_DISTRIBUTE_TYPE_DIRECT = 2 - STATE_DISTRIBUTE_TYPE_RANDOM = 3 - STATE_DISTRIBUTE_TYPE_ALL = 4 + TICKET_ACT_STATE_DRAFT = 0 # 草稿中 + TICKET_ACT_STATE_ONGOING = 1 # 进行中 + TICKET_ACT_STATE_BACK = 2 # 被退回 + TICKET_ACT_STATE_RETREAT = 3 # 被撤回 + TICKET_ACT_STATE_FINISH = 4 # 已完成 + TICKET_ACT_STATE_CLOSED = 5 # 已关闭 + act_state_choices =( - (STATE_DISTRIBUTE_TYPE_ACTIVE, '主动接单'), - (STATE_DISTRIBUTE_TYPE_DIRECT, '直接处理'), - (STATE_DISTRIBUTE_TYPE_RANDOM, '随机分配'), - (STATE_DISTRIBUTE_TYPE_ALL, '全部处理') + (TICKET_ACT_STATE_DRAFT, '草稿中'), + (TICKET_ACT_STATE_ONGOING, '进行中'), + (TICKET_ACT_STATE_BACK, '被退回'), + (TICKET_ACT_STATE_RETREAT, '被撤回'), + (TICKET_ACT_STATE_FINISH, '已完成'), + (TICKET_ACT_STATE_CLOSED, '已关闭') ) title = models.CharField('标题', max_length=500, blank=True, default='', help_text="工单标题") workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='关联工作流') @@ -135,8 +152,8 @@ class Ticket(CommonAModel): in_add_node = models.BooleanField('加签状态中', default=False, 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.JSONField('当前处理人', null=True, blank=True, help_text='可以为空(无处理人的情况,如结束状态)、userid、userid列表') + participant_type = models.IntegerField('当前处理人类型', default=0, help_text='0.无处理人,1.个人,2.多人', choices=State.type2_choices) + participant = models.JSONField('当前处理人', default=list, blank=True, help_text='可以为空(无处理人的情况,如结束状态)、userid、userid列表') act_state = models.IntegerField('进行状态', default=1, help_text='当前工单的进行状态', choices=act_state_choices) multi_all_person = models.JSONField('全部处理的结果', default=dict, blank=True, help_text='需要当前状态处理人全部处理时实际的处理结果,json格式') diff --git a/hb_server/apps/wf/services.py b/hb_server/apps/wf/services.py index c7f0159..8e4cc0f 100644 --- a/hb_server/apps/wf/services.py +++ b/hb_server/apps/wf/services.py @@ -1,6 +1,9 @@ from apps.system.models import User from apps.wf.models import CustomField, State, Ticket, Transition, Workflow from rest_framework.exceptions import APIException +from django.utils import timezone +from datetime import timedelta +import random class WfService(object): @staticmethod def get_worlflow_states(workflow:Workflow): @@ -72,16 +75,30 @@ class WfService(object): 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: + def get_ticket_sn(cls, workflow:Workflow): + """ + 生成工单流水号 + """ + now = timezone.now() + today = str(now)[:10]+' 00:00:00' + next_day = str(now+timedelta(days=1))[:10]+' 00:00:00' + ticket_day_count_new = Ticket.objects.filter(create_time__gte=today, create_time__lte=next_day, workflow=workflow).count()+1 + return '%s_%04d%02d%02d%04d' % (workflow.sn_prefix, now.year, now.month, now.day, ticket_day_count_new) + + + + @classmethod + def get_next_state_id_by_transition_and_ticket_info(cls, ticket:Ticket, transition: Transition)->object: """ 获取下个节点状态 """ - if ticket: # 如果是新建工单 - source_state = ticket.state - else: - source_state = cls.get_workflow_start_state(workflow) - if transition.source_state != source_state: - raise APIException('流转错误') + # if ticket: # 如果是新建工单 + # source_state = ticket.state + # else: + # source_state = cls.get_workflow_start_state(workflow) + # if transition.source_state != source_state: + # raise APIException('流转错误') + source_state = ticket.state destination_state = transition.destination_state if transition.condition_expression: pass @@ -98,14 +115,15 @@ class WfService(object): """ return dict(destination_participant_type=State.PARTICIPANT_TYPE_PERSONAL, destination_participant=ticket.create_by, - multi_all_person="{}") + multi_all_person=dict()) elif state.type == State.STATE_TYPE_END: """ 到达结束状态 """ return dict(destination_participant_type=State.PARTICIPANT_TYPE_PERSONAL, destination_participant='', - multi_all_person="{}") + multi_all_person=dict()) + multi_all_person_dict = {} destination_participant_type, destination_participant = State.participant_type, State.participant if destination_participant_type == State.PARTICIPANT_TYPE_FIELD: @@ -123,6 +141,14 @@ class WfService(object): else: destination_participant_type = State.PARTICIPANT_TYPE_PERSONAL - return dict(destination_participant_type) + if destination_participant_type == State.PARTICIPANT_TYPE_MULTI: + if state.distribute_type == State.STATE_DISTRIBUTE_TYPE_RANDOM: + destination_participant = random.choice(destination_participant) + elif state.distribute_type == State.STATE_DISTRIBUTE_TYPE_ALL: + for i in destination_participant: + multi_all_person_dict[i]={} + return dict(destination_participant_type=destination_participant_type, + destination_participant=destination_participant, + multi_all_person=multi_all_person_dict) diff --git a/hb_server/apps/wf/views.py b/hb_server/apps/wf/views.py index 2a7d877..1702059 100644 --- a/hb_server/apps/wf/views.py +++ b/hb_server/apps/wf/views.py @@ -114,7 +114,17 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin 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']) + ticket = serializer.save() + next_state = WfService.get_next_state_id_by_transition_and_ticket_info(ticket=ticket, transition=transition) + participant_info = WfService.get_ticket_state_participant_info(state=next_state, ticket=ticket, ticket_data=ticket.ticket_data) + destination_participant_type = participant_info.get('destination_participant_type', 0) + destination_participant = participant_info.get('destination_participant', '') + multi_all_person = participant_info.get('multi_all_person', '{}') # 多人需要全部处理情况 + sn = WfService.get_ticket_sn(ticket.workflow) + if next_state.type == State.STATE_TYPE_END: + act_state = Ticket.TICKET_ACT_STATE_FINISH + elif next_state.type == State.STATE_TYPE_START: + pass @action(methods=['get'], detail=True, perms_map={'get':'*'}) def flowsteps(self, request, pk=None):