This commit is contained in:
shilixia 2021-08-17 09:51:12 +08:00
commit a18cabf86d
28 changed files with 351 additions and 36 deletions

View File

@ -218,6 +218,16 @@ export const asyncRoutes = [
}
]
},
{
path: 'docs-link2',
component: Layout,
children: [
{
path: process.env.VUE_APP_BASE_API + '/swagger/',
meta: { title: 'Swagger文档', icon: 'link', perms: ['dev_docs'] }
}
]
},
{
path: 'admin-link',
component: Layout,

View File

@ -1,6 +1,6 @@
from django.apps import AppConfig
class SystemConfig(AppConfig):
class EmConfig(AppConfig):
name = 'apps.em'
verbose_name = '设备仪器管理'

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.6 on 2021-08-16 01:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('em', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='equipment',
name='model',
field=models.CharField(blank=True, max_length=60, null=True, verbose_name='规格型号'),
),
]

View File

@ -35,5 +35,5 @@ class Equipment(CommonBModel):
verbose_name_plural = verbose_name
def __str__(self):
return self.name + '-' + self.name
return self.number + '-' + self.name

View File

@ -3,11 +3,11 @@ from rest_framework.viewsets import ModelViewSet
from apps.em.models import Equipment
from apps.em.serializers import EquipmentSerializer
from apps.system.mixins import OptimizationMixin
from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin
# Create your views here.
class EquipmentViewSet(ModelViewSet):
class EquipmentViewSet(CreateUpdateModelAMixin, OptimizationMixin, ModelViewSet):
"""
设备台账-增删改查
"""

View File

@ -1,7 +1,9 @@
from django.apps import AppConfig
class SystemConfig(AppConfig):
name = 'apps.sam'
class HrmConfig(AppConfig):
name = 'apps.hrm'
verbose_name = '人力资源管理'
def ready(self):
import apps.hrm.signals

View File

@ -0,0 +1,39 @@
# Generated by Django 3.2.6 on 2021-08-13 09:16
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Employee',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('number', models.CharField(blank=True, max_length=50, null=True, unique=True, verbose_name='人员编号')),
('photo', models.CharField(blank=True, max_length=1000, null=True, verbose_name='证件照')),
('ID_number', models.CharField(blank=True, max_length=100, null=True, verbose_name='身份证号')),
('gender', models.CharField(default='', max_length=10, verbose_name='性别')),
('signature', models.CharField(blank=True, max_length=200, null=True, verbose_name='签名图片')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='employee_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='employee_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='employee_user', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': '员工补充信息',
'verbose_name_plural': '员工补充信息',
},
),
]

View File

@ -9,23 +9,18 @@ from simple_history.models import HistoricalRecords
class Employee(BaseModel):
class Employee(CommonAModel):
"""
员工信息
"""
job_choices = (
('在职', '在职'),
('离职', '离职'),
)
name = models.CharField('姓名', max_length=50)
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='employee_user')
number = models.CharField('人员编号', max_length=50,null=True, blank=True, unique=True)
gender = models.CharField('性别', max_length=10,null=True, blank=True)
phone = models.CharField('手机号', max_length=11,null=True, blank=True, unique=True)
jobstate = models.CharField('在职状态', max_length=11,choices=job_choices, default='在职')
dept = models.ForeignKey(Organization, verbose_name='关联部门', on_delete=models.CASCADE, related_name='employee_dept')
description = models.CharField('描述', max_length=200, blank=True, null=True)
photo = models.CharField('证件照', max_length=1000, null=True, blank=True)
ID_number = models.CharField('身份证号', max_length=100, null=True, blank=True)
gender = models.CharField('性别', max_length=10, default='')
signature = models.CharField('签名图片', max_length=200, null=True, blank=True)
class Meta:
verbose_name = '客户信息'
verbose_name = '员工补充信息'
verbose_name_plural = verbose_name
def __str__(self):

View File

@ -0,0 +1,22 @@
from apps.system.models import User
from rest_framework.serializers import ModelSerializer
from .models import Employee
from apps.system.serializers import UserListSerializer, UserSimpleSerializer
from django.db.models.query import Prefetch
class EmployeeSerializer(ModelSerializer):
# user_ = UserListSerializer(source='user', read_only=True)
class Meta:
model = Employee
fields = '__all__'
# @staticmethod
# def setup_eager_loading(queryset):
# """ Perform necessary eager loading of data. """
# queryset = queryset.select_related('user', 'user__dept')
# # queryset = queryset.prefetch_related('user','user__dept')
# queryset = queryset.prefetch_related(
# Prefetch('user_',
# queryset=User.objects.filter(employee_user__isnull=True))
# )
# return queryset

View File

@ -0,0 +1,9 @@
from django.db.models.signals import post_save
from apps.system.models import User
from django.dispatch import receiver
from apps.hrm.models import Employee
@receiver(post_save, sender=User)
def createEmployee(sender, instance, created, **kwargs):
if created:
Employee.objects.get_or_create(user=instance)

View File

@ -0,0 +1,12 @@
from django.db.models import base
from rest_framework import urlpatterns
from apps.hrm.views import EmployeeViewSet
from django.urls import path, include
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('employee', EmployeeViewSet, basename='employee')
urlpatterns = [
path('', include(router.urls)),
]

View File

@ -1,3 +1,15 @@
from django.shortcuts import render
from rest_framework.viewsets import ModelViewSet, GenericViewSet
from rest_framework.mixins import UpdateModelMixin, RetrieveModelMixin
from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin
from apps.hrm.models import Employee
from apps.hrm.serializers import EmployeeSerializer
# Create your views here.
class EmployeeViewSet(CreateUpdateModelAMixin, OptimizationMixin, UpdateModelMixin, RetrieveModelMixin, GenericViewSet):
"""
员工详细信息
"""
perms_map = {'get': '*', 'put': 'employee_update'}
queryset = Employee.objects.all()
serializer_class = EmployeeSerializer
ordering = ['-pk']

View File

@ -1,5 +1,6 @@
from django.shortcuts import render
import psutil
from rest_framework import response
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
@ -7,6 +8,8 @@ from rest_framework.viewsets import ViewSet
from django.conf import settings
import os
from rest_framework import serializers, status
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
# Create your views here.
class ServerInfoView(APIView):
@ -29,17 +32,45 @@ class ServerInfoView(APIView):
ret['disk']['percent'] = disk.percent
return Response(ret)
def get_file_list(file_path):
dir_list = os.listdir(file_path)
if not dir_list:
return
else:
# 注意这里使用lambda表达式将文件按照最后修改时间顺序升序排列
# os.path.getmtime() 函数是获取文件最后修改时间
# os.path.getctime() 函数是获取文件最后创建时间
dir_list = sorted(dir_list,key=lambda x: os.path.getmtime(os.path.join(file_path, x)), reverse=True)
# print(dir_list)
return dir_list
class LogView(APIView):
@swagger_auto_schema(manual_parameters=[
openapi.Parameter('name', openapi.IN_QUERY, description='日志文件名', type=openapi.TYPE_STRING)
])
def get(self, request, *args, **kwargs):
"""
查看最近的日志列表
:query name
"""
logs =[]
for root, dirs, files in os.walk(settings.LOG_PATH):
for file in files:
name = request.GET.get('name', None)
# for root, dirs, files in os.walk(settings.LOG_PATH):
# files.reverse()
for file in get_file_list(settings.LOG_PATH):
if len(logs)>50:break
filepath = os.path.join(root, file)
filepath = os.path.join(settings.LOG_PATH, file)
if name:
if name in filepath:
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({
@ -49,6 +80,7 @@ class LogView(APIView):
})
return Response(logs)
class LogDetailView(APIView):
def get(self, request, name):

View File

@ -1,6 +1,6 @@
from django.apps import AppConfig
class SystemConfig(AppConfig):
class PumConfig(AppConfig):
name = 'apps.pum'
verbose_name = '采购管理'

View File

@ -3,11 +3,11 @@ from rest_framework.viewsets import ModelViewSet
from apps.pum.models import Vendor
from apps.pum.serializers import VendorSerializer
from apps.system.mixins import OptimizationMixin
from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin
# Create your views here.
class VendorViewSet(ModelViewSet):
class VendorViewSet(CreateUpdateModelAMixin, ModelViewSet):
"""
供应商-增删改查
"""

View File

@ -1,6 +1,6 @@
from django.apps import AppConfig
class SystemConfig(AppConfig):
class SamConfig(AppConfig):
name = 'apps.sam'
verbose_name = '销售管理'

View File

@ -120,6 +120,13 @@ class UserSimpleSerializer(serializers.ModelSerializer):
model = User
fields = ['id', 'username', 'name']
# class UserStandardSerializer(serializers.ModelSerializer):
# dept_name = serializers.StringRelatedField(source='dept')
# class Meta:
# model = User
# fields = ['id', 'username', 'name', 'is_active', 'dept_name', 'dept']
class UserListSerializer(serializers.ModelSerializer):
"""
用户列表序列化
@ -138,6 +145,11 @@ class UserListSerializer(serializers.ModelSerializer):
queryset = queryset.prefetch_related('roles',)
return queryset
def get_field_names(self, declared_fields, info):
if hasattr(self.Meta.model, 'employee_user'):
self.Meta.fields.append('employee_user')
return super().get_field_names(declared_fields, info)
class UserModifySerializer(serializers.ModelSerializer):
"""
用户编辑序列化

View File

View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@ -0,0 +1,7 @@
from django.apps import AppConfig
class WfConfig(AppConfig):
name = 'apps.wf'
verbose_name = '工作流管理'

View File

@ -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只保存文本内容')

View File

@ -0,0 +1,9 @@
from rest_framework.serializers import ModelSerializer
from .models import Workflow
class WorkflowSerializer(ModelSerializer):
class Meta:
model = Workflow
fields = '__all__'

View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

12
hb_server/apps/wf/urls.py Normal file
View File

@ -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)),
]

View File

@ -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']

View File

@ -7,5 +7,6 @@ django-simple-history==3.0.0
djangorestframework==3.12.4
djangorestframework-simplejwt==4.7.2
drf-yasg==1.20.0
psycopg2==2.9.1
psutil==5.8.0
pillow==8.3.1
opencv-python==4.5.3.56

View File

@ -49,6 +49,7 @@ INSTALLED_APPS = [
'apps.monitor',
'apps.pum',
'apps.em',
'apps.hrm'
]
MIDDLEWARE = [
@ -196,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/'
}
# 日志配置
# 创建日志的路径

View File

@ -25,6 +25,7 @@ from rest_framework.documentation import include_docs_urls
from rest_framework_simplejwt.views import (TokenObtainPairView,
TokenRefreshView)
from django.views.generic import TemplateView
from utils.view import GenSignature
router = routers.DefaultRouter()
router.register('', FileViewSet, basename="file")
@ -58,6 +59,10 @@ urlpatterns = [
path('api/monitor/', include('apps.monitor.urls')),
path('api/pum/', include('apps.pum.urls')),
path('api/em/', include('apps.em.urls')),
path('api/hrm/', include('apps.hrm.urls')),
# 工具
path('api/utils/signature/', GenSignature.as_view()),
# 前端页面入口
path('',TemplateView.as_view(template_name="index.html"))