工艺流程存json

This commit is contained in:
caoqianming 2021-08-27 16:20:26 +08:00
parent d677cb42fb
commit ff47f66463
12 changed files with 232 additions and 42 deletions

View File

@ -2,5 +2,5 @@
ENV = 'production'
# base api
VUE_APP_BASE_API = ''
VUE_APP_BASE_API = 'http://47.95.0.242:2222/api'

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.6 on 2021-08-27 02:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mtm', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='stepoperationitem',
name='sort',
field=models.IntegerField(default=1, verbose_name='排序号'),
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 3.2.6 on 2021-08-27 08:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mtm', '0002_stepoperationitem_sort'),
]
operations = [
migrations.RemoveField(
model_name='material',
name='process',
),
migrations.AddField(
model_name='material',
name='processes',
field=models.JSONField(blank=True, default=list, verbose_name='工艺流程'),
),
]

View File

@ -19,7 +19,7 @@ class Material(CommonAModel):
number = models.CharField('编号', max_length=100, unique=True)
type = models.CharField('物料类型', choices= type_choices, max_length=20, default=1)
sort_str = models.CharField('排序字符', max_length=100, null=True, blank=True)
process = models.ManyToManyField('mtm.process', through='mtm.ProductProcess', related_name='product_process')
processes = models.JSONField('工艺流程', default=list, blank=True)
class Meta:
verbose_name = '物料表'

View File

@ -1,43 +1,55 @@
from rest_framework.serializers import ModelSerializer
from rest_framework import serializers
from .models import Material, Process, ProductProcess, Step
from apps.system.serializers import FileSimpleSerializer
class MaterialSerializer(ModelSerializer):
class MaterialSerializer(serializers.ModelSerializer):
processes = serializers.ListField(child=serializers.IntegerField(min_value=1))
class Meta:
model = Material
fields = '__all__'
class MaterialSimpleSerializer(ModelSerializer):
class MaterialDetailSerializer(serializers.ModelSerializer):
processes_ = serializers.SerializerMethodField()
class Meta:
model = Material
fields = '__all__'
def get_processes_(self, obj):
objs = Process.objects.filter(id__in=obj.processes)
return ProcessSimpleSerializer(instance=objs, many=True).data
class MaterialSimpleSerializer(serializers.ModelSerializer):
class Meta:
model = Material
fields = ['id', 'name', 'number']
class ProcessSerializer(ModelSerializer):
class ProcessSerializer(serializers.ModelSerializer):
instruction_ = FileSimpleSerializer(source='instruction', read_only=True)
class Meta:
model = Process
fields = '__all__'
class ProcessSimpleSerializer(ModelSerializer):
class ProcessSimpleSerializer(serializers.ModelSerializer):
class Meta:
model = Process
fields = ['id', 'name', 'number']
class StepSerializer(ModelSerializer):
class StepSerializer(serializers.ModelSerializer):
class Meta:
model = Step
fields = '__all__'
class ProductProcessListSerializer(ModelSerializer):
class ProductProcessListSerializer(serializers.ModelSerializer):
process_ = ProcessSimpleSerializer(source='process', read_only=True)
product_ = MaterialSimpleSerializer(source='product', read_only=True)
class Meta:
model = ProductProcess
fields = '__all__'
class ProductProcessUpdateSerializer(ModelSerializer):
class ProductProcessUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = ProductProcess
fields = ['sort']

View File

@ -3,7 +3,7 @@ from rest_framework.viewsets import ModelViewSet, GenericViewSet
from rest_framework.mixins import CreateModelMixin, ListModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin
from apps.mtm.models import Material, Process, ProductProcess, Step
from apps.mtm.serializers import MaterialSerializer, ProductProcessListSerializer, ProductProcessUpdateSerializer, ProcessSerializer, StepSerializer
from apps.mtm.serializers import MaterialDetailSerializer, MaterialSerializer, MaterialSimpleSerializer, ProductProcessListSerializer, ProductProcessUpdateSerializer, ProcessSerializer, StepSerializer
from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin
from rest_framework.decorators import action
from rest_framework.response import Response
@ -24,14 +24,19 @@ class MaterialViewSet(PageOrNot, CreateUpdateModelAMixin, ModelViewSet):
ordering_fields = ['number', 'sort_str']
ordering = ['number']
@action(methods=['get'], detail=True, perms_map={'get':'*'}, pagination_class=None, serializer_class=StepSerializer)
def steps(self, request, pk=None):
"""
工序下的子工序
"""
process = self.get_object()
serializer = self.serializer_class(instance=Step.objects.filter(process=process, is_deleted=False), many=True)
return Response(serializer.data)
def get_serializer_class(self, *args, **kwargs):
if self.action == 'retrieve':
return MaterialDetailSerializer
return MaterialSerializer
# @action(methods=['get'], detail=True, perms_map={'get':'*'}, pagination_class=None, serializer_class=MaterialSimpleSerializer)
# def processes(self, request, pk=None):
# """
# 产品生产工艺流程
# """
# material = self.get_object()
# serializer = self.serializer_class(instance=Process.objects.filter(id__in=material.processes), many=True)
# return Response(serializer.data)
class ProcessViewSet(PageOrNot, CreateUpdateModelAMixin, ModelViewSet):

View File

@ -0,0 +1,76 @@
# Generated by Django 3.2.6 on 2021-08-27 03:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('wf', '0006_auto_20210825_1542'),
]
operations = [
migrations.RemoveField(
model_name='ticket',
name='add_node_man',
),
migrations.RemoveField(
model_name='ticketflow',
name='participant',
),
migrations.AddField(
model_name='state',
name='distribute_type',
field=models.IntegerField(choices=[(1, '主动接单'), (2, '直接处理'), (3, '随机分配'), (4, '全部处理')], default=1, help_text='1.主动接单(如果当前处理人实际为多人的时候,需要先接单才能处理) 2.直接处理(即使当前处理人实际为多人,也可以直接处理) 3.随机分配(如果实际为多人,则系统会随机分配给其中一个人) 4.全部处理(要求所有参与人都要处理一遍,才能进入下一步)', verbose_name='分配方式'),
),
migrations.AddField(
model_name='state',
name='participant',
field=models.JSONField(blank=True, default=list, help_text='可以为空(无处理人的情况,如结束状态)、userid、userid列表\\部门id\\角色id\\变量(create_by,create_by_tl)\\脚本记录的id等包含子工作流的需要设置处理人为loonrobot', verbose_name='参与者'),
),
migrations.AddField(
model_name='ticket',
name='act_state',
field=models.IntegerField(choices=[(0, '草稿中'), (1, '进行中'), (2, '被退回'), (3, '被撤回'), (4, '已完成'), (5, '已关闭')], default=1, help_text='当前工单的进行状态', verbose_name='进行状态'),
),
migrations.AddField(
model_name='ticket',
name='multi_all_person',
field=models.JSONField(blank=True, default=dict, help_text='需要当前状态处理人全部处理时实际的处理结果json格式', verbose_name='全部处理的结果'),
),
migrations.AddField(
model_name='ticket',
name='participant',
field=models.JSONField(blank=True, default=list, help_text='可以为空(无处理人的情况,如结束状态)、userid、userid列表', verbose_name='当前处理人'),
),
migrations.AddField(
model_name='ticket',
name='participant_type',
field=models.IntegerField(choices=[(0, '无处理人'), (1, '个人'), (2, '多人'), (3, '部门'), (4, '角色'), (5, '变量'), (6, '脚本'), (7, '工单的字段'), (8, '父工单的字段')], default=0, help_text='0.无处理人,1.个人,2.多人', verbose_name='当前处理人类型'),
),
migrations.AddField(
model_name='workflow',
name='sn_prefix',
field=models.CharField(default='hb', max_length=50, verbose_name='流水号前缀'),
),
migrations.AlterField(
model_name='state',
name='participant_type',
field=models.IntegerField(blank=True, choices=[(0, '无处理人'), (1, '个人'), (2, '多人'), (3, '部门'), (4, '角色'), (5, '变量'), (6, '脚本'), (7, '工单的字段'), (8, '父工单的字段')], default=1, help_text='0.无处理人,1.个人,2.多人,3.部门,4.角色,5.变量(支持工单创建人,创建人的leader),6.脚本,7.工单的字段内容(如表单中的"测试负责人",需要为用户名或者逗号隔开的多个用户名),8.父工单的字段内容。 初始状态请选择类型5参与人填create_by', verbose_name='参与者类型'),
),
migrations.AlterField(
model_name='state',
name='type',
field=models.IntegerField(choices=[(0, '普通'), (1, '开始'), (2, '结束')], default=0, help_text='0.普通类型 1.初始状态(用于新建工单时,获取对应的字段必填及transition信息) 2.结束状态(此状态下的工单不得再处理即没有对应的transition)', verbose_name='状态类型'),
),
migrations.AlterField(
model_name='workflow',
name='display_form_str',
field=models.JSONField(blank=True, default=list, help_text='默认"[]",用于用户只有对应工单查看权限时显示哪些字段,field_key的list的json,如["days","sn"],内置特殊字段participant_info.participant_name:当前处理人信息(部门名称、角色名称)state.state_name:当前状态的状态名,workflow.workflow_name:工作流名称', verbose_name='展现表单字段'),
),
migrations.AlterField(
model_name='workflow',
name='limit_expression',
field=models.JSONField(blank=True, default=dict, 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的用户提交工单)', verbose_name='限制表达式'),
),
]

View File

@ -0,0 +1,26 @@
# Generated by Django 3.2.6 on 2021-08-27 03:04
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', '0007_auto_20210827_1103'),
]
operations = [
migrations.AddField(
model_name='ticket',
name='add_node_man',
field=models.ForeignKey(blank=True, help_text='加签操作的人,工单当前处理人处理完成后会回到该处理人,当处于加签状态下才有效', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='加签人'),
),
migrations.AddField(
model_name='ticketflow',
name='participant',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='处理人'),
),
]

View File

@ -12,11 +12,11 @@ class Workflow(CommonAModel):
工作流
"""
name = models.CharField('名称', max_length=50)
sn_prefix = models.CharField('流水号前缀', max_length=50)
sn_prefix = models.CharField('流水号前缀', max_length=50, default='hb')
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的用户提交工单)')
display_form_str = models.JSONField('展现表单字段', max_length=10000, default=list, blank=True, help_text='默认"[]",用于用户只有对应工单查看权限时显示哪些字段,field_key的list的json,如["days","sn"],内置特殊字段participant_info.participant_name:当前处理人信息(部门名称、角色名称)state.state_name:当前状态的状态名,workflow.workflow_name:工作流名称')
limit_expression = models.JSONField('限制表达式', 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的用户提交工单)')
display_form_str = models.JSONField('展现表单字段', default=list, blank=True, help_text='默认"[]",用于用户只有对应工单查看权限时显示哪些字段,field_key的list的json,如["days","sn"],内置特殊字段participant_info.participant_name:当前处理人信息(部门名称、角色名称)state.state_name:当前状态的状态名,workflow.workflow_name:工作流名称')
title_template = models.CharField('标题模板', max_length=50, default='你有一个待办工单:{title}', null=True, blank=True, help_text='工单字段的值可以作为参数写到模板中,格式如:你有一个待办工单:{title}')
content_template = models.CharField('内容模板', max_length=1000, default='标题:{title}, 创建时间:{create_time}', null=True, blank=True, help_text='工单字段的值可以作为参数写到模板中,格式如:标题:{title}, 创建时间:{create_time}')
@ -60,6 +60,10 @@ class State(CommonAModel):
(STATE_DISTRIBUTE_TYPE_RANDOM, '随机分配'),
(STATE_DISTRIBUTE_TYPE_ALL, '全部处理'),
)
STATE_FIELD_READONLY= 1 # 字段只读
STATE_FIELD_REQUIRED = 2 # 字段必填
STATE_FIELD_OPTIONAL = 3 # 字段可选
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中不显示此状态(当前处于此状态时除外)')
@ -69,7 +73,7 @@ class State(CommonAModel):
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不显示, 字典的字典
distribute_type = models.IntegerField('分配方式', default=1, help_text='1.主动接单(如果当前处理人实际为多人的时候,需要先接单才能处理) 2.直接处理(即使当前处理人实际为多人,也可以直接处理) 3.随机分配(如果实际为多人,则系统会随机分配给其中一个人) 4.全部处理(要求所有参与人都要处理一遍,才能进入下一步)')
distribute_type = models.IntegerField('分配方式', default=1, choices=state_distribute_choices, help_text='1.主动接单(如果当前处理人实际为多人的时候,需要先接单才能处理) 2.直接处理(即使当前处理人实际为多人,也可以直接处理) 3.随机分配(如果实际为多人,则系统会随机分配给其中一个人) 4.全部处理(要求所有参与人都要处理一遍,才能进入下一步)')
class Transition(CommonAModel):
"""
@ -150,7 +154,7 @@ class Ticket(CommonAModel):
parent_state = models.ForeignKey(State, null=True, blank=True, on_delete=models.CASCADE, verbose_name='父工单状态', related_name='ticket_parent_state')
ticket_data = models.JSONField('工单数据', default=dict, 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.ForeignKey(User, verbose_name='加签人', on_delete=models.SET_NULL, null=True, blank=True, help_text='加签操作的人,工单当前处理人处理完成后会回到该处理人,当处于加签状态下才有效')
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列表')
@ -164,6 +168,6 @@ class TicketFlow(BaseModel):
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE, verbose_name='关联工单')
transition = models.ForeignKey(Transition, verbose_name='流转id', help_text='与worklow.Transition关联 为0时表示认为干预的操作', on_delete=models.CASCADE)
suggestion = models.CharField('处理意见', max_length=10000, default='', blank=True)
participant = models.CharField('处理人', max_length=50, default='', blank=True)
participant = models.ForeignKey(User, verbose_name='处理人', on_delete=models.SET_NULL, null=True, blank=True)
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

@ -48,6 +48,10 @@ class TicketCreateSerializer(serializers.ModelSerializer):
class Meta:
model=Ticket
fields=['title','workflow','ticket_data', 'transition']
def create(self, validated_data):
validated_data.pop('transition')
return super().create(validated_data)
class TicketSerializer(serializers.ModelSerializer):
workflow_ = WorkflowSimpleSerializer(source='workflow', read_only=True)

View File

@ -114,26 +114,26 @@ class WfService(object):
回到初始状态
"""
return dict(destination_participant_type=State.PARTICIPANT_TYPE_PERSONAL,
destination_participant=ticket.create_by,
multi_all_person=dict())
destination_participant=ticket.create_by.id,
multi_all_person={})
elif state.type == State.STATE_TYPE_END:
"""
到达结束状态
"""
return dict(destination_participant_type=State.PARTICIPANT_TYPE_PERSONAL,
destination_participant='',
multi_all_person=dict())
destination_participant=None,
multi_all_person={})
multi_all_person_dict = {}
destination_participant_type, destination_participant = State.participant_type, State.participant
if destination_participant_type == State.PARTICIPANT_TYPE_FIELD:
destination_participant = ticket_data.get(destination_participant, '') if destination_participant in ticket_data else Ticket.ticket_data.get(destination_participant, '')
destination_participant = ticket_data.get(destination_participant, None) if destination_participant in ticket_data else Ticket.ticket_data.get(destination_participant, None)
if destination_participant_type == State.PARTICIPANT_TYPE_DEPT:#单部门
destination_participant = User.objects.filter(dept=destination_participant).values_list('id')
destination_participant = list(User.objects.filter(dept=destination_participant).values_list('id', flat=True))
if destination_participant_type == State.PARTICIPANT_TYPE_ROLE:#单角色
destination_participant = User.objects.filter(roles=destination_participant).values_list('id')
destination_participant = list(User.objects.filter(roles=destination_participant).values_list('id', flat=True))
if type(destination_participant) == list:
destination_participant_type = State.PARTICIPANT_TYPE_MULTI

View File

@ -5,7 +5,7 @@ from apps.wf.serializers import CustomFieldSerializer, StateSerializer, TicketCr
from django.shortcuts import 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
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
@ -104,27 +104,50 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
"""
新建工单
"""
serializer = self.get_serializer(data=request.data)
rdata = request.data
serializer = self.get_serializer(data=rdata)
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']
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 == 2:
if value == State.STATE_FIELD_REQUIRED:
if key not in ticket_data or not ticket_data[key]:
raise APIException('字段{}必填'.format(key))
ticket = serializer.save()
ticket = serializer.save(state=start_state) # 先创建出来
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)
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
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=ticket_data,
suggestion=rdata.get('suggestion',''), participant_type=State.PARTICIPANT_TYPE_PERSONAL,
particant=ticket.create_by)
return Response(serializer.data)
@action(methods=['get'], detail=True, perms_map={'get':'*'})
def flowsteps(self, request, pk=None):