From eda2c2365981ec5662a17e9f6c37cf69f71b82e0 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Mon, 16 Aug 2021 17:25:40 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0workflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hb_client/.env.development | 2 +- hb_server/apps/em/apps.py | 2 +- hb_server/apps/hrm/apps.py | 2 +- hb_server/apps/monitor/views.py | 30 +++++++---- hb_server/apps/pum/apps.py | 2 +- hb_server/apps/sam/apps.py | 2 +- hb_server/apps/wf/__init__.py | 0 hb_server/apps/wf/admin.py | 3 ++ hb_server/apps/wf/apps.py | 7 +++ hb_server/apps/wf/models.py | 88 ++++++++++++++++++++++++++++++++ hb_server/apps/wf/serializers.py | 9 ++++ hb_server/apps/wf/tests.py | 3 ++ hb_server/apps/wf/urls.py | 12 +++++ hb_server/apps/wf/views.py | 18 +++++++ hb_server/server/settings.py | 5 ++ 15 files changed, 171 insertions(+), 14 deletions(-) create mode 100644 hb_server/apps/wf/__init__.py create mode 100644 hb_server/apps/wf/admin.py create mode 100644 hb_server/apps/wf/apps.py create mode 100644 hb_server/apps/wf/models.py create mode 100644 hb_server/apps/wf/serializers.py create mode 100644 hb_server/apps/wf/tests.py create mode 100644 hb_server/apps/wf/urls.py create mode 100644 hb_server/apps/wf/views.py diff --git a/hb_client/.env.development b/hb_client/.env.development index f0aff47..21478c7 100644 --- a/hb_client/.env.development +++ b/hb_client/.env.development @@ -2,7 +2,7 @@ ENV = 'development' # base api -VUE_APP_BASE_API = 'http://localhost:8000/api' +VUE_APP_BASE_API = 'http://47.95.0.242:2222/api' # vue-cli uses the VUE_CLI_BABEL_TRANSPILE_MODULES environment variable, # to control whether the babel-plugin-dynamic-import-node plugin is enabled. diff --git a/hb_server/apps/em/apps.py b/hb_server/apps/em/apps.py index d05d748..0b795c8 100644 --- a/hb_server/apps/em/apps.py +++ b/hb_server/apps/em/apps.py @@ -1,6 +1,6 @@ from django.apps import AppConfig -class SystemConfig(AppConfig): +class EmConfig(AppConfig): name = 'apps.em' verbose_name = '设备仪器管理' diff --git a/hb_server/apps/hrm/apps.py b/hb_server/apps/hrm/apps.py index d2dc3ce..852f1e7 100644 --- a/hb_server/apps/hrm/apps.py +++ b/hb_server/apps/hrm/apps.py @@ -1,6 +1,6 @@ from django.apps import AppConfig -class SystemConfig(AppConfig): +class HrmConfig(AppConfig): name = 'apps.hrm' verbose_name = '人力资源管理' diff --git a/hb_server/apps/monitor/views.py b/hb_server/apps/monitor/views.py index 3fa290d..3e6cee4 100644 --- a/hb_server/apps/monitor/views.py +++ b/hb_server/apps/monitor/views.py @@ -30,23 +30,35 @@ class ServerInfoView(APIView): return Response(ret) class LogView(APIView): - + def get(self, request, *args, **kwargs): """ 查看最近的日志列表 + :query name """ - logs =[] + logs =[] + name = request.GET.get('name', None) for root, dirs, files in os.walk(settings.LOG_PATH): for file in files: if len(logs)>50:break filepath = os.path.join(root, file) - fsize = os.path.getsize(filepath) - if fsize: - logs.append({ - "name":file, - "filepath":filepath, - "size":round(fsize/1000,1) - }) + if name: + if filepath.contains(name): + fsize = os.path.getsize(filepath) + if fsize: + logs.append({ + "name":file, + "filepath":filepath, + "size":round(fsize/1000,1) + }) + else: + fsize = os.path.getsize(filepath) + if fsize: + logs.append({ + "name":file, + "filepath":filepath, + "size":round(fsize/1000,1) + }) return Response(logs) class LogDetailView(APIView): diff --git a/hb_server/apps/pum/apps.py b/hb_server/apps/pum/apps.py index 5032791..c48013e 100644 --- a/hb_server/apps/pum/apps.py +++ b/hb_server/apps/pum/apps.py @@ -1,6 +1,6 @@ from django.apps import AppConfig -class SystemConfig(AppConfig): +class PumConfig(AppConfig): name = 'apps.pum' verbose_name = '采购管理' diff --git a/hb_server/apps/sam/apps.py b/hb_server/apps/sam/apps.py index bc73763..44a0d59 100644 --- a/hb_server/apps/sam/apps.py +++ b/hb_server/apps/sam/apps.py @@ -1,6 +1,6 @@ from django.apps import AppConfig -class SystemConfig(AppConfig): +class SamConfig(AppConfig): name = 'apps.sam' verbose_name = '销售管理' diff --git a/hb_server/apps/wf/__init__.py b/hb_server/apps/wf/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hb_server/apps/wf/admin.py b/hb_server/apps/wf/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/hb_server/apps/wf/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/hb_server/apps/wf/apps.py b/hb_server/apps/wf/apps.py new file mode 100644 index 0000000..f0709ed --- /dev/null +++ b/hb_server/apps/wf/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + +class WfConfig(AppConfig): + name = 'apps.wf' + verbose_name = '工作流管理' + + diff --git a/hb_server/apps/wf/models.py b/hb_server/apps/wf/models.py new file mode 100644 index 0000000..eff4766 --- /dev/null +++ b/hb_server/apps/wf/models.py @@ -0,0 +1,88 @@ +from django.db import models +from django.db.models.base import Model +import django.utils.timezone as timezone +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 + + +class Workflow(CommonAModel): + """ + 工作流 + """ + name = models.CharField('名称', max_length=50) + description = models.CharField('描述', max_length=200) + view_permission_check = models.BooleanField('查看权限校验', default=True, help_text='开启后,只允许工单的关联人(创建人、曾经的处理人)有权限查看工单') + title_template = models.CharField('标题模板', max_length=50, default='你有一个待办工单:{title}', null=True, blank=True, help_text='工单字段的值可以作为参数写到模板中,格式如:你有一个待办工单:{title}') + +class State(BaseModel): + """ + 状态记录 + """ + type_choices = ( + (0, '普通类型'), + (1, '初始状态'), + (2, '结束状态') + ) + 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中不显示此状态(当前处于此状态时除外)') + sort = models.IntegerField('状态顺序', default=0, help_text='用于工单步骤接口时,step上状态的顺序(因为存在网状情况,所以需要人为设定顺序),值越小越靠前') + 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('参与者类型', default=1, blank=True, help_text='0.无处理人,1.个人,2.多人,3.部门,4.角色,5.变量(支持工单创建人,创建人的leader),6.脚本,7.工单的字段内容(如表单中的"测试负责人",需要为用户名或者逗号隔开的多个用户名),8.父工单的字段内容。 初始状态请选择类型5,参与人填creator') + + +class Transition(BaseModel): + """ + 工作流流转,定时器,条件(允许跳过), 条件流转与定时器不可同时存在 + """ + attribute_type_choices = ( + (1, '同意'), + (2, '拒绝'), + (3, '其他') + ) + name = models.CharField('操作', max_length=50) + workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='所属工作流') + timer = models.IntegerField('定时器(单位秒)', default=0, help_text='单位秒。处于源状态X秒后如果状态都没有过变化则自动流转到目标状态。设置时间有效') + source_state = models.ForeignKey(State, on_delete=models.CASCADE, verbose_name='源状态', related_name='sstate_transition') + destination_state = models.ForeignKey(State, on_delete=models.CASCADE, verbose_name='目的状态', related_name='dstate_transition') + condition_expression = models.JSONField('条件表达式', max_length=1000, default='[]', help_text='流转条件表达式,根据表达式中的条件来确定流转的下个状态,格式为[{"expression":"{days} > 3 and {days}<10", "target_state":11}] 其中{}用于填充工单的字段key,运算时会换算成实际的值,当符合条件下个状态将变为target_state_id中的值,表达式只支持简单的运算或datetime/time运算.loonflow会以首次匹配成功的条件为准,所以多个条件不要有冲突' ) + attribute_type = models.IntegerField('属性类型', default=1, choices=attribute_type_choices, help_text='属性类型,1.同意,2.拒绝,3.其他') + field_require_check = models.BooleanField('是否校验必填项', default=True, help_text='默认在用户点击操作的时候需要校验工单表单的必填项,如果设置为否则不检查。用于如"退回"属性的操作,不需要填写表单内容') + + +class CustomField(BaseModel): + """自定义字段, 设定某个工作流有哪些自定义字段""" + field_type_choices = ( + ('string', '字符串'), + ('int', '整型'), + ('float', '浮点'), + ('bol', '布尔'), + ('date', '日期'), + ('datetime', '日期时间'), + ('radio', '单选'), + ('checkbox', '多选'), + ('select', '单选下拉'), + ('mutiselect', '多选下拉'), + ('textarea', '文本域'), + ('selectuser', '单选用户'), + ('selectusers', '多选用户'), + ('file', '附件') + ) + workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='所属工作流') + field_type = models.IntegerField('类型', help_text='5.字符串,10.整形,15.浮点型,20.布尔,25.日期,30.日期时间,35.单选框,40.多选框,45.下拉列表,50.多选下拉列表,55.文本域,60.用户名, 70.多选的用户名, 80.附件(只保存路径,多个使用逗号隔开)') + field_key = models.CharField('字段标识', max_length=50, help_text='字段类型请尽量特殊,避免与系统中关键字冲突') + field_name = models.CharField('字段名称', max_length=50) + sort = models.IntegerField('排序', default=0, help_text='工单基础字段在表单中排序为:流水号0,标题20,状态id40,状态名41,创建人80,创建时间100,更新时间120.前端展示工单信息的表单可以根据这个id顺序排列') + default_value = models.CharField('默认值', null=True, blank=True, max_length=100, help_text='前端展示时,可以将此内容作为表单中的该字段的默认值') + description = models.CharField('描述', max_length=100, blank=True, default='', help_text='字段的描述信息,可用于显示在字段的下方对该字段的详细描述') + placeholder = models.CharField('占位符', max_length=100, blank=True, default='', help_text='用户工单详情表单中作为字段的占位符显示') + field_template = models.TextField('文本域模板', default='', blank=True, help_text='文本域类型字段前端显示时可以将此内容作为字段的placeholder') + boolean_field_display = models.JSONField('布尔类型显示名', default='{}', blank=True, + help_text='当为布尔类型时候,可以支持自定义显示形式。{"1":"是","0":"否"}或{"1":"需要","0":"不需要"},注意数字也需要引号') + field_choice = models.JSONField('radio、checkbox、select的选项', default='{}', blank=True, + help_text='radio,checkbox,select,multiselect类型可供选择的选项,格式为json如:{"1":"中国", "2":"美国"},注意数字也需要引号') + label = models.JSONField('标签', blank=True, default='{}', help_text='自定义标签,json格式,调用方可根据标签自行处理特殊场景逻辑,loonflow只保存文本内容') \ No newline at end of file diff --git a/hb_server/apps/wf/serializers.py b/hb_server/apps/wf/serializers.py new file mode 100644 index 0000000..4fb91c4 --- /dev/null +++ b/hb_server/apps/wf/serializers.py @@ -0,0 +1,9 @@ +from rest_framework.serializers import ModelSerializer + +from .models import Workflow + + +class WorkflowSerializer(ModelSerializer): + class Meta: + model = Workflow + fields = '__all__' diff --git a/hb_server/apps/wf/tests.py b/hb_server/apps/wf/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/hb_server/apps/wf/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/hb_server/apps/wf/urls.py b/hb_server/apps/wf/urls.py new file mode 100644 index 0000000..a81ad24 --- /dev/null +++ b/hb_server/apps/wf/urls.py @@ -0,0 +1,12 @@ +from django.db.models import base +from rest_framework import urlpatterns +from apps.pum.views import VendorViewSet +from django.urls import path, include +from rest_framework.routers import DefaultRouter + +router = DefaultRouter() +router.register('vendor', VendorViewSet, basename='vendor') +urlpatterns = [ + path('', include(router.urls)), +] + diff --git a/hb_server/apps/wf/views.py b/hb_server/apps/wf/views.py new file mode 100644 index 0000000..bbba501 --- /dev/null +++ b/hb_server/apps/wf/views.py @@ -0,0 +1,18 @@ +from apps.wf.serializers import WorkflowSerializer +from django.shortcuts import render +from rest_framework.viewsets import ModelViewSet + +from apps.wf.models import Workflow, State, Transition +from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin + + +# 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'] \ No newline at end of file diff --git a/hb_server/server/settings.py b/hb_server/server/settings.py index 20ca66c..a48a0e9 100644 --- a/hb_server/server/settings.py +++ b/hb_server/server/settings.py @@ -197,6 +197,11 @@ CELERYD_MAX_TASKS_PER_CHILD = 100 # 每个worker最多执行300个任务就会 CELERY_TIMEZONE = 'Asia/Shanghai' # 设置时区 CELERY_ENABLE_UTC = True # 启动时区设置 +# swagger配置 +SWAGGER_SETTINGS = { + 'LOGIN_URL':'/api/admin/login/', + 'LOGOUT_URL':'/api/admin/logout/' +} # 日志配置 # 创建日志的路径