Merge branch 'develop' of https://e.coding.net/ctcdevteam/hberp/hberp into develop

This commit is contained in:
shijing 2021-10-08 08:39:26 +08:00
commit ae58c3bd59
7 changed files with 141 additions and 31 deletions

View File

@ -1,5 +1,6 @@
from django.shortcuts import render
from rest_framework.viewsets import ModelViewSet
from rest_framework.mixins import ListModelMixin
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from apps.inm.models import WareHouse,Inventory
from apps.inm.serializers import WareHouseSerializer, WareHouseCreateUpdateSerializer,InventorySerializer,InventoryCreateUpdateSerializer
@ -23,7 +24,8 @@ class WarehouseViewSet(CreateUpdateModelAMixin, ModelViewSet):
if self.action in ['create', 'update']:
return WareHouseCreateUpdateSerializer
return WareHouseSerializer
class InventoryViewSet(CreateUpdateModelAMixin, ModelViewSet):
class InventoryViewSet(ListModelMixin, GenericViewSet):
"""
物料基本信息-增删改查
"""
@ -33,8 +35,3 @@ class InventoryViewSet(CreateUpdateModelAMixin, ModelViewSet):
filterset_fields = []
ordering_fields = ['create_time']
ordering = ['-create_time']
def get_serializer_class(self):
if self.action in ['create', 'update']:
return InventoryCreateUpdateSerializer
return InventorySerializer

View File

@ -0,0 +1,24 @@
from django_filters import rest_framework as filters
from .models import Ticket
class TicketFilterSet(filters.FilterSet):
start_create = filters.DateFilter(field_name="create_time", lookup_expr='gte')
end_create = filters.DateFilter(field_name="create_time", lookup_expr='lte')
category = filters.ChoiceFilter(choices = Ticket.category_choices, method='filter_category')
class Meta:
model = Ticket
fields = ['workflow', 'state', 'act_state', 'start_create', 'end_create', 'category']
def filter_category(self, queryset, name, value):
user=self.request.user
if value == 'owner':
queryset = queryset.filter(create_by=user)
elif value == 'duty':
queryset = queryset.filter(participant__contains=user.id).exclude(act_state__in=[Ticket.TICKET_ACT_STATE_FINISH, Ticket.TICKET_ACT_STATE_CLOSED])
elif value == 'worked':
queryset = queryset.filter(ticketflow_ticket__participant=user).exclude(create_by=user)
elif value == 'all':
pass
else:
queryset = queryset.none()
return queryset

View File

@ -0,0 +1,31 @@
# Generated by Django 3.2.6 on 2021-09-30 01:54
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('wf', '0010_alter_ticketflow_transition'),
]
operations = [
migrations.AlterField(
model_name='state',
name='state_fields',
field=models.JSONField(default=dict, help_text='json格式字典存储,包括读写属性1只读2必填3可选. 示例:{"create_time":1,"title":2, "sn":1}, 内置特殊字段participant_info.participant_name:当前处理人信息(部门名称、角色名称)state.state_name:当前状态的状态名,workflow.workflow_name:工作流名称', verbose_name='表单字段'),
),
migrations.AlterField(
model_name='ticketflow',
name='participant',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='ticketflow_participant', to=settings.AUTH_USER_MODEL, verbose_name='处理人'),
),
migrations.AlterField(
model_name='ticketflow',
name='ticket',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ticketflow_ticket', to='wf.ticket', verbose_name='关联工单'),
),
]

View File

@ -72,7 +72,7 @@ class State(CommonAModel):
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.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不显示, 字典的字典
state_fields = models.JSONField('表单字段', default=dict, help_text='json格式字典存储,包括读写属性1只读2必填3可选. 示例:{"create_time":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, choices=state_distribute_choices, help_text='1.主动接单(如果当前处理人实际为多人的时候,需要先接单才能处理) 2.直接处理(即使当前处理人实际为多人,也可以直接处理) 3.随机分配(如果实际为多人,则系统会随机分配给其中一个人) 4.全部处理(要求所有参与人都要处理一遍,才能进入下一步)')
class Transition(CommonAModel):
@ -149,6 +149,13 @@ class Ticket(CommonAModel):
(TICKET_ACT_STATE_FINISH, '已完成'),
(TICKET_ACT_STATE_CLOSED, '已关闭')
)
category_choices =(
('all', '全部'),
('owner', '我创建的'),
('duty', '代办'),
('worked', '我处理的'),
('relation', '抄送我的')
)
title = models.CharField('标题', max_length=500, blank=True, default='', help_text="工单标题")
workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='关联工作流')
sn = models.CharField('流水号', max_length=25, help_text="工单的流水号")
@ -168,10 +175,10 @@ class TicketFlow(BaseModel):
"""
工单流转日志
"""
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE, verbose_name='关联工单')
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE, verbose_name='关联工单', related_name='ticketflow_ticket')
transition = models.ForeignKey(Transition, verbose_name='流转id', help_text='与worklow.Transition关联 为0时表示认为干预的操作', on_delete=models.CASCADE, null=True, blank=True)
suggestion = models.CharField('处理意见', max_length=10000, default='', blank=True)
participant_type = models.IntegerField('处理人类型', default=0, help_text='0.无处理人,1.个人,2.多人', choices=State.type2_choices)
participant = models.ForeignKey(User, verbose_name='处理人', on_delete=models.SET_NULL, null=True, blank=True)
participant = models.ForeignKey(User, verbose_name='处理人', on_delete=models.SET_NULL, null=True, blank=True, related_name='ticketflow_participant')
state = models.ForeignKey(State, verbose_name='当前状态', default=0, blank=True, on_delete=models.CASCADE)
ticket_data = models.JSONField('工单数据', default=dict, blank=True, help_text='可以用于记录当前表单数据json格式')

View File

@ -23,7 +23,7 @@ class WorkflowSimpleSerializer(serializers.ModelSerializer):
class StateSimpleSerializer(serializers.ModelSerializer):
class Meta:
model = State
fields = ['id', 'name', 'type']
fields = ['id', 'name', 'type', 'distribute_type']
class TransitionSerializer(serializers.ModelSerializer):
source_state_ = StateSimpleSerializer(source='source_state', read_only=True)
@ -71,6 +71,43 @@ class TicketSerializer(serializers.ModelSerializer):
queryset = queryset.select_related('workflow','state')
return queryset
class TicketListSerializer(serializers.ModelSerializer):
workflow_ = WorkflowSimpleSerializer(source='workflow', read_only=True)
state_ = StateSimpleSerializer(source='state', read_only=True)
class Meta:
model = Ticket
fields = ['id', 'title', 'sn', 'workflow', 'workflow_', 'state', 'state_', 'act_state', 'create_time', 'update_time', 'participant_type']
@staticmethod
def setup_eager_loading(queryset):
queryset = queryset.select_related('workflow','state')
return queryset
class TicketDetailSerializer(serializers.ModelSerializer):
workflow_ = WorkflowSimpleSerializer(source='workflow', read_only=True)
state_ = StateSimpleSerializer(source='state', read_only=True)
ticket_data_ = serializers.SerializerMethodField()
class Meta:
model = Ticket
fields = '__all__'
@staticmethod
def setup_eager_loading(queryset):
queryset = queryset.select_related('workflow','state')
return queryset
def get_ticket_data_(self, obj):
ticket_data = obj.ticket_data
state_fields = obj.state.state_fields
all_fields = CustomField.objects.filter(workflow=obj.workflow).order_by('sort')
all_fields_l = CustomFieldSerializer(instance=all_fields, many=True).data
for i in all_fields_l:
key = i['field_key']
i['field_state'] = state_fields.get(key, 1)
i['field_value'] = ticket_data.get(key, None)
return all_fields_l
class TicketFlowSerializer(serializers.ModelSerializer):
participant_ = UserSimpleSerializer(source='participant', read_only=True)
state_ = StateSimpleSerializer(source='state', read_only=True)

View File

@ -1,3 +1,4 @@
from apps.wf.serializers import CustomFieldSerializer
from apps.wf.serializers import TicketSerializer, TicketSimpleSerializer
from typing import Tuple
from apps.system.models import User
@ -90,20 +91,20 @@ class WfService(object):
@classmethod
def get_next_state_by_transition_and_ticket_info(cls, ticket:Ticket, transition: Transition)->object:
def get_next_state_by_transition_and_ticket_info(cls, ticket:Ticket, transition: Transition, ticket_data:dict={})->object:
"""
获取下个节点状态
"""
# 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
ticket_all_value = cls.get_ticket_all_field_value(ticket)
ticket_all_value.update(**ticket_data)
if transition.condition_expression:
pass
for i in transition.condition_expression:
expression = i['expression'].format(**ticket_all_value)
import datetime, time # 用于支持条件表达式中对时间的操作
if eval(expression):
destination_state = State.objects.get(i['expression'].get('target_state'))
return destination_state
@classmethod
@ -163,21 +164,23 @@ class WfService(object):
state = ticket.state
if participant_type == State.PARTICIPANT_TYPE_PERSONAL:
if user.id != participant:
return dict(permission=False, msg="非当前处理人")
return dict(permission=False, msg="非当前处理人", need_accept=False)
elif participant_type in [State.PARTICIPANT_TYPE_MULTI, State.PARTICIPANT_TYPE_DEPT, State.PARTICIPANT_TYPE_ROLE]:
if user.id not in participant:
return dict(permission=False, msg="非当前处理人")
return dict(permission=False, msg="非当前处理人", need_accept=False)
current_participant_count = len(participant)
if current_participant_count == 1:
if [user.id] != participant:
return dict(permission=False, msg="非当前处理人")
if [user.id] == participant or user.id == participant:
pass
else:
return dict(permission=False, msg="非当前处理人", need_accept=False)
elif current_participant_count >1 and state.distribute_type == State.STATE_DISTRIBUTE_TYPE_ACTIVE:
if user.id not in participant:
return dict(permission=False, msg="非当前处理人")
return dict(permission=False, msg="非当前处理人", need_accept=False)
return dict(permission=False, msg="需要先接单再处理", need_accept=True)
if ticket.in_add_node:
return dict(permission=False, msg="工单当前处于加签中,请加签完成后操作")
return dict(permission=True, msg="")
return dict(permission=False, msg="工单当前处于加签中,请加签完成后操作", need_accept=False)
return dict(permission=True, msg="", need_accept=False)
@classmethod
def check_dict_has_all_same_value(cls, dict_obj: object)->tuple:
@ -212,3 +215,4 @@ class WfService(object):
return field_info_dict

View File

@ -1,8 +1,9 @@
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, TicketCreateSerializer, TicketFlowSerializer, TicketHandleSerializer, TicketSerializer, TransitionSerializer, WorkflowSerializer
from apps.wf.serializers import CustomFieldSerializer, StateSerializer, TicketCreateSerializer, TicketFlowSerializer, TicketHandleSerializer, 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
@ -93,7 +94,7 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
queryset = Ticket.objects.all()
serializer_class = TicketSerializer
search_fields = ['title']
filterset_fields = ['workflow', 'state']
filterset_class = TicketFilterSet
ordering = ['-create_time']
def get_serializer_class(self):
@ -101,8 +102,17 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
return TicketCreateSerializer
elif self.action == 'handle':
return TicketHandleSerializer
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):
"""
新建工单
@ -118,7 +128,7 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
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) # 先创建出来
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)
@ -152,7 +162,7 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
return Response(TicketSerializer(instance=ticket).data)
@action(methods=['post'], detail=True, perms_map={'get':'*'})
@action(methods=['post'], detail=True, perms_map={'post':'*'})
def handle(self, request, pk=None):
"""
处理工单
@ -175,7 +185,7 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
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)
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)