from apps.system.models import User from apps.wf.filters import TicketFilterSet from django.core.exceptions import AppRegistryNotReady from rest_framework.response import Response from rest_framework import serializers from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin from apps.wf.serializers import CustomFieldSerializer, StateSerializer, TicketAddNodeSerializer, TicketCloseSerializer, TicketCreateSerializer, TicketFlowSerializer, TicketFlowSimpleSerializer, TicketHandleSerializer, TicketRetreatSerializer, TicketSerializer, TransitionSerializer, WorkflowSerializer, TicketListSerializer, TicketDetailSerializer from django.shortcuts import get_object_or_404, render from rest_framework.viewsets import GenericViewSet, ModelViewSet from rest_framework.decorators import action, api_view from apps.wf.models import CustomField, Ticket, Workflow, State, Transition, TicketFlow from apps.system.mixins import CreateUpdateCustomMixin, CreateUpdateModelAMixin, OptimizationMixin from apps.wf.services import WfService from rest_framework.exceptions import APIException, PermissionDenied from rest_framework import status # Create your views here. class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet): perms_map = {'get': '*', 'post': 'workflow_create', 'put': 'workflow_update', 'delete': 'workflow_delete'} queryset = Workflow.objects.all() serializer_class = WorkflowSerializer search_fields = ['name', 'description'] filterset_fields = [] ordering_fields = ['create_time'] ordering = ['-create_time'] @action(methods=['get'], detail=True, perms_map={'get':'workflow_update'}, pagination_class=None, serializer_class=StateSerializer) def states(self, request, pk=None): """ 工作流下的状态节点 """ wf = self.get_object() serializer = self.serializer_class(instance=WfService.get_worlflow_states(wf), many=True) return Response(serializer.data) @action(methods=['get'], detail=True, perms_map={'get':'workflow_update'}, pagination_class=None, serializer_class=TransitionSerializer) def transitions(self, request, pk=None): """ 工作流下的流转规则 """ wf = self.get_object() serializer = self.serializer_class(instance=WfService.get_workflow_transitions(wf), many=True) return Response(serializer.data) @action(methods=['get'], detail=True, perms_map={'get':'workflow_update'}, pagination_class=None, serializer_class=CustomFieldSerializer) def customfields(self, request, pk=None): """ 工作流下的自定义字段 """ wf = self.get_object() serializer = self.serializer_class(instance=CustomField.objects.filter(workflow=wf), many=True) return Response(serializer.data) @action(methods=['get'], detail=True, perms_map={'get':'workflow_init'}) def init(self, request, pk=None): """ 新建工单初始化 """ ret={} wf = self.get_object() start_state = WfService.get_workflow_start_state(wf) transitions = WfService.get_state_transitions(start_state) 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) class StateViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet): perms_map = {'*':'*'} queryset = State.objects.all() serializer_class = StateSerializer search_fields = ['name'] filterset_fields = ['workflow'] ordering = ['sort'] class TransitionViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet): perms_map = {'*':'*'} queryset = Transition.objects.all() serializer_class = TransitionSerializer search_fields = ['name'] filterset_fields = ['workflow'] ordering = ['id'] class CustomFieldViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet): perms_map = {'*':'*'} queryset = CustomField.objects.all() serializer_class = CustomFieldSerializer search_fields = ['field_name'] filterset_fields = ['workflow', 'field_type'] ordering = ['sort'] class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, RetrieveModelMixin, GenericViewSet): perms_map = {'*':'*'} queryset = Ticket.objects.all() serializer_class = TicketSerializer search_fields = ['title'] filterset_class = TicketFilterSet ordering = ['-create_time'] def get_serializer_class(self): if self.action == 'create': return TicketCreateSerializer elif self.action == 'handle': return TicketHandleSerializer elif self.action == 'retreat': return TicketRetreatSerializer elif self.action == 'list': return TicketListSerializer elif self.action == 'retrieve': return TicketDetailSerializer return super().get_serializer_class() def get_queryset(self): if self.action=='list' and (not self.request.query_params.get('category', None)): raise APIException('请指定查询分类') return super().get_queryset() def create(self, request, *args, **kwargs): """ 新建工单 """ rdata = request.data serializer = self.get_serializer(data=rdata) serializer.is_valid(raise_exception=True) start_state = WfService.get_workflow_start_state(rdata['workflow']) transition = Transition.objects.get(pk=rdata['transition']) ticket_data = rdata['ticket_data'] if transition.field_require_check: for key, value in start_state.state_fields.items(): #校验必填项 if value == State.STATE_FIELD_REQUIRED: if key not in ticket_data or not ticket_data[key]: raise APIException('字段{}必填'.format(key)) ticket = serializer.save(state=start_state, create_by=request.user, act_state=Ticket.TICKET_ACT_STATE_DRAFT) # 先创建出来 next_state = WfService.get_next_state_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', None) 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: act_state = Ticket.TICKET_ACT_STATE_DRAFT else: act_state = Ticket.TICKET_ACT_STATE_ONGOING title = rdata.get('title', '') title_template = ticket.workflow.title_template if title_template: all_ticket_data = {**rdata, **rdata['ticket_data']} title = title_template.format(**all_ticket_data) # 更新一下信息 ticket.sn=sn ticket.title=title ticket.state=next_state ticket.participant=destination_participant ticket.participant_type=destination_participant_type ticket.act_state=act_state ticket.multi_all_person=multi_all_person ticket.save() # 新增流转记录 TicketFlow.objects.create(ticket=ticket, state=start_state, ticket_data=WfService.get_ticket_all_field_value(ticket), suggestion=rdata.get('suggestion',''), participant_type=State.PARTICIPANT_TYPE_PERSONAL, participant=ticket.create_by, transition=transition) return Response(TicketSerializer(instance=ticket).data) @action(methods=['post'], detail=True, perms_map={'post':'*'}) def handle(self, request, pk=None): """ 处理工单 """ try: ticket = Ticket.objects.get(pk=pk) except: raise APIException('工单不存在') data = request.data result = WfService.ticket_handle_permission_check(ticket, request.user) source_state = ticket.state source_ticket_data = ticket.ticket_data if result.get('permission') is False: raise PermissionDenied(result.get('msg')) # 校验表单必填项目 transition = Transition.objects.get(pk=data['transition']) ticket_data = data['ticket_data'] if transition.field_require_check: for key, value in ticket.state.state_fields.items(): if value == State.STATE_FIELD_REQUIRED: if key not in ticket_data or not ticket_data[key]: raise APIException('字段{}必填'.format(key)) destination_state = WfService.get_next_state_by_transition_and_ticket_info(ticket, transition, ticket_data) multi_all_person = ticket.multi_all_person if multi_all_person: multi_all_person[request.user.id] =dict(transition=transition.id) # 判断所有人处理结果是否一致 if WfService.check_dict_has_all_same_value(multi_all_person): participant_info = WfService.get_ticket_state_participant_info(destination_state, ticket, data['ticket_data']) destination_participant_type = participant_info.get('destination_participant_type', 0) destination_participant = participant_info.get('destination_participant', None) multi_all_person = {} else: # 处理人没有没有全部处理完成或者处理动作不一致 destination_participant_type = ticket.participant_type destination_state = ticket.state # 保持原状态 destination_participant = [] for key, value in multi_all_person.items(): if not value: destination_participant.push(key) else: # 当前处理人类型非全部处理 participant_info = WfService.get_ticket_state_participant_info(destination_state, ticket, data['ticket_data']) destination_participant_type = participant_info.get('destination_participant_type', 0) destination_participant = participant_info.get('destination_participant', None) multi_all_person = participant_info.get('multi_all_person', {}) # 更新工单信息:基础字段及自定义字段, add_relation字段 需要下个处理人是部门、角色等的情况 ticket.state = destination_state ticket.participant_type = destination_participant_type ticket.participant = destination_participant ticket.multi_all_person = multi_all_person if destination_state.type == State.STATE_TYPE_END: ticket.act_state = Ticket.TICKET_ACT_STATE_FINISH elif destination_state.type == State.STATE_TYPE_START: ticket.act_state = Ticket.TICKET_ACT_STATE_DRAFT else: ticket.act_state = Ticket.TICKET_ACT_STATE_ONGOING if transition.attribute_type == Transition.TRANSITION_ATTRIBUTE_TYPE_REFUSE: ticket.act_state = Ticket.TICKET_ACT_STATE_BACK # 只更新必填和可选的字段 for key, value in ticket.state.state_fields.items(): if value in (State.STATE_FIELD_REQUIRED, State.STATE_FIELD_OPTIONAL): source_ticket_data[key] = ticket_data[key] ticket.ticket_data = source_ticket_data ticket.save() # 更新工单流转记录 TicketFlow.objects.create(ticket=ticket, state=source_state, ticket_data=WfService.get_ticket_all_field_value(ticket), suggestion=data.get('suggestion',''), participant_type=State.PARTICIPANT_TYPE_PERSONAL, participant=request.user, transition=transition) return Response(TicketSerializer(instance=ticket).data) @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 flowlogs(self, request, pk=None): """ 工单流转记录 """ ticket = self.get_object() flowlogs = TicketFlow.objects.filter(ticket=ticket).order_by('-create_time') serializer = TicketFlowSerializer(instance=flowlogs, many=True) return Response(serializer.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) @action(methods=['post'], detail=True, perms_map={'post':'*'}) def accpet(self, request, pk=None): """ 接单,当工单当前处理人实际为多个人时(角色、部门、多人都有可能, 注意角色和部门有可能实际只有一人) """ ticket = self.get_object() result = WfService.ticket_handle_permission_check(ticket, request.user) if result.get('need_accept', False): ticket.participant_type = State.PARTICIPANT_TYPE_PERSONAL ticket.participant = request.user.id ticket.save() # 接单日志 # 更新工单流转记录 TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket), suggestion='', participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_ATTRIBUTE_TYPE_ACCEPT, participant=request.user, transition=None) return Response() else: raise APIException('无需接单') @action(methods=['post'], detail=True, perms_map={'post':'*'}) def retreat(self, request, pk=None): """ 撤回工单,允许创建人在指定状态撤回工单至初始状态,状态设置中开启允许撤回 """ ticket = self.get_object() if ticket.create_by != request.user: raise APIException('非创建人不可撤回') if not ticket.state.enable_retreat: raise APIException('该状态不可撤回') start_state = WfService.get_workflow_start_state(ticket.workflow) ticket.state = start_state ticket.participant_type = State.PARTICIPANT_TYPE_PERSONAL ticket.participant = request.user.id ticket.act_state = Ticket.TICKET_ACT_STATE_RETREAT ticket.save() # 更新流转记录 suggestion = request.data.get('suggestion', '') # 撤回原因 TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket), suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_RETREAT, participant=request.user, transition=None) return Response() @action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=TicketAddNodeSerializer) def add_node(self, request, pk=None): """ 加签 """ ticket = self.get_object() data = request.data add_user = User.objects.get(pk=data['user']) ticket.participant_type = State.PARTICIPANT_TYPE_PERSONAL ticket.participant = add_user.id ticket.in_add_node = True ticket.add_node_man = add_user ticket.save() # 更新流转记录 suggestion = request.data.get('suggestion', '') # 加签说明 TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket), suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_ADD_NODE, participant=request.user, transition=None) return Response() @action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=TicketCloseSerializer) def close(self, request, pk=None): """ 关闭工单(创建人在初始状态) """ ticket = self.get_object() if ticket.state.type == State.STATE_TYPE_START and ticket.create_by==request.user: end_state = WfService.get_workflow_end_state(ticket.workflow) ticket.state = end_state ticket.participant_type = 0 ticket.participant = 0 ticket.act_state = Ticket.TICKET_ACT_STATE_CLOSED ticket.save() # 更新流转记录 suggestion = request.data.get('suggestion', '') # 关闭原因 TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket), suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_CLOSE, participant=request.user, transition=None) return Response() else: return Response('工单不可关闭', status=status.HTTP_400_BAD_REQUEST) class TicketFlowViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): """ 工单日志 """ perms_map = {'*':'*'} queryset = TicketFlow.objects.all() serializer_class = TicketFlowSerializer search_fields = ['suggestion'] filterset_fields = ['ticket'] ordering = ['-create_time']