diff --git a/hb_server/apps/wf/filters.py b/hb_server/apps/wf/filters.py index 677b894..bffa820 100644 --- a/hb_server/apps/wf/filters.py +++ b/hb_server/apps/wf/filters.py @@ -18,7 +18,7 @@ class TicketFilterSet(filters.FilterSet): elif value == 'worked': queryset = queryset.filter(ticketflow_ticket__participant=user).exclude(create_by=user).order_by('-update_time').distinct() elif value == 'cc': - pass + queryset = queryset.filter(ticketflow_ticket__participant_cc__contains=user.id).exclude(create_by=user).order_by('-update_time').distinct() elif value == 'all': pass else: diff --git a/hb_server/apps/wf/migrations/0016_auto_20211024_2349.py b/hb_server/apps/wf/migrations/0016_auto_20211024_2349.py new file mode 100644 index 0000000..c0fc04f --- /dev/null +++ b/hb_server/apps/wf/migrations/0016_auto_20211024_2349.py @@ -0,0 +1,53 @@ +# Generated by Django 3.2.6 on 2021-10-24 15:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wf', '0015_auto_20211021_1049'), + ] + + operations = [ + migrations.AddField( + model_name='state', + name='participant_cc', + field=models.JSONField(blank=True, default=list, help_text='抄送给(userid列表)', verbose_name='抄送给'), + ), + migrations.AddField( + model_name='ticketflow', + name='participant_cc', + field=models.JSONField(blank=True, default=list, help_text='抄送给(userid列表)', verbose_name='抄送给'), + ), + migrations.AddField( + model_name='wfscript', + name='func_str', + field=models.CharField(blank=True, max_length=50, null=True, verbose_name='函数名'), + ), + migrations.AlterField( + model_name='state', + name='participant_type', + field=models.IntegerField(blank=True, choices=[(0, '无处理人'), (1, '个人'), (2, '多人'), (4, '角色'), (6, '脚本'), (7, '工单的字段'), (9, '代码获取')], default=1, help_text='0.无处理人,1.个人,2.多人,3.部门,4.角色,5.变量(支持工单创建人,创建人的leader),6.脚本,7.工单的字段内容(如表单中的"测试负责人",需要为用户名或者逗号隔开的多个用户名),8.父工单的字段内容。 初始状态请选择类型5,参与人填create_by', verbose_name='参与者类型'), + ), + migrations.AlterField( + model_name='ticket', + name='participant_type', + field=models.IntegerField(choices=[(0, '无处理人'), (1, '个人'), (2, '多人'), (4, '角色'), (6, '脚本'), (7, '工单的字段'), (9, '代码获取')], default=0, help_text='0.无处理人,1.个人,2.多人', verbose_name='当前处理人类型'), + ), + migrations.AlterField( + model_name='ticketflow', + name='intervene_type', + field=models.IntegerField(choices=[(0, '正常处理'), (1, '转交'), (2, '加签'), (3, '加签处理完成'), (4, '接单'), (5, '评论'), (6, '删除'), (7, '强制关闭'), (8, '强制修改状态'), (9, 'hook操作'), (10, '撤回'), (11, '抄送')], default=0, help_text='流转类型', verbose_name='干预类型'), + ), + migrations.AlterField( + model_name='ticketflow', + name='participant_type', + field=models.IntegerField(choices=[(0, '无处理人'), (1, '个人'), (2, '多人'), (4, '角色'), (6, '脚本'), (7, '工单的字段'), (9, '代码获取')], default=0, help_text='0.无处理人,1.个人,2.多人', verbose_name='处理人类型'), + ), + migrations.AlterField( + model_name='wfscript', + name='usage', + field=models.IntegerField(choices=[(1, '获取处理人'), (2, '执行操作')], default=2, verbose_name='脚本用途'), + ), + ] diff --git a/hb_server/apps/wf/models.py b/hb_server/apps/wf/models.py index 6133090..4bc7d75 100644 --- a/hb_server/apps/wf/models.py +++ b/hb_server/apps/wf/models.py @@ -85,6 +85,7 @@ class State(CommonAModel): 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.全部处理(要求所有参与人都要处理一遍,才能进入下一步)') filter_policy = models.IntegerField('参与人过滤策略', default=0, choices=state_filter_choices) + participant_cc = models.JSONField('抄送给', default=list, blank=True, help_text='抄送给(userid列表)') class Transition(CommonAModel): """ @@ -108,6 +109,7 @@ class Transition(CommonAModel): TRANSITION_INTERVENE_TYPE_ALTER_STATE = 8 # 强制修改状态操作 TRANSITION_INTERVENE_TYPE_HOOK = 9 # hook操作 TRANSITION_INTERVENE_TYPE_RETREAT = 10 # 撤回 + TRANSITION_INTERVENE_TYPE_CC = 11 # 抄送 intervene_type_choices = ( (0, '正常处理'), @@ -120,7 +122,8 @@ class Transition(CommonAModel): (TRANSITION_INTERVENE_TYPE_CLOSE, '强制关闭'), (TRANSITION_INTERVENE_TYPE_ALTER_STATE, '强制修改状态'), (TRANSITION_INTERVENE_TYPE_HOOK, 'hook操作'), - (TRANSITION_INTERVENE_TYPE_RETREAT, '撤回') + (TRANSITION_INTERVENE_TYPE_RETREAT, '撤回'), + (TRANSITION_INTERVENE_TYPE_CC, '抄送') ) name = models.CharField('操作', max_length=50) @@ -219,6 +222,7 @@ class TicketFlow(BaseModel): 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格式') intervene_type = models.IntegerField('干预类型', default=0, help_text='流转类型', choices=Transition.intervene_type_choices) + participant_cc = models.JSONField('抄送给', default=list, blank=True, help_text='抄送给(userid列表)') class WfScript(CommonAModel): """ @@ -228,7 +232,7 @@ class WfScript(CommonAModel): (1, '获取处理人'), (2, '执行操作'), ) - usage = models.IntegerField('脚本用途', default=1, choices=usage_choices) + usage = models.IntegerField('脚本用途', default=2, choices=usage_choices) wait = models.BooleanField('是否等待执行完成', default=True) name = models.CharField('脚本名称', max_length=100) workflow = models.ForeignKey(Workflow, verbose_name='关联工作流', null=True, blank=True, on_delete=models.SET_NULL) diff --git a/hb_server/apps/wf/services.py b/hb_server/apps/wf/services.py index bb7f705..f45c1b5 100644 --- a/hb_server/apps/wf/services.py +++ b/hb_server/apps/wf/services.py @@ -8,6 +8,7 @@ from django.utils import timezone from datetime import timedelta import random from .scripts import GetParticipants +from utils.queryset import get_parent_queryset class WfService(object): @staticmethod @@ -159,11 +160,14 @@ class WfService(object): user_queryset = User.objects.filter(roles__in=destination_participant) # 如果选择了角色, 需要走过滤策略 if ticket.filter_policy == 1: - user_queryset = user_queryset.filter(dept=ticket.belong_dept) + depts = get_parent_queryset(ticket.belong_dept) + user_queryset = user_queryset.filter(dept__in=depts) elif ticket.filter_policy == 2: - user_queryset = user_queryset.filter(dept=ticket.create_by.dept) + depts = get_parent_queryset(ticket.create_by.dept) + user_queryset = user_queryset.filter(dept__in=depts) elif ticket.filter_policy == 3: - user_queryset = user_queryset.filter(dept=request.user.dept) + depts = get_parent_queryset(request.user.dept) + user_queryset = user_queryset.filter(dept__in=depts) destination_participant = list(user_queryset.values_list('id', flat=True)) if type(destination_participant) == list: destination_participant_type = State.PARTICIPANT_TYPE_MULTI diff --git a/hb_server/apps/wf/views.py b/hb_server/apps/wf/views.py index b3158cc..e97cbb4 100644 --- a/hb_server/apps/wf/views.py +++ b/hb_server/apps/wf/views.py @@ -18,6 +18,7 @@ from rest_framework import status from django.db.models import Count from .scripts import GetParticipants + # Create your views here. class FromCodeListView(APIView): def get(self, request, format=None): @@ -144,6 +145,7 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin 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, belong_dept=request.user.dept) # 先创建出来 + 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) @@ -174,6 +176,16 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin 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) + # 开始状态需要抄送 + if start_state.participant_cc: + TicketFlow.objects.create(ticket=ticket, state=ticket.start_state, + participant_type=0, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_CC, + participant=None, participant_cc=start_state.participant_cc) + # 目标状态需要抄送 + if next_state.participant_cc: + TicketFlow.objects.create(ticket=ticket, state=ticket.next_state, + participant_type=0, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_CC, + participant=None, participant_cc=next_state.participant_cc) return Response(TicketSerializer(instance=ticket).data) @action(methods=['get'], detail=False, perms_map={'get':'*'}) @@ -228,7 +240,7 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin destination_participant = [] for key, value in multi_all_person.items(): if not value: - destination_participant.push(key) + destination_participant.append(key) else: # 当前处理人类型非全部处理 participant_info = WfService.get_ticket_state_participant_info(destination_state, ticket, data['ticket_data']) @@ -262,6 +274,11 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin 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) + # 目标状态需要抄送 + if destination_state.participant_cc: + TicketFlow.objects.create(ticket=ticket, state=ticket.destination_state, + participant_type=0, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_CC, + participant=None, participant_cc=destination_state.participant_cc) return Response(TicketSerializer(instance=ticket).data) diff --git a/hb_server/apps/wpm/models.py b/hb_server/apps/wpm/models.py index 6d03ea9..3e34f34 100644 --- a/hb_server/apps/wpm/models.py +++ b/hb_server/apps/wpm/models.py @@ -5,11 +5,11 @@ from django.db.models.query import QuerySet from apps.system.models import CommonAModel, CommonBModel, Organization, User, Dict, File from utils.model import SoftModel, BaseModel from simple_history.models import HistoricalRecords -from apps.mtm.models import Material, Step +from apps.mtm.models import Material, Step, RecordForm -class Good(CommonAModel): +class Product(CommonAModel): """ - 物品 + 产品(所有生产过程中出现过的) """ act_state_choices=( (0, '待执行'), @@ -20,20 +20,27 @@ class Good(CommonAModel): m_state = models.ForeignKey(Material, verbose_name='所属物料状态', on_delete=models.CASCADE) p_state = models.ForeignKey(Step, verbose_name='所在步骤', on_delete=models.CASCADE, null=True, blank=True) act_state = models.IntegerField('进行状态', default=0) + remark = models.CharField('备注', max_length=200, null=True, blank=True) + +class ProductForm(CommonAModel): + """ + 记录表格 + """ + record_form = models.ForeignKey(RecordForm, verbose_name='所用表格', on_delete=models.CASCADE) + data = models.JSONField('记录的数据', default=dict, blank=True) -class GoodFlow(BaseModel): +class ProductFlow(BaseModel): """ - 物品流转日志 + 产品流转日志 """ - pass + product = models.ForeignKey(Product, verbose_name='产品', on_delete=models.CASCADE) class Vendor(CommonAModel): """ 供应商信息 """ - name = models.CharField('供应商名称', max_length=50, unique=True) contact = models.CharField('联系人', max_length=20) contact_phone = models.CharField('联系电话', max_length=11, unique=True) diff --git a/hb_server/utils/queryset.py b/hb_server/utils/queryset.py index 80c1d3d..73a6bb1 100644 --- a/hb_server/utils/queryset.py +++ b/hb_server/utils/queryset.py @@ -57,4 +57,14 @@ def get_child_queryset2(obj, hasParent=True): while child_queryset: queryset = queryset | child_queryset child_queryset = cls.objects.filter(parent__in=child_queryset) - return queryset \ No newline at end of file + return queryset + +def get_parent_queryset(obj, hasSelf=True): + cls = type(obj) + ids = [] + if hasSelf: + ids.append(obj.id) + while obj.parent: + obj = obj.parent + ids.append(obj.id) + return cls.objects.filter(id__in=ids) \ No newline at end of file