代码重构

This commit is contained in:
caoqianming 2022-06-07 10:21:55 +08:00
parent c114f2ccf4
commit e096dd7ad3
72 changed files with 1268 additions and 502 deletions

View File

@ -1,64 +0,0 @@
# Generated by Django 3.2.12 on 2022-05-09 01:05
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 = [
('system', '0004_auto_20220421_1511'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Access',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, 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='删除标记')),
('type', models.PositiveSmallIntegerField(choices=[(10, '准入'), (20, '禁入')], verbose_name='准入类型')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Area',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, 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='删除标记')),
('name', models.CharField(max_length=20, verbose_name='名称')),
('level', models.PositiveSmallIntegerField(verbose_name='区域等级')),
('sort_str', models.CharField(default='1', max_length=12, verbose_name='排序字符')),
('visitor_no', models.BooleanField(default=True, verbose_name='不准许访客')),
('rparty_no', models.BooleanField(default=True, verbose_name='不准许相关方')),
('third_info', models.JSONField(blank=True, default=dict, verbose_name='三方信息')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='area_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('manager', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='区域负责人')),
('posts_access', models.ManyToManyField(through='am.Access', to='system.Post')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='area_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='access',
name='area',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='am.area', verbose_name='关联区域'),
),
migrations.AddField(
model_name='access',
name='post',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='system.post', verbose_name='关联岗位'),
),
]

View File

@ -1,28 +1,37 @@
from django.db import models from django.db import models
from apps.system.models import Post, User from apps.system.models import Post, User
from apps.utils.models import BaseModel, CommonAModel from apps.utils.models import CommonADModel, CommonBModel
# Create your models here. # Create your models here.
class Area(CommonAModel): class Area(CommonBModel):
""" """
地图区域 地图区域
""" """
AREA_LEVEL_1 = 10
AREA_LEVEL_2 = 20
AREA_LEVEL_3 = 30
AREA_LEVEL_4 = 40
AREA_LEVEL_HOICES = (
(AREA_LEVEL_1, '办公'),
(AREA_LEVEL_2, '生产一般'),
(AREA_LEVEL_3, '生产重点'),
(AREA_LEVEL_4, '四级')
)
name = models.CharField('名称', max_length=20) name = models.CharField('名称', max_length=20)
level = models.PositiveSmallIntegerField('区域等级') level = models.PositiveSmallIntegerField('区域等级')
sort_str = models.CharField('排序字符', max_length=12, default='1') sort_str = models.CharField('排序字符', max_length=12, default='1')
manager = models.ForeignKey(User, on_delete=models.CASCADE, visitor_yes = models.BooleanField('准许访客人员', default=False)
verbose_name='区域负责人') remployee_yes = models.BooleanField('准许相关方人员', default=False)
visitor_no = models.BooleanField('不准许访客', default=True) employee_yes = models.BooleanField('准许全部员工', default=True)
rparty_no = models.BooleanField('不准许相关方', default=True)
posts_access = models.ManyToManyField(Post, through='am.access') posts_access = models.ManyToManyField(Post, through='am.access')
third_info = models.JSONField('三方信息', default=dict, third_info = models.JSONField('三方信息', default=dict,
null=False, blank=True) null=False, blank=True)
class Access(BaseModel): class Access(CommonADModel):
""" """
准入权限 岗位准入权限
""" """
ACCESS_IN_YES = 10 ACCESS_IN_YES = 10
ACCESS_IN_NO = 20 ACCESS_IN_NO = 20

View File

@ -1,4 +1,4 @@
from apps.am.models import Area from apps.am.models import Access, Area
from apps.utils.serializers import CustomModelSerializer from apps.utils.serializers import CustomModelSerializer
@ -6,3 +6,15 @@ class AreaSimpleSerializer(CustomModelSerializer):
class Meta: class Meta:
model = Area model = Area
fields = ['id', 'name', 'level'] fields = ['id', 'name', 'level']
class AreaCreateUpdateSerializer(CustomModelSerializer):
class Meta:
model = Area
fields = ['name', 'level', 'sort_srt', 'visitor_yes', 'remployee_yes', 'employee_yes', 'belong_dept']
class AccessCreateSerializer(CustomModelSerializer):
class Meta:
model = Access
fields = ['type', 'area', 'post']

13
apps/am/urls.py Normal file
View File

@ -0,0 +1,13 @@
from apps.am.views import AreaViewSet, AccessViewSet
from django.urls import path, include
from rest_framework.routers import DefaultRouter
API_BASE_URL = 'api/am/'
HTML_BASE_URL = 'am/'
router = DefaultRouter()
router.register('area', AreaViewSet, basename='area')
router.register('access', AccessViewSet, basename='access')
urlpatterns = [
path(API_BASE_URL, include(router.urls)),
]

View File

@ -1,3 +1,46 @@
from django.shortcuts import render from apps.am.models import Area
from apps.am.serializers import AccessCreateSerializer, AreaCreateUpdateSerializer
from apps.utils.viewsets import CustomModelViewSet, CustomGenericViewSet
from django.db import transaction
from rest_framework.decorators import action
from rest_framework import serializers
from apps.third.clients import xxClient
from apps.third.tapis import xxapis
from rest_framework.response import Response
from rest_framework.mixins import ListModelMixin, CreateModelMixin, DestroyModelMixin
# Create your views here. # Create your views here.
class AreaViewSet(CustomModelViewSet):
queryset = Area.objects.all()
create_serializer_class = AreaCreateUpdateSerializer
update_serializer_class = AreaCreateUpdateSerializer
@transaction.atomic
@action(methods=['post'], detail=True, perms_map={'post': 'area:bind_rail'},
serializer_class=serializers.Serializer)
def bind_rail(self, request, pk=None):
"""
绑定围栏
"""
data = request.data
obj = self.get_object()
third_info = obj.third_info
if 'xx_rail' in third_info:
data['uuid'] = third_info['xx_rail']['uuid']
_, res = xxClient.request(**xxapis['rail_update'], json=data)
third_info['xx_rail']['detail'] = data
obj.third_info = third_info
obj.save()
else:
_, res = xxClient.request(**xxapis['rail_create'], json=data)
rail_info = {'uuid': res, 'detail': data}
third_info['xx_rail'] = rail_info
obj.third_info = third_info
obj.save()
return Response()
class AccessViewSet(ListModelMixin, CreateModelMixin, DestroyModelMixin, CustomGenericViewSet):
perms_map = {'post': 'access:create', 'delete': 'access:delete'}
create_serializer_class = AccessCreateSerializer

View File

@ -7,7 +7,8 @@ from rest_framework.exceptions import APIException
class ReloadServerGit(APIView): class ReloadServerGit(APIView):
permission_classes = [IsAdminUser] authentication_classes = []
permission_classes = []
def post(self, request): def post(self, request):
""" """

View File

3
apps/ecm/admin.py Normal file
View File

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

6
apps/ecm/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class EcmConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.ecm'

85
apps/ecm/models.py Normal file
View File

@ -0,0 +1,85 @@
from django.db import models
from apps.am.models import Area
from apps.hrm.models import Employee
from apps.utils.models import BaseModel, CommonAModel, CommonBModel
from apps.system.models import Dept, Post, User
from django.utils import timezone
# Create your models here.
class EventCate(CommonAModel):
"""
事件种类
"""
EVENT_TRIGGER_CHOICES = (
(10, '监控'),
(20, '定位')
)
code = models.CharField('标识', max_length=10, unique=True)
name = models.CharField('名称', max_length=20, unique=True)
trigger = models.PositiveSmallIntegerField('触发方式', default=10, choices=EVENT_TRIGGER_CHOICES)
speaker_on = models.BooleanField('开启音响报警', default=True)
speakers = models.ManyToManyField('third.tdevice', verbose_name='固定音响')
filter_area_level = models.PositiveSmallIntegerField('固定音响区域级别过滤', choices=Area.AREA_LEVEL_HOICES,
default=Area.AREA_LEVEL_1)
class PushSetting(CommonAModel):
"""
推送配置
"""
PUSH_FILTER1_CHOICES = (
(10, '当事人部门'),
(20, '当事人部门以上'),
(30, '属地部门'),
(40, '属地部门以上')
)
event_cate = models.ForeignKey(EventCate, verbose_name='关联事件种类',
to_field='code', on_delete=models.CASCADE)
post = models.ForeignKey(Post, verbose_name='推送岗位',
on_delete=models.CASCADE, null=True, blank=True)
filter_to_user = models.PositiveSmallIntegerField('推送人员过滤', null=True, blank=True)
filter_area_level = models.PositiveSmallIntegerField('区域级别过滤', null=True, blank=True)
sms_enable = models.BooleanField('短信通知', default=False)
can_handle = models.BooleanField('是否可处理', default=False)
sort = models.PositiveIntegerField('排序', default=1)
class Event(CommonBModel):
"""
事件
"""
cate = models.ForeignKey(EventCate, verbose_name='事件种类')
imgs = models.JSONField('事件图片', default=list, null=False, blank=True)
area = models.ForeignKey(Area, verbose_name='发生区域', on_delete=models.CASCADE)
location = models.JSONField('事件点位坐标', default=dict, null=False, blank=True)
peope_type = models.CharField('当事人员类型', choices=Employee.PEOPLE_TYPE_CHOICES,
max_length=20, null=True, blank=True)
people = models.ForeignKey(Employee, verbose_name='当事人',
on_delete=models.CASCADE, null=True, blank=True)
handle_time = models.DateTimeField('处理时间', null=True, blank=True)
handle_user = models.ForeignKey(User, verbose_name='处理人',
on_delete=models.CASCADE, null=True, blank=True)
handle_desc = models.TextField('处理描述', null=True, blank=True)
is_pushed = models.BooleanField('是否已推送', default=False)
class Push(BaseModel):
"""
推送情况
"""
event = models.ForeignKey(Event, verbose_name='关联事件',
on_delete=models.CASCADE)
pusher = models.ForeignKey(User, verbose_name='推送人',
on_delete=models.CASCADE)
push_setting = models.ForeignKey(PushSetting, verbose_name='通过哪个配置',
on_delete=models.CASCADE, null=True, blank=True)
post = models.ForeignKey(Post, verbose_name='岗位',
on_delete=models.CASCADE, null=True, blank=True)
dept = models.ForeignKey(Dept, verbose_name='部门',
on_delete=models.CASCADE, null=True, blank=True)
msg = models.TextField('推送文本')
is_read = models.BooleanField('站内信已读', default=False)
is_sms_send = models.BooleanField('短信已发送', default=False)
can_handle = models.BooleanField('是否可处理', default=False)
last_push_time = models.DateTimeField('最后推送时间', default=timezone.now)

54
apps/ecm/serializers.py Normal file
View File

@ -0,0 +1,54 @@
from apps.am.serializers import AreaSimpleSerializer
from apps.ecm.models import EventCate, Push, PushSetting, Event
from apps.utils.serializers import CustomModelSerializer
from rest_framework import serializers
from apps.system.serializers import UserSimpleSerializer
class EventCateSimpleSerializer(CustomModelSerializer):
class Meta:
model = Event
fields = ['id', 'name', 'code']
class EventCateListSerializer(CustomModelSerializer):
class Meta:
model = EventCate
fields = '__all__'
class EventCateUpdateSerializer(CustomModelSerializer):
class Meta:
model = EventCate
fields = ['speaker_on', 'speakers', 'filter_area_level']
class PushSettingsSerializer(CustomModelSerializer):
class Meta:
model = PushSetting
fields = '__all__'
class EventSerializer(CustomModelSerializer):
area_ = AreaSimpleSerializer(source='area', read_only=True)
cate_ = EventCateSimpleSerializer(source='cate', read_only=True)
people_name = serializers.CharField(source='people.name', read_only=True)
handle_user_name = serializers.CharField(source='handle_user.name', read_only=True)
class Meta:
model = Event
fields = '__all__'
class EventHandleSerializer(CustomModelSerializer):
class Meta:
model = Event
fields = ['handle_desc']
class PushSerializer(CustomModelSerializer):
pusher_ = UserSimpleSerializer(source='pusher', read_only=True)
class Meta:
model = Push
fields = '__all__'

3
apps/ecm/tests.py Normal file
View File

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

15
apps/ecm/urls.py Normal file
View File

@ -0,0 +1,15 @@
from apps.ecm.views import EventCateViewSet, PushSettingViewSet, EventViewSet, PushViewSet
from django.urls import path, include
from rest_framework.routers import DefaultRouter
API_BASE_URL = 'api/ecm/'
HTML_BASE_URL = 'ecm/'
router = DefaultRouter()
router.register('event_cate', EventCateViewSet, basename='event_cate')
router.register('event', EventViewSet, basename='event')
router.register('push_setting', PushSettingViewSet, basename='push_setting')
router.register('push', PushViewSet, basename='push')
urlpatterns = [
path(API_BASE_URL, include(router.urls)),
]

68
apps/ecm/views.py Normal file
View File

@ -0,0 +1,68 @@
from apps.ecm.models import Event, EventCate, Push, PushSetting
from apps.ecm.serializers import (EventCateListSerializer, EventCateUpdateSerializer, EventHandleSerializer,
EventSerializer, PushSerializer, PushSettingsSerializer)
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
from rest_framework.mixins import UpdateModelMixin, ListModelMixin, RetrieveModelMixin
from django.db import transaction
from rest_framework.decorators import action
from rest_framework import serializers
from django.utils import timezone
from rest_framework.response import Response
# Create your views here.
class EventCateViewSet(UpdateModelMixin, ListModelMixin, CustomGenericViewSet):
perms_map = {'put': 'eventcate:update'}
queryset = EventCate.objects.all()
list_serializer_class = EventCateListSerializer
update_serializer_class = EventCateUpdateSerializer
class PushSettingViewSet(CustomModelViewSet):
queryset = PushSetting.objects.all()
serializer_class = PushSettingsSerializer
class EventViewSet(ListModelMixin, RetrieveModelMixin, CustomGenericViewSet):
perms_map = {'get': 'event:view'}
queryset = Event.objects.all()
serializer_class = EventSerializer
@transaction.atomic
@action(methods=['post'], detail=True, perms_map={'post': 'event:handle'},
serializer_class=EventHandleSerializer)
def handle(self, request, pk=None):
"""
处理事件
"""
obj = self.get_object()
data = request.data
obj.handle_desc = data.get('handle_desc', '')
obj.handle_user = request.user
obj.handle_time = timezone.now()
obj.save()
return Response()
class PushViewSet(ListModelMixin, CustomGenericViewSet):
perms_map = {'get': 'push:view'}
queryset = Push.objects.all()
serializer_class = PushSerializer
@action(methods=['get'], detail=False, perms_map={'get': '*'})
def my(self, request, *args, **kwargs):
"""
推送给我的
"""
user = self.request.user
queryset = self.filter_queryset(self.get_queryset().filter(pusher=user))
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)

View File

@ -1,85 +0,0 @@
# Generated by Django 3.2.12 on 2022-04-13 06:34
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 = [
('system', '0003_remove_user_phone'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='NotWorkRemark',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, 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='删除标记')),
('not_work_date', models.DateField(verbose_name='未打卡日期')),
('remark', 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='notworkremark_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='notworkremark_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='用户')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Employee',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, 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='删除标记')),
('name', models.CharField(max_length=20, verbose_name='姓名')),
('phone', models.CharField(blank=True, max_length=11, null=True, verbose_name='手机号')),
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='邮箱号')),
('number', models.CharField(blank=True, max_length=50, null=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='签名图片')),
('birthday', models.DateField(blank=True, null=True, verbose_name='出生年月日')),
('qualification', models.CharField(blank=True, max_length=50, null=True, verbose_name='学历')),
('job_state', models.IntegerField(choices=[(10, '在职'), (20, '离职')], default=1, verbose_name='在职状态')),
('face_data', models.JSONField(blank=True, null=True, verbose_name='人脸识别数据')),
('is_atwork', models.BooleanField(default=False, verbose_name='当前在岗')),
('show_atwork', models.BooleanField(default=True, verbose_name='是否展示在岗状态')),
('last_check_time', models.DateTimeField(blank=True, null=True, verbose_name='打卡时间')),
('not_work_remark', models.CharField(blank=True, max_length=200, null=True, verbose_name='当前未打卡说明')),
('third_info', models.JSONField(blank=True, default=dict, null=True, verbose_name='三方信息')),
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='employee_belong_dept', to='system.dept', 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(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='employee_user', to=settings.AUTH_USER_MODEL, verbose_name='系统账号')),
],
options={
'verbose_name': '员工补充信息',
'verbose_name_plural': '员工补充信息',
},
),
migrations.CreateModel(
name='ClockRecord',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, 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='删除标记')),
('type', models.PositiveSmallIntegerField(choices=[(10, '上班打卡')], default=10, verbose_name='打卡类型')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='clockrecord_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='clockrecord_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.12 on 2022-04-21 07:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hrm', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='employee',
name='third_info',
field=models.JSONField(blank=True, default=dict, verbose_name='三方信息'),
),
]

View File

@ -1,7 +1,7 @@
from django.db import models from django.db import models
from apps.system.models import User from apps.system.models import User
from apps.utils.models import CommonADModel, CommonAModel, CommonBModel from apps.utils.models import BaseModel, CommonADModel, CommonAModel, CommonBModel
class Employee(CommonBModel): class Employee(CommonBModel):
@ -14,9 +14,15 @@ class Employee(CommonBModel):
(JOB_ON, '在职'), (JOB_ON, '在职'),
(JOB_OFF, '离职'), (JOB_OFF, '离职'),
) )
PEOPLE_TYPE_CHOICES = (
('employee', '内部员工'),
('remployee', '相关方人员'),
('visitor', '访客')
)
type = models.CharField('人员类型', default='employee', max_length=10, choices=PEOPLE_TYPE_CHOICES)
user = models.OneToOneField(User, user = models.OneToOneField(User,
verbose_name='系统账号', verbose_name='系统账号',
on_delete=models.PROTECT, related_name='employee_user', null=True, blank=True) on_delete=models.PROTECT, null=True, blank=True)
name = models.CharField('姓名', max_length=20) name = models.CharField('姓名', max_length=20)
phone = models.CharField('手机号', max_length=11, null=True, blank=True) phone = models.CharField('手机号', max_length=11, null=True, blank=True)
email = models.EmailField('邮箱号', null=True, blank=True) email = models.EmailField('邮箱号', null=True, blank=True)
@ -33,7 +39,7 @@ class Employee(CommonBModel):
show_atwork = models.BooleanField('是否展示在岗状态', default=True) show_atwork = models.BooleanField('是否展示在岗状态', default=True)
last_check_time = models.DateTimeField('打卡时间', null=True, blank=True) last_check_time = models.DateTimeField('打卡时间', null=True, blank=True)
not_work_remark = models.CharField('当前未打卡说明', null=True, blank=True, max_length=200) not_work_remark = models.CharField('当前未打卡说明', null=True, blank=True, max_length=200)
third_info = models.JSONField('三方信息', default=dict, null=False, blank=True) third_info = models.JSONField('三方信息', default=dict, null=False, blank=True) # 主要是定位卡信息
class Meta: class Meta:
verbose_name = '员工补充信息' verbose_name = '员工补充信息'
@ -69,3 +75,22 @@ class ClockRecord(CommonADModel):
(ClOCK_WORK1, '上班打卡'), (ClOCK_WORK1, '上班打卡'),
) )
type = models.PositiveSmallIntegerField('打卡类型', choices=type_choice, default=ClOCK_WORK1) type = models.PositiveSmallIntegerField('打卡类型', choices=type_choice, default=ClOCK_WORK1)
class Certificate(CommonAModel):
"""
证书
"""
CERTIFICATE_TYPE_CHOICES = (
(10, '特种作业证书'),
(20, '特种设备操作证书'),
(30, '安全管理人员证书')
)
employee = models.ForeignKey(Employee, verbose_name='对应人员', on_delete=models.CASCADE)
name = models.CharField('证书名称', max_length=20)
number = models.CharField('证书编号', max_length=20)
type = models.PositiveSmallIntegerField('证书类型', default=10)
issue_date = models.DateField('发证日期')
expiration_date = models.DateField('有效期')
review_date = models.DateField('下一次复审日期')
file = models.CharField('文件地址', max_length=1000, null=True, blank=True)

View File

@ -3,9 +3,9 @@ from rest_framework.serializers import ModelSerializer
from rest_framework import serializers from rest_framework import serializers
from apps.utils.serializers import CustomModelSerializer from apps.utils.serializers import CustomModelSerializer
from apps.utils.constants import EXCLUDE_FIELDS from apps.utils.constants import EXCLUDE_FIELDS, EXCLUDE_FIELDS_BASE
from apps.utils.tools import rannum from apps.utils.tools import rannum
from apps.hrm.models import ClockRecord, Employee, NotWorkRemark from apps.hrm.models import Certificate, ClockRecord, Employee, NotWorkRemark
from apps.system.serializers import DeptSimpleSerializer, UserSimpleSerializer from apps.system.serializers import DeptSimpleSerializer, UserSimpleSerializer
from django.db import transaction from django.db import transaction
from apps.third.clients import dhClient from apps.third.clients import dhClient
@ -37,7 +37,7 @@ class EmployeeCreateUpdateSerializer(EmployeeBaseSerializer):
model = Employee model = Employee
exclude = EXCLUDE_FIELDS + ['face_data', exclude = EXCLUDE_FIELDS + ['face_data',
'is_atwork', 'last_check_time', 'is_atwork', 'last_check_time',
'not_work_remark', 'third_info'] 'not_work_remark', 'third_info', 'type']
extra_kwargs = { extra_kwargs = {
'phone': {'required': True}, 'phone': {'required': True},
'number': {'required': True}, 'number': {'required': True},
@ -214,3 +214,15 @@ class NotWorkRemarkListSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = NotWorkRemark model = NotWorkRemark
fields = '__all__' fields = '__all__'
class CertificateCreateUpdateSerializer(CustomModelSerializer):
class Meta:
model = Certificate
exclude = EXCLUDE_FIELDS
class CertificateSerializer(CustomModelSerializer):
class Meta:
model = Certificate
fields = '__all__'

View File

@ -7,9 +7,10 @@ from apps.hrm.models import Employee
@receiver(post_save, sender=User) @receiver(post_save, sender=User)
def updateEmployee(sender, instance, created, **kwargs): def updateEmployee(sender, instance, created, **kwargs):
# if created: # if created:
# 如果账号所属部门有变动, 更新关联人员的所属部门 # 如果账号所属部门有变动, 更新关联人员的所属部门, 只限内部人员
ep = Employee.objects.filter(user=instance).first() if not instance.is_superuser:
if ep: ep = Employee.objects.filter(user=instance).first()
if ep.belong_dept and ep.belong_dept != instance.belong_dept: if ep and ep.type == 'employee':
ep.belong_dept = instance.belong_dept if ep.belong_dept and ep.belong_dept != instance.belong_dept:
ep.save() ep.belong_dept = instance.belong_dept
ep.save()

View File

@ -12,8 +12,8 @@ from rest_framework.response import Response
from apps.hrm.errors import NO_NEED_LEVEL_REMARK from apps.hrm.errors import NO_NEED_LEVEL_REMARK
from apps.hrm.filters import (ClockRecordFilterSet, EmployeeFilterSet, from apps.hrm.filters import (ClockRecordFilterSet, EmployeeFilterSet,
NotWorkRemarkFilterSet) NotWorkRemarkFilterSet)
from apps.hrm.models import ClockRecord, Employee, NotWorkRemark from apps.hrm.models import Certificate, ClockRecord, Employee, NotWorkRemark
from apps.hrm.serializers import (ChannelAuthoritySerializer, from apps.hrm.serializers import (CertificateCreateUpdateSerializer, CertificateSerializer, ChannelAuthoritySerializer,
ClockRecordListSerializer, ClockRecordListSerializer,
EmployeeCreateUpdateSerializer, EmployeeCreateUpdateSerializer,
EmployeeNotWorkRemarkSerializer, EmployeeNotWorkRemarkSerializer,
@ -182,3 +182,10 @@ class NotWorkRemarkViewSet(ListModelMixin, CustomGenericViewSet):
serializer_class = NotWorkRemarkListSerializer serializer_class = NotWorkRemarkListSerializer
filterset_class = NotWorkRemarkFilterSet filterset_class = NotWorkRemarkFilterSet
ordering = ['-pk'] ordering = ['-pk']
class CertificateViewSet(CustomModelViewSet):
queryset = Certificate.objects.filter(employee__type='employee')
create_serializer_class = CertificateCreateUpdateSerializer
update_serializer_class = CertificateCreateUpdateSerializer
serializer_class = CertificateSerializer

7
apps/monitor/migrations/0001_initial.py Executable file → Normal file
View File

@ -1,9 +1,10 @@
# Generated by Django 3.2.12 on 2022-04-11 03:20 # Generated by Django 3.2.12 on 2022-06-06 07:59
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import django.utils.timezone import django.utils.timezone
import uuid
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -21,12 +22,12 @@ class Migration(migrations.Migration):
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, 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='删除标记')), ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('id', models.UUIDField(primary_key=True, serialize=False)), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('requested_at', models.DateTimeField(db_index=True)), ('requested_at', models.DateTimeField(db_index=True)),
('response_ms', models.PositiveIntegerField(default=0)), ('response_ms', models.PositiveIntegerField(default=0)),
('path', models.CharField(db_index=True, help_text='请求地址', max_length=400)), ('path', models.CharField(db_index=True, help_text='请求地址', max_length=400)),
('view', models.CharField(blank=True, db_index=True, help_text='执行视图', max_length=400, null=True)), ('view', models.CharField(blank=True, db_index=True, help_text='执行视图', max_length=400, null=True)),
('view_method', models.CharField(blank=True, db_index=True, max_length=6, null=True)), ('view_method', models.CharField(blank=True, db_index=True, max_length=20, null=True)),
('remote_addr', models.GenericIPAddressField()), ('remote_addr', models.GenericIPAddressField()),
('host', models.URLField()), ('host', models.URLField()),
('method', models.CharField(max_length=10)), ('method', models.CharField(max_length=10)),

View File

@ -1,19 +0,0 @@
# Generated by Django 3.2.12 on 2022-04-11 03:27
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('monitor', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='drfrequestlog',
name='id',
field=models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.12 on 2022-04-18 02:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('monitor', '0002_alter_drfrequestlog_id'),
]
operations = [
migrations.AlterField(
model_name='drfrequestlog',
name='view_method',
field=models.CharField(blank=True, db_index=True, max_length=20, null=True),
),
]

0
apps/monitor/migrations/__init__.py Executable file → Normal file
View File

View File

3
apps/opm/admin.py Normal file
View File

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

6
apps/opm/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class OpmConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.opm'

69
apps/opm/models.py Normal file
View File

@ -0,0 +1,69 @@
from django.db import models
from apps.system.models import Dept, Dictionary, User
from apps.utils.models import BaseModel, CommonAModel, CommonBModel
from apps.am.models import Area
from apps.wf.models import Ticket, Workflow
# Create your models here.
class OptCate(CommonAModel):
"""
作业票种类
"""
code = models.CharField('标识', max_length=10, unique=True)
name = models.CharField('名称', max_length=20, unique=True)
template_export = models.TextField('导出word模板', null=True, blank=True)
workflow = models.ForeignKey(Workflow, verbose_name='所用工作流',
on_delete=models.CASCADE, null=True, blank=True)
class Operation(CommonBModel):
OP_STATE_CHOICES = (
(10, '创建中'),
(20, '审批中'),
(30, '待作业'),
(40, '待关闭'),
(50, '已关闭')
)
OP_WORK_CHOICES = (
('work', '运行'),
('stop', '停机'),
('repair', '检修')
)
number = models.CharField('作业编号', max_length=20, null=True, blank=True)
name = models.CharField('作业简述', max_length=200)
area = models.ForeignKey(Area, verbose_name='作业区域',
on_delete=models.CASCADE)
place = models.TextField('具体地点', null=True, blank=True)
start_time = models.DateTimeField('作业开始时间')
end_time = models.DateTimeField('作业结束时间')
dept_sd = models.ForeignKey(Dept, verbose_name='属地部门',
on_delete=models.CASCADE)
dept_yw = models.ForeignKey(Dept, verbose_name='业务部门',
on_delete=models.CASCADE)
user_xt = models.ForeignKey(User, verbose_name='业务部门协调员',
on_delete=models.CASCADE)
user_fz = models.ForeignKey(User, verbose_name='作业负责人',
on_delete=models.CASCADE)
user_jh = models.ForeignKey(User, verbose_name='作业监护人',
)
state_work = models.CharField('生产状态', choices=OP_WORK_CHOICES)
class Opt(CommonBModel):
"""
作业票
"""
operation = models.ForeignKey(Operation, verbose_name='关联作业',
on_delete=models.CASCADE)
number = models.CharField('作业票编号', max_length=20, null=True, blank=True)
cate = models.ForeignKey(OptCate, verbose_name='作业票种类',
on_delete=models.CASCADE)
dept_zy = models.ForeignKey(Dept, verbose_name='作业部门',
on_delete=models.CASCADE)
user_fz = models.ForeignKey(User, verbose_name='作业负责人',
on_delete=models.CASCADE)
user_jh = models.ForeignKey(User, verbose_name='作业监护人',
)
ticket = models.ForeignKey(Ticket, verbose_name='关联工单',
on_delete=models.CASCADE, null=True, blank=True)

30
apps/opm/serializers.py Normal file
View File

@ -0,0 +1,30 @@
from apps.opm.models import Operation, OptCate
from apps.utils.serializers import CustomModelSerializer
from apps.wf.serializers import WorkflowSimpleSerializer
from apps.utils.constants import EXCLUDE_FIELDS
class OptCateCreateUpdateSerializer(CustomModelSerializer):
class Meta:
model = OptCate
fields = ['code', 'name', 'template_export', 'workflow']
class OptCateSerializer(CustomModelSerializer):
workflow_ = WorkflowSimpleSerializer(source='workflow', read_only=True)
class Meta:
model = OptCate
fields = '__all__'
class OperationCreateUpdateSerializer(CustomModelSerializer):
class Meta:
model = Operation
fields = EXCLUDE_FIELDS + ['number']
class OperationSerializer(CustomModelSerializer):
class Meta:
model = Operation
fields = "__all__"

5
apps/opm/services.py Normal file
View File

@ -0,0 +1,5 @@
from apps.wf.models import Ticket
def create_opt(ticket: Ticket):
pass

3
apps/opm/tests.py Normal file
View File

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

19
apps/opm/views.py Normal file
View File

@ -0,0 +1,19 @@
from django.shortcuts import render
from apps.opm.models import Operation, OptCate
from apps.opm.serializers import OperationCreateUpdateSerializer, OptCateCreateUpdateSerializer, OptCateSerializer
from apps.utils.viewsets import CustomModelViewSet
# Create your views here.
class OptCateViewSet(CustomModelViewSet):
queryset = OptCate.objects.all()
create_serializer_class = OptCateCreateUpdateSerializer
update_serializer_class = OptCateCreateUpdateSerializer
serializer_class = OptCateSerializer
class OperationViewSet(CustomModelViewSet):
queryset = Operation.objects.all()
create_serializer_class = OperationCreateUpdateSerializer
update_serializer_class = OperationCreateUpdateSerializer

View File

3
apps/rpm/admin.py Normal file
View File

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

6
apps/rpm/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class RpmConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.rpm'

111
apps/rpm/models.py Normal file
View File

@ -0,0 +1,111 @@
from django.db import models
from apps.hrm.models import Certificate, Employee
from apps.system.models import Dept, Dictionary, File, User
from apps.utils.models import CommonBModel, BaseModel
# Create your models here.
class Rparty(CommonBModel):
"""
相关方
"""
dept = models.OneToOneField(Dept, verbose_name='关联部门',
on_delete=models.CASCADE,
null=True, blank=True)
name = models.CharField('名称', max_length=20)
number = models.CharField('信用代码', max_length=50, null=True, blank=True)
lawer = models.CharField('法人', max_length=20, null=True, blank=True)
contacter = models.CharField('联系人', max_length=20)
phone = models.CharField('联系电话', max_length=20)
email = models.EmailField('邮箱', null=True, blank=True)
addresss = models.CharField('企业地址', max_length=200, null=True, blank=True)
description = models.TextField('概述', null=True, blank=True)
# belong_dept是归属部门
class RpartyFile(BaseModel):
"""
相关方文件库
"""
file_cate = models.ForeignKey(Dictionary, verbose_name='文件种类',
on_delete=models.CASCADE, null=True, blank=True)
files = models.ManyToManyField(File, verbose_name='文件')
rparty = models.ForeignKey(Rparty, verbose_name='关联相关方', on_delete=models.CASCADE)
class Rproject(CommonBModel):
"""
相关方项目
"""
RP_START = 10
RP_APPROVAL = 20
RP_ENTER = 30
RP_WORKING = 40
RP_DONE = 50
RP_STATE_CHOICES = (
(10, '创建中'),
(20, '审批中'),
(30, '待入厂'),
(40, '进行中'),
(50, '已完成')
)
RP_TYPE_CHOICES = (
(10, '建筑施工'),
(20, '设备设施检维修'),
(30, '保安保洁服务'),
(40, '其他')
)
name = models.CharField('名称', max_length=20)
type = models.PositiveSmallIntegerField('项目类型', default=10)
contract_number = models.CharField('合同编号', max_length=20)
come_time = models.DateTimeField('进厂时间')
leave_time = models.DateTimeField('离厂时间')
state = models.PositiveSmallIntegerField('状态', default=10)
rparty = models.ForeignKey(Rparty, verbose_name='关联相关方', on_delete=models.CASCADE)
# belong_dept是业务部门可以带过来
class RprojectFile(BaseModel):
"""
相关方项目文件库
"""
file_cate = models.ForeignKey(Dictionary, verbose_name='文件种类',
on_delete=models.CASCADE, null=True, blank=True)
files = models.ManyToManyField(File, verbose_name='文件')
rproject = models.ForeignKey(Rproject, verbose_name='关联相关方项目', on_delete=models.CASCADE)
class Remployee(CommonBModel):
"""
相关方成员
"""
employee = models.OneToOneField(Employee, verbose_name='成员信息', on_delete=models.CASCADE)
rparty = models.ForeignKey(Rparty, verbose_name='所属相关方', on_delete=models.CASCADE)
rproject = models.ForeignKey(Rproject, verbose_name='当前所属相关方项目', on_delete=models.CASCADE,
null=True, blank=True)
class Rpeople(BaseModel):
"""
相关方项目人员
"""
rproject = models.ForeignKey(Rproject, verbose_name='关联项目', on_delete=models.CASCADE)
employee = models.ForeignKey(Employee, verbose_name='关联人员',
on_delete=models.CASCADE, null=True, blank=True)
is_manager = models.BooleanField('是否项目负责人', default=False)
class Rcertificate(BaseModel):
"""
相关方项目人员证书
"""
rproject = models.ForeignKey(Rproject, verbose_name='关联项目', on_delete=models.CASCADE)
employee = models.ForeignKey(Employee, verbose_name='关联人员',
on_delete=models.CASCADE, null=True, blank=True)
name = models.CharField('证书名称', max_length=20)
number = models.CharField('证书编号', max_length=20)
type = models.PositiveSmallIntegerField('证书类型', default=10, choices=Certificate.CERTIFICATE_TYPE_CHOICES)
issue_date = models.DateField('发证日期')
expiration_date = models.DateField('有效期')
review_date = models.DateField('下一次复审日期')
file = models.CharField('文件地址', max_length=1000, null=True, blank=True)

96
apps/rpm/serializers.py Normal file
View File

@ -0,0 +1,96 @@
from apps.hrm.models import Employee
from apps.hrm.serializers import phone_check
from apps.rpm.models import Rparty, Rpeople, Rproject
from apps.system.models import Dept
from apps.utils.constants import EXCLUDE_FIELDS
from apps.utils.serializers import CustomModelSerializer
from apps.system.serializers import DictSerializer, FileSerializer
from rest_framework import serializers
class RpartyCreateUpdateSerializer(CustomModelSerializer):
class Meta:
model = Rparty
fields = ['name', 'number', 'lawer', 'contacter', 'phone', 'email',
'description', 'belong_dept']
extra_kwargs = {
'belong_dept': {'required': True}
}
def create(self, validated_data):
instance = super().create(validated_data)
dept = Dept.objects.create(name=instance.name,
parent=instance.belong_dept)
instance.dept = dept
instance.save()
return instance
def update(self, instance, validated_data):
instance = super().update(instance, validated_data)
dept = instance.dept
dept.name = instance.name
dept.parent = instance.belong_dept
dept.save()
return instance
class RpartySerializer(CustomModelSerializer):
class Meta:
model = Rparty
fields = '__all__'
class RpartySimpleSerializer(CustomModelSerializer):
class Meta:
model = Rparty
fields = ['id', 'name']
class RpartyAssignSerializer(serializers.Serializer):
username = serializers.CharField('用户名')
class RpartyFileListSerializer(CustomModelSerializer):
rparty_ = RpartySimpleSerializer(source='rparty', read_only=True)
file_cate_ = DictSerializer(source='file_cate', read_only=True)
files_ = FileSerializer(source='files', many=True, read_only=True)
class RprojectCreateUpdateSerializer(CustomModelSerializer):
class Meta:
model = Rproject
fields = ['name', 'contract_number', 'type', 'come_time', 'leave_time', 'belong_dept']
class RprojectListSerializer(CustomModelSerializer):
class Meta:
model = Rproject
fields = '__all__'
class RemployeeCreateUpdateSerializer(CustomModelSerializer):
phone = serializers.CharField(label="手机号", validators=[phone_check])
rparty = serializers.PrimaryKeyRelatedField(queryset=Rparty.objects.all(), label='相关方ID', required=False)
class Meta:
model = Employee
exclude = EXCLUDE_FIELDS + ['face_data',
'is_atwork', 'last_check_time',
'not_work_remark', 'third_info', 'type']
extra_kwargs = {
'phone': {'required': True},
'number': {'required': True},
'photo': {'required': True},
'id_number': {'required': True},
}
class RpeopleCreatesSerializer(CustomModelSerializer):
employees = serializers.PrimaryKeyRelatedField(
queryset=Employee.objects.filter(type='remployee'),
many=True, label='员工ID列表'
)
class Meta:
model = Rpeople
fields = ['employees', 'rproject']

3
apps/rpm/tests.py Normal file
View File

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

0
apps/hrm/migrations/__init__.py → apps/rpm/urls.py Executable file → Normal file
View File

176
apps/rpm/views.py Normal file
View File

@ -0,0 +1,176 @@
from django.shortcuts import render
from apps.hrm.models import Certificate, Employee
from apps.hrm.serializers import CertificateCreateUpdateSerializer, CertificateSerializer
from apps.rpm.models import Remployee, Rparty, RpartyFile, Rpeople, Rproject
from apps.rpm.serializers import RemployeeCreateUpdateSerializer, RpartyAssignSerializer, RpartyCreateUpdateSerializer, RpartyFileListSerializer, RpartySerializer, RpeopleCreatesSerializer, RprojectCreateUpdateSerializer
from apps.system.models import Post, User, UserPost
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
from rest_framework.mixins import CreateModelMixin, ListModelMixin
from rest_framework.decorators import action
from rest_framework.response import Response
from django.contrib.auth.hashers import check_password, make_password
from django.db import transaction
from rest_framework.exceptions import ParseError
from rest_framework import serializers
# Create your views here.
class RpartyViewSet(CustomModelViewSet):
queryset = Rparty.objects.all()
create_serializer_class = RpartyCreateUpdateSerializer
update_serializer_class = RpartyCreateUpdateSerializer
serializer_class = RpartySerializer
@action(methods=['post'], detail=True, perms_map={'post': 'rparty:assgin'}, serializer_class=RpartyAssignSerializer)
@transaction.atomic
def assign(self, request, *args, **kwargs):
"""
分配账号
"""
obj = self.get_object()
username = request.data.get('username')
password = make_password('0000')
user = User.objects.create(username=username,
password=password,
type='remployee',
belong_dept=obj.dept)
obj.user = user
obj.save()
post, _ = Post.objects.get_or_create(code='rparty_admin',
defaults={
'name': '本相关方管理员',
'code': 'rparty_admin'
})
UserPost.objects.create(user=user, dept=obj.dept,
post=post)
return Response()
class RpartyFileViewSet(ListModelMixin, CustomGenericViewSet):
perms_map = {'get': 'rparty_file:view'}
queryset = RpartyFile.objects.all()
list_serializer_class = RpartyFileListSerializer
def get_queryset(self):
queryset = super().get_queryset()
# 防止越权,加入的逻辑,可以通过岗位控权实现
if self.request.user.type == 'remployee':
queryset = queryset.filter(rparty=self.request.user.belong_dept)
return queryset
class RemployeeViewSet(CustomModelViewSet):
queryset = Employee.objects.filter(type='remployee')
create_serializer_class = RemployeeCreateUpdateSerializer
update_serializer_class = RemployeeCreateUpdateSerializer
def get_queryset(self):
user = self.request.user
queryset = super().get_queryset()
if user.type == 'remployee':
queryset = queryset.filter(belong_dept=user.belong_dept)
return queryset
@transaction.atomic
def create(self, request, *args, **kwargs):
"""
添加人员
"""
user = self.request.user
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data
if user.type == 'remployee': # 如果是相关方账号
ep = serializer.save()
Remployee.objects.get_or_create(employee=ep, rparty=user.belong_dept.rparty,
defaults={
"employee": ep,
"rparty": user.belong_dept.rparty
})
else:
if 'rparty' not in vdata:
raise ParseError('未指定相关方')
serializer.save()
return Response(serializer.data, status=201)
class Rcertificate(CustomModelViewSet):
queryset = Certificate.objects.filter(employee__type='remployee')
create_serializer_class = CertificateCreateUpdateSerializer
update_serializer_class = CertificateCreateUpdateSerializer
serializer_class = CertificateSerializer
def get_queryset(self):
user = self.request.user
queryset = super().get_queryset()
if user.type == 'remployee':
queryset = queryset.filter(belong_dept=user.belong_dept)
return queryset
class RprojectViewSet(CustomModelViewSet):
queryset = Rproject.objects.all()
create_serializer_class = RprojectCreateUpdateSerializer
update_serializer_class = RprojectCreateUpdateSerializer
def get_queryset(self):
user = self.request.user
queryset = super().get_queryset()
if user.type == 'remployee':
queryset = queryset.filter(belong_dept=user.belong_dept)
return queryset
def create(self, request, *args, **kwargs):
user = self.request.user
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data
if user.type == 'remployee':
vdata['rparty'] = user.belong_dept.rparty
if not vdata.get('rparty', None):
raise ParseError('请指定相关方')
if not vdata.get('belong_dept', None):
vdata['belong_dept'] = vdata['rparty'].belong_dept
return super().create(request, *args, **kwargs)
def update(self, request, *args, **kwargs):
obj = self.get_object()
if obj.state == Rproject.RP_START:
return super().update(request, *args, **kwargs)
raise ParseError('项目非创建状态不可更改')
class RpeopleViewSet(CustomGenericViewSet):
perms_map = {'get': '*'}
queryset = Rpeople.objects.all()
@action(methods=['post'], detail=False,
perms_map={'post': 'rproject:update'}, serializer_class=RpeopleCreatesSerializer)
@transaction.atomic
def creates(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data
rp = vdata['rproject']
if rp.state != Rproject.RP_START:
raise ParseError('项目非创建状态不可更改')
for i in vdata['employees']:
Rpeople.objects.create(employee=i, rproject=rp)
# 有证书的添加人员证书
return Response()
@action(methods=['put'], detail=True,
perms_map={'put': 'rproject:update'}, serializer_class=serializers.Serializer)
@transaction.atomic
def make_manager(self, request):
"""
设为项目负责人
"""
obj = self.get_object()
if obj.rproject.state != Rproject.RP_START:
raise ParseError('项目非创建状态不可更改')
Rpeople.objects.filter(rproject=obj.rproject).update(is_manager=False)
obj.is_manager = True
obj.save()
return Response()

View File

@ -1,5 +1,4 @@
from django.contrib import admin from django.contrib import admin
from simple_history.admin import SimpleHistoryAdmin
from .models import User, Dept, Role, Permission, DictType, Dictionary, File from .models import User, Dept, Role, Permission, DictType, Dictionary, File
# Register your models here. # Register your models here.
admin.site.register(User) admin.site.register(User)
@ -7,5 +6,5 @@ admin.site.register(Dept)
admin.site.register(Role) admin.site.register(Role)
admin.site.register(Permission) admin.site.register(Permission)
admin.site.register(DictType) admin.site.register(DictType)
admin.site.register(Dictionary, SimpleHistoryAdmin) admin.site.register(Dictionary)
admin.site.register(File) admin.site.register(File)

58
apps/system/migrations/0001_initial.py Executable file → Normal file
View File

@ -1,4 +1,4 @@
# Generated by Django 3.2.12 on 2022-04-11 03:20 # Generated by Django 3.2.12 on 2022-06-06 07:58
from django.conf import settings from django.conf import settings
import django.contrib.auth.models import django.contrib.auth.models
@ -34,8 +34,9 @@ class Migration(migrations.Migration):
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, 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='删除标记')), ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('type', models.CharField(default='employee', max_length=10, verbose_name='账号类型')),
('name', models.CharField(blank=True, max_length=20, null=True, verbose_name='姓名')), ('name', models.CharField(blank=True, max_length=20, null=True, verbose_name='姓名')),
('phone', models.CharField(blank=True, max_length=11, null=True, unique=True, verbose_name='手机号')), ('phone', models.CharField(blank=True, max_length=11, null=True, verbose_name='手机号')),
('avatar', models.CharField(blank=True, default='/media/default/avatar.png', max_length=100, null=True, verbose_name='头像')), ('avatar', models.CharField(blank=True, default='/media/default/avatar.png', max_length=100, null=True, verbose_name='头像')),
], ],
options={ options={
@ -55,7 +56,7 @@ class Migration(migrations.Migration):
('update_time', models.DateTimeField(auto_now=True, 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='删除标记')), ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.CharField(max_length=60, verbose_name='名称')), ('name', models.CharField(max_length=60, verbose_name='名称')),
('type', models.PositiveSmallIntegerField(choices=[(10, '公司'), (20, '部门')], default=20, verbose_name='类型')), ('type', models.CharField(default='dept', max_length=20, verbose_name='类型')),
('sort', models.PositiveSmallIntegerField(default=1, verbose_name='排序标记')), ('sort', models.PositiveSmallIntegerField(default=1, verbose_name='排序标记')),
('third_info', models.JSONField(default=dict, verbose_name='三方系统信息')), ('third_info', models.JSONField(default=dict, verbose_name='三方系统信息')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='dept_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='dept_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
@ -94,11 +95,14 @@ class Migration(migrations.Migration):
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, 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='删除标记')), ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.CharField(max_length=32, unique=True, verbose_name='名称')), ('name', models.CharField(max_length=32, verbose_name='名称')),
('code', models.CharField(blank=True, max_length=32, null=True, unique=True, verbose_name='岗位标识')), ('code', models.CharField(blank=True, max_length=32, null=True, verbose_name='岗位标识')),
('description', models.CharField(blank=True, max_length=50, null=True, verbose_name='描述')), ('description', models.CharField(blank=True, max_length=50, null=True, verbose_name='描述')),
('data_range', models.PositiveSmallIntegerField(choices=[(10, '全部'), (30, '同级及以下'), (40, '本级及以下'), (50, '本级'), (60, '仅本人')], default=40, verbose_name='数据权限范围')), ('min_hour', models.PositiveSmallIntegerField(default=0, verbose_name='最小在岗时间')),
('max_hour', models.PositiveSmallIntegerField(default=12, verbose_name='最长在岗时间')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='post_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='post_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='system.post', verbose_name='')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='post_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
], ],
options={ options={
'verbose_name': '职位/岗位', 'verbose_name': '职位/岗位',
@ -132,8 +136,8 @@ class Migration(migrations.Migration):
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, 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='删除标记')), ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.CharField(max_length=32, unique=True, verbose_name='角色')), ('name', models.CharField(max_length=32, verbose_name='名称')),
('code', models.CharField(blank=True, max_length=32, null=True, unique=True, verbose_name='角色标识')), ('code', models.CharField(blank=True, max_length=32, null=True, verbose_name='角色标识')),
('description', models.CharField(blank=True, max_length=50, null=True, verbose_name='描述')), ('description', models.CharField(blank=True, max_length=50, null=True, verbose_name='描述')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='role_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='role_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('perms', models.ManyToManyField(blank=True, related_name='role_perms', to='system.Permission', verbose_name='功能权限')), ('perms', models.ManyToManyField(blank=True, related_name='role_perms', to='system.Permission', verbose_name='功能权限')),
@ -145,15 +149,20 @@ class Migration(migrations.Migration):
'ordering': ['code'], 'ordering': ['code'],
}, },
), ),
migrations.AddField( migrations.CreateModel(
model_name='post', name='PostRole',
name='roles', fields=[
field=models.ManyToManyField(related_name='post_roles', to='system.Role', verbose_name='关联角色'), ('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
), ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
migrations.AddField( ('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
model_name='post', ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
name='update_by', ('data_range', models.PositiveSmallIntegerField(choices=[(10, '全部'), (30, '同级及以下'), (40, '本级及以下'), (50, '本级'), (60, '仅本人')], default=40, verbose_name='数据权限范围')),
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='post_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'), ('post', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='system.post', verbose_name='关联岗位')),
('role', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='system.role', verbose_name='关联角色')),
],
options={
'abstract': False,
},
), ),
migrations.CreateModel( migrations.CreateModel(
name='File', name='File',
@ -184,7 +193,7 @@ class Migration(migrations.Migration):
('update_time', models.DateTimeField(auto_now=True, 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='删除标记')), ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.CharField(max_length=30, verbose_name='名称')), ('name', models.CharField(max_length=30, verbose_name='名称')),
('code', models.CharField(max_length=30, unique=True, verbose_name='标识')), ('code', models.CharField(max_length=30, verbose_name='标识')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='dicttype_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='dicttype_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='system.dicttype', verbose_name='')), ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='system.dicttype', verbose_name='')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='dicttype_update_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='dicttype_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
@ -220,6 +229,11 @@ class Migration(migrations.Migration):
name='posts', name='posts',
field=models.ManyToManyField(related_name='user_posts', through='system.UserPost', to='system.Post'), field=models.ManyToManyField(related_name='user_posts', through='system.UserPost', to='system.Post'),
), ),
migrations.AddField(
model_name='user',
name='roles',
field=models.ManyToManyField(to='system.Role', verbose_name='关联角色'),
),
migrations.AddField( migrations.AddField(
model_name='user', model_name='user',
name='superior', name='superior',
@ -236,7 +250,7 @@ class Migration(migrations.Migration):
field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'), field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'),
), ),
migrations.CreateModel( migrations.CreateModel(
name='Dict', name='Dictionary',
fields=[ fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')), ('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
@ -248,10 +262,10 @@ class Migration(migrations.Migration):
('description', models.TextField(blank=True, null=True, verbose_name='描述')), ('description', models.TextField(blank=True, null=True, verbose_name='描述')),
('sort', models.PositiveSmallIntegerField(default=1, verbose_name='排序')), ('sort', models.PositiveSmallIntegerField(default=1, verbose_name='排序')),
('is_used', models.BooleanField(default=True, verbose_name='是否有效')), ('is_used', models.BooleanField(default=True, verbose_name='是否有效')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='dict_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='dictionary_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='system.dict', verbose_name='')), ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='system.dictionary', verbose_name='')),
('type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='system.dicttype', verbose_name='类型')), ('type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='system.dicttype', verbose_name='类型')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='dict_update_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='dictionary_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
], ],
options={ options={
'verbose_name': '字典', 'verbose_name': '字典',

View File

@ -1,43 +0,0 @@
# Generated by Django 3.2.12 on 2022-04-11 13:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('system', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='dicttype',
name='code',
field=models.CharField(max_length=30, verbose_name='标识'),
),
migrations.AlterField(
model_name='post',
name='code',
field=models.CharField(blank=True, max_length=32, null=True, verbose_name='岗位标识'),
),
migrations.AlterField(
model_name='post',
name='name',
field=models.CharField(max_length=32, verbose_name='名称'),
),
migrations.AlterField(
model_name='role',
name='code',
field=models.CharField(blank=True, max_length=32, null=True, verbose_name='角色标识'),
),
migrations.AlterField(
model_name='role',
name='name',
field=models.CharField(max_length=32, verbose_name='名称'),
),
migrations.AlterField(
model_name='user',
name='phone',
field=models.CharField(blank=True, max_length=11, null=True, verbose_name='手机号'),
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 3.2.12 on 2022-04-12 05:13
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('system', '0002_auto_20220411_2148'),
]
operations = [
migrations.RemoveField(
model_name='user',
name='phone',
),
]

View File

@ -1,44 +0,0 @@
# Generated by Django 3.2.12 on 2022-04-21 07:11
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('system', '0003_remove_user_phone'),
]
operations = [
migrations.CreateModel(
name='Dictionary',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, 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='删除标记')),
('name', models.CharField(max_length=60, verbose_name='名称')),
('value', models.CharField(max_length=10, verbose_name='')),
('code', models.CharField(blank=True, max_length=30, null=True, verbose_name='标识')),
('description', models.TextField(blank=True, null=True, verbose_name='描述')),
('sort', models.PositiveSmallIntegerField(default=1, verbose_name='排序')),
('is_used', models.BooleanField(default=True, verbose_name='是否有效')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='dictionary_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='system.dictionary', verbose_name='')),
('type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='system.dicttype', verbose_name='类型')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='dictionary_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'verbose_name': '字典',
'verbose_name_plural': '字典',
'ordering': ['sort'],
'unique_together': {('name', 'is_used', 'type')},
},
),
migrations.DeleteModel(
name='Dict',
),
]

0
apps/system/migrations/__init__.py Executable file → Normal file
View File

View File

@ -1,8 +1,17 @@
import enum
from django.db import models from django.db import models
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from apps.utils.models import CommonAModel, CommonBModel, BaseModel from apps.utils.models import CommonAModel, CommonBModel, BaseModel
class DataFilter(models.IntegerChoices):
ALL = 10, '全部'
SAMELEVE_AND_BELOW = 30, '同级及以下'
THISLEVEL_AND_BELOW = 40, '本级及以下'
THISLEVEL = 50, '本级'
MYSELF = 60, '仅本人'
class Permission(BaseModel): class Permission(BaseModel):
""" """
功能权限:目录,菜单,按钮 功能权限:目录,菜单,按钮
@ -35,14 +44,8 @@ class Dept(CommonAModel):
""" """
部门 部门
""" """
DEPT_TYPE_COMPANY = 10
DEPT_TYPE_DEPT = 20
dept_type_choices = (
(DEPT_TYPE_COMPANY, '公司'),
(DEPT_TYPE_DEPT, '部门')
)
name = models.CharField('名称', max_length=60) name = models.CharField('名称', max_length=60)
type = models.PositiveSmallIntegerField('类型', choices=dept_type_choices, default=20) type = models.CharField('类型', max_length=20, default='dept')
parent = models.ForeignKey('self', null=True, blank=True, parent = models.ForeignKey('self', null=True, blank=True,
on_delete=models.SET_NULL, verbose_name='') on_delete=models.SET_NULL, verbose_name='')
sort = models.PositiveSmallIntegerField('排序标记', default=1) sort = models.PositiveSmallIntegerField('排序标记', default=1)
@ -82,22 +85,10 @@ class Post(CommonAModel):
name = models.CharField('名称', max_length=32) name = models.CharField('名称', max_length=32)
code = models.CharField('岗位标识', max_length=32, null=True, blank=True) code = models.CharField('岗位标识', max_length=32, null=True, blank=True)
description = models.CharField('描述', max_length=50, blank=True, null=True) description = models.CharField('描述', max_length=50, blank=True, null=True)
roles = models.ManyToManyField(Role, verbose_name='关联角色', related_name='post_roles') parent = models.ForeignKey('self', null=True, blank=True,
POST_DATA_ALL = 10 on_delete=models.SET_NULL, verbose_name='')
POST_DATA_CUSTOM = 20 min_hour = models.PositiveSmallIntegerField('最小在岗时间', default=0)
POST_DATA_SAMELEVE_AND_BELOW = 30 max_hour = models.PositiveSmallIntegerField('最长在岗时间', default=12)
POST_DATA_THISLEVEL_AND_BELOW = 40
POST_DATA_THISLEVEL = 50
POST_DATA_MYSELF = 60
data_type_choices = (
(POST_DATA_ALL, '全部'),
(POST_DATA_SAMELEVE_AND_BELOW, '同级及以下'),
(POST_DATA_THISLEVEL_AND_BELOW, '本级及以下'),
(POST_DATA_THISLEVEL, '本级'),
(POST_DATA_MYSELF, '仅本人')
)
data_range = models.PositiveSmallIntegerField('数据权限范围', choices=data_type_choices,
default=POST_DATA_THISLEVEL_AND_BELOW)
class Meta: class Meta:
verbose_name = '职位/岗位' verbose_name = '职位/岗位'
@ -108,17 +99,32 @@ class Post(CommonAModel):
return self.name return self.name
class PostRole(BaseModel):
"""
岗位角色关系
"""
data_range = models.PositiveSmallIntegerField('数据权限范围', choices=DataFilter.choices,
default=DataFilter.THISLEVEL_AND_BELOW)
post = models.ForeignKey(Post, verbose_name='关联岗位',
on_delete=models.CASCADE, null=True, blank=True)
role = models.ForeignKey(Role, verbose_name='关联角色',
on_delete=models.CASCADE, null=True, blank=True)
class User(AbstractUser, CommonBModel): class User(AbstractUser, CommonBModel):
""" """
用户 用户
""" """
type = models.CharField('账号类型', max_length=10, default='employee')
name = models.CharField('姓名', max_length=20, null=True, blank=True) name = models.CharField('姓名', max_length=20, null=True, blank=True)
phone = models.CharField('手机号', max_length=11, null=True, blank=True)
avatar = models.CharField( avatar = models.CharField(
'头像', default='/media/default/avatar.png', max_length=100, null=True, blank=True) '头像', default='/media/default/avatar.png', max_length=100, null=True, blank=True)
superior = models.ForeignKey( superior = models.ForeignKey(
'self', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='上级主管') 'self', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='上级主管')
posts = models.ManyToManyField(Post, through='system.userpost', related_name='user_posts') posts = models.ManyToManyField(Post, through='system.userpost', related_name='user_posts')
depts = models.ManyToManyField(Dept, through='system.userpost') depts = models.ManyToManyField(Dept, through='system.userpost')
roles = models.ManyToManyField(Role, verbose_name='关联角色')
class Meta: class Meta:
verbose_name = '用户信息' verbose_name = '用户信息'

View File

@ -5,7 +5,7 @@ from django_celery_results.models import TaskResult
from apps.system.errors import USERNAME_EXIST from apps.system.errors import USERNAME_EXIST
from apps.utils.serializers import CustomModelSerializer from apps.utils.serializers import CustomModelSerializer
from apps.utils.constants import EXCLUDE_FIELDS, EXCLUDE_FIELDS_BASE from apps.utils.constants import EXCLUDE_FIELDS, EXCLUDE_FIELDS_BASE
from .models import (Dictionary, DictType, File, Dept, Permission, Post, from .models import (Dictionary, DictType, File, Dept, Permission, Post, PostRole,
Role, User, UserPost) Role, User, UserPost)
from rest_framework.exceptions import ParseError from rest_framework.exceptions import ParseError
from django.db import transaction from django.db import transaction
@ -224,9 +224,6 @@ class DeptSerializer(CustomModelSerializer):
""" """
组织架构序列化 组织架构序列化
""" """
type = serializers.ChoiceField(
choices=Dept.dept_type_choices, default='部门')
class Meta: class Meta:
model = Dept model = Dept
fields = '__all__' fields = '__all__'
@ -367,6 +364,26 @@ class UserPostCreateSerializer(CustomModelSerializer):
exclude = EXCLUDE_FIELDS_BASE exclude = EXCLUDE_FIELDS_BASE
class PostRoleSerializer(CustomModelSerializer):
"""
岗位-角色序列化
"""
post_ = PostSimpleSerializer(source='post', read_only=True)
role_ = RoleSimpleSerializer(source='role', read_only=True)
class Meta:
model = PostRole
fields = '__all__'
class PostRoleCreateSerializer(CustomModelSerializer):
"""
岗位-角色创建序列化
"""
class Meta:
model = PostRole
exclude = EXCLUDE_FIELDS_BASE
class UserInfoSerializer(CustomModelSerializer): class UserInfoSerializer(CustomModelSerializer):
posts_ = UserPostSerializer(source='post', read_only=True) posts_ = UserPostSerializer(source='post', read_only=True)

View File

@ -1,5 +1,5 @@
from django.urls import path, include from django.urls import path, include
from .views import FileViewSet, PTaskViewSet, PTaskResultViewSet, TaskList, \ from .views import FileViewSet, PTaskViewSet, PTaskResultViewSet, PostRoleViewSet, TaskList, \
UserPostViewSet, UserViewSet, DeptViewSet, \ UserPostViewSet, UserViewSet, DeptViewSet, \
PermissionViewSet, RoleViewSet, PostViewSet, \ PermissionViewSet, RoleViewSet, PostViewSet, \
DictTypeViewSet, DictViewSet DictTypeViewSet, DictViewSet
@ -21,6 +21,7 @@ router.register('ptask_result', PTaskResultViewSet, basename="ptask_result")
# router.register('qschedule', QScheduleViewSet, basename="qschedule") # router.register('qschedule', QScheduleViewSet, basename="qschedule")
# router.register('qtask_result', QTaskResultViewSet, basename="qtask_result") # router.register('qtask_result', QTaskResultViewSet, basename="qtask_result")
router.register('user_post', UserPostViewSet, basename='user_post') router.register('user_post', UserPostViewSet, basename='user_post')
router.register('post_role', PostRoleViewSet, basename='post_role')
router2 = routers.DefaultRouter() router2 = routers.DefaultRouter()
router2.register('file', FileViewSet, basename='file') router2.register('file', FileViewSet, basename='file')

View File

@ -22,12 +22,12 @@ from apps.utils.permission import ALL_PERMS, get_user_perms_map
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
from server.celery import app as celery_app from server.celery import app as celery_app
from .filters import UserFilter from .filters import UserFilter
from .models import (Dept, Dictionary, DictType, File, Permission, Post, Role, User, from .models import (Dept, Dictionary, DictType, File, Permission, Post, PostRole, Role, User,
UserPost) UserPost)
from .serializers import (DeptCreateUpdateSerializer, DeptSerializer, DictCreateUpdateSerializer, from .serializers import (DeptCreateUpdateSerializer, DeptSerializer, DictCreateUpdateSerializer,
DictSerializer, DictTypeCreateUpdateSerializer, DictTypeSerializer, DictSerializer, DictTypeCreateUpdateSerializer, DictTypeSerializer,
FileSerializer, PasswordChangeSerializer, PermissionCreateUpdateSerializer, FileSerializer, PasswordChangeSerializer, PermissionCreateUpdateSerializer,
PermissionSerializer, PostCreateUpdateSerializer, PostSerializer, PermissionSerializer, PostCreateUpdateSerializer, PostRoleCreateSerializer, PostRoleSerializer, PostSerializer,
PTaskSerializer, PTaskCreateUpdateSerializer, PTaskResultSerializer, PTaskSerializer, PTaskCreateUpdateSerializer, PTaskResultSerializer,
RoleCreateUpdateSerializer, RoleSerializer, TaskRunSerializer, RoleCreateUpdateSerializer, RoleSerializer, TaskRunSerializer,
UserCreateSerializer, UserListSerializer, UserPostCreateSerializer, UserCreateSerializer, UserListSerializer, UserPostCreateSerializer,
@ -305,7 +305,7 @@ class DeptViewSet(CustomModelViewSet):
部门-增删改查 部门-增删改查
""" """
queryset = Dept.objects.all() queryset = Dept.objects.filter(type__in=['dept', 'company'])
serializer_class = DeptSerializer serializer_class = DeptSerializer
create_serializer_class = DeptCreateUpdateSerializer create_serializer_class = DeptCreateUpdateSerializer
update_serializer_class = DeptCreateUpdateSerializer update_serializer_class = DeptCreateUpdateSerializer
@ -327,6 +327,18 @@ class RoleViewSet(CustomModelViewSet):
search_fields = ['name', 'code'] search_fields = ['name', 'code']
class PostRoleViewSet(CreateModelMixin, DestroyModelMixin, ListModelMixin, CustomGenericViewSet):
"""岗位/角色关系
岗位/角色关系
"""
perms_map = {'get': '*', 'post': 'post:update', 'delete': 'post:update'}
queryset = PostRole.objects.select_related('post', 'role').all()
serializer_class = PostRoleSerializer
create_serializer_class = PostRoleCreateSerializer
filterset_fields = ['post', 'role']
class UserPostViewSet(CreateModelMixin, DestroyModelMixin, ListModelMixin, CustomGenericViewSet): class UserPostViewSet(CreateModelMixin, DestroyModelMixin, ListModelMixin, CustomGenericViewSet):
"""用户/岗位关系 """用户/岗位关系

View File

@ -1,35 +0,0 @@
# Generated by Django 3.2.12 on 2022-05-09 01:05
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
('am', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='TDevice',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, 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='删除标记')),
('type', models.PositiveSmallIntegerField(choices=[(10, '定位基站'), (20, '定位信标'), (30, '定位标签'), (40, 'aoa引擎'), (50, '音响'), (60, '视频通道'), (70, '闸机通道'), (80, '面板机')], verbose_name='设备类型')),
('code', models.CharField(max_length=20, verbose_name='设备唯一标识')),
('location', models.JSONField(blank=True, default=dict, verbose_name='位置信息')),
('third_info', models.JSONField(blank=True, default=dict, verbose_name='三方信息')),
('area', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='am.area', verbose_name='所在区')),
('areas', models.ManyToManyField(related_name='tareas', to='am.Area', verbose_name='覆盖区')),
],
options={
'abstract': False,
},
),
]

View File

@ -95,6 +95,14 @@ xxapis = {
"ibeacon_list": { "ibeacon_list": {
"url": "/api/devicesV3/ibeacons", "url": "/api/devicesV3/ibeacons",
"method": "post" "method": "post"
},
"rail_create": {
"url": '/api/railsV2/add',
"method": "post"
},
"rail_update": {
"url": '/api/railsV2/edit',
"method": "post"
} }
} }

View File

@ -7,6 +7,7 @@ from rest_framework.exceptions import APIException, ParseError
from apps.utils.errors import DH_REQUEST_ERROR from apps.utils.errors import DH_REQUEST_ERROR
from apps.utils.tools import print_roundtrip from apps.utils.tools import print_roundtrip
requests.packages.urllib3.disable_warnings() requests.packages.urllib3.disable_warnings()
@ -69,6 +70,8 @@ class DhClient:
def request(self, url: str, method: str, params=dict(), json=dict(), timeout=10, def request(self, url: str, method: str, params=dict(), json=dict(), timeout=10,
file_path_rela=None, raise_exception=True): file_path_rela=None, raise_exception=True):
if self is None:
raise ParseError('大华对接未启用')
if self.isGetingToken: if self.isGetingToken:
req_num = 0 req_num = 0
while True: while True:

View File

@ -1,7 +1,7 @@
from django.core.cache import cache from django.core.cache import cache
from rest_framework.permissions import BasePermission from rest_framework.permissions import BasePermission
from apps.utils.queryset import get_child_queryset2 from apps.utils.queryset import get_child_queryset2
from apps.system.models import Dept, Permission, Post, UserPost from apps.system.models import DataFilter, Dept, Permission, Post, PostRole, UserPost
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
ALL_PERMS = [ ALL_PERMS = [
@ -11,7 +11,7 @@ ALL_PERMS = [
def get_user_perms_map(user): def get_user_perms_map(user):
""" """
获取权限字典,可用redis存取 获取权限字典,可用redis存取(包括功能和数据权限)
""" """
user_perms_map = {} user_perms_map = {}
if user.is_superuser: if user.is_superuser:
@ -20,16 +20,19 @@ def get_user_perms_map(user):
objs = UserPost.objects.filter(user=user) objs = UserPost.objects.filter(user=user)
for i in objs: for i in objs:
dept_id = str(i.dept.id) dept_id = str(i.dept.id)
if i.post.roles: # 岗位下有角色 for pr in PostRole.objects.filter(post=i.post).exists():
for perm in Permission.objects.filter(role__perms__in=i.post.roles): """
岗位角色
"""
for perm in Permission.objects.filter(role__perms=pr.role):
if perm.codes: if perm.codes:
for code in perm.codes: for code in perm.codes:
if code in user_perms_map: if code in user_perms_map:
data_range = user_perms_map[code].get(dept_id, -1) data_range = user_perms_map[code].get(dept_id, -1)
if i.post.data_range < data_range: if pr.data_range < data_range:
user_perms_map[code][dept_id] = data_range user_perms_map[code][dept_id] = data_range
else: else:
user_perms_map[code] = {dept_id: i.post.data_range} user_perms_map[code] = {dept_id: pr.data_range}
cache.set('perms_' + user.id, user_perms_map, 60*60) cache.set('perms_' + user.id, user_perms_map, 60*60)
return user_perms_map return user_perms_map
@ -109,20 +112,20 @@ class RbacDataMixin:
new_queryset = queryset.none() new_queryset = queryset.none()
for dept_id, data_range in user_perms_map[action_str].items: for dept_id, data_range in user_perms_map[action_str].items:
dept = Dept.objects.get(id=dept_id) dept = Dept.objects.get(id=dept_id)
if data_range == Post.POST_DATA_ALL: if data_range == DataFilter.ALL:
return queryset return queryset
elif data_range == Post.POST_DATA_SAMELEVE_AND_BELOW: elif data_range == DataFilter.SAMELEVE_AND_BELOW:
if dept.parent: if dept.parent:
belong_depts = get_child_queryset2(dept.parent) belong_depts = get_child_queryset2(dept.parent)
else: else:
belong_depts = get_child_queryset2(dept) belong_depts = get_child_queryset2(dept)
queryset = queryset.filter(belong_dept__in=belong_depts) queryset = queryset.filter(belong_dept__in=belong_depts)
elif data_range == Post.POST_DATA_THISLEVEL_AND_BELOW: elif data_range == DataFilter.THISLEVEL_AND_BELOW:
belong_depts = get_child_queryset2(dept) belong_depts = get_child_queryset2(dept)
queryset = queryset.filter(belong_dept__in=belong_depts) queryset = queryset.filter(belong_dept__in=belong_depts)
elif data_range == Post.POST_DATA_THISLEVEL: elif data_range == DataFilter.THISLEVEL:
queryset = queryset.filter(belong_dept=dept) queryset = queryset.filter(belong_dept=dept)
elif data_range == Post.POST_DATA_THISLEVEL: elif data_range == DataFilter.MYSELF:
queryset = queryset.filter(create_by=user) queryset = queryset.filter(create_by=user)
new_queryset = new_queryset | queryset new_queryset = new_queryset | queryset
return new_queryset return new_queryset

View File

@ -2,12 +2,12 @@ import time
from threading import Thread from threading import Thread
import requests import requests
# from django.conf import settings
from server import settings
from rest_framework.exceptions import APIException, ParseError from rest_framework.exceptions import APIException, ParseError
from django.conf import settings
from apps.utils.errors import SP_REQUEST_ERROR from apps.utils.errors import SP_REQUEST_ERROR
from apps.utils.tools import print_roundtrip from apps.utils.tools import print_roundtrip
requests.packages.urllib3.disable_warnings() requests.packages.urllib3.disable_warnings()
@ -69,6 +69,8 @@ class SpClient:
def request(self, url: str, method: str, params=dict(), json=dict(), timeout=10, def request(self, url: str, method: str, params=dict(), json=dict(), timeout=10,
file_path_rela=None, raise_exception=True): file_path_rela=None, raise_exception=True):
if self is None:
raise ParseError('音响对接未启用')
if self.isGetingToken: if self.isGetingToken:
req_num = 0 req_num = 0
while True: while True:

View File

@ -8,7 +8,7 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet from rest_framework.viewsets import GenericViewSet
from apps.system.models import Dept, Post from apps.system.models import DataFilter, Dept
from apps.utils.errors import PKS_ERROR from apps.utils.errors import PKS_ERROR
from apps.utils.mixins import CustomDestoryModelMixin, MyLoggingMixin from apps.utils.mixins import CustomDestoryModelMixin, MyLoggingMixin
from apps.utils.permission import ALL_PERMS, RbacPermission, get_user_perms_map from apps.utils.permission import ALL_PERMS, RbacPermission, get_user_perms_map
@ -78,20 +78,20 @@ class CustomGenericViewSet(MyLoggingMixin, GenericViewSet):
new_queryset = queryset.none() new_queryset = queryset.none()
for dept_id, data_range in user_perms_map[action_str].items: for dept_id, data_range in user_perms_map[action_str].items:
dept = Dept.objects.get(id=dept_id) dept = Dept.objects.get(id=dept_id)
if data_range == Post.POST_DATA_ALL: if data_range == DataFilter.ALL:
return queryset return queryset
elif data_range == Post.POST_DATA_SAMELEVE_AND_BELOW: elif data_range == DataFilter.SAMELEVE_AND_BELOW:
if dept.parent: if dept.parent:
belong_depts = get_child_queryset2(dept.parent) belong_depts = get_child_queryset2(dept.parent)
else: else:
belong_depts = get_child_queryset2(dept) belong_depts = get_child_queryset2(dept)
queryset = queryset.filter(belong_dept__in=belong_depts) queryset = queryset.filter(belong_dept__in=belong_depts)
elif data_range == Post.POST_DATA_THISLEVEL_AND_BELOW: elif data_range == DataFilter.THISLEVEL_AND_BELOW:
belong_depts = get_child_queryset2(dept) belong_depts = get_child_queryset2(dept)
queryset = queryset.filter(belong_dept__in=belong_depts) queryset = queryset.filter(belong_dept__in=belong_depts)
elif data_range == Post.POST_DATA_THISLEVEL: elif data_range == DataFilter.THISLEVEL:
queryset = queryset.filter(belong_dept=dept) queryset = queryset.filter(belong_dept=dept)
elif data_range == Post.POST_DATA_THISLEVEL: elif data_range == DataFilter.MYSELF:
queryset = queryset.filter(create_by=user) queryset = queryset.filter(create_by=user)
new_queryset = new_queryset | queryset new_queryset = new_queryset | queryset
return new_queryset return new_queryset

View File

@ -1,10 +1,13 @@
import time
from threading import Thread from threading import Thread
import requests import requests
from django.conf import settings
from rest_framework.exceptions import APIException, ParseError
from apps.utils.errors import XX_REQUEST_ERROR from apps.utils.errors import XX_REQUEST_ERROR
from apps.utils.tools import print_roundtrip from apps.utils.tools import print_roundtrip
from django.conf import settings
import time
from rest_framework.exceptions import APIException, ParseError
requests.packages.urllib3.disable_warnings() requests.packages.urllib3.disable_warnings()
@ -58,6 +61,8 @@ class XxClient:
self.t.join() self.t.join()
def request(self, url: str, method: str = 'post', params=dict(), json=dict(), timeout=4, raise_exception=True): def request(self, url: str, method: str = 'post', params=dict(), json=dict(), timeout=4, raise_exception=True):
if self is None:
raise ParseError('寻息对接未启用')
params['accessToken'] = self.token params['accessToken'] = self.token
json['username'] = self.username json['username'] = self.username
json['buildId'] = settings.XX_BUILDID json['buildId'] = settings.XX_BUILDID

View File

3
apps/vm/admin.py Normal file
View File

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

7
apps/vm/apps.py Normal file
View File

@ -0,0 +1,7 @@
from django.apps import AppConfig
class VmConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.vm'
verbose_name = "访客管理"

31
apps/vm/models.py Normal file
View File

@ -0,0 +1,31 @@
from django.db import models
from apps.hrm.models import Employee
from apps.utils.models import CommonAModel, CommonBModel, BaseModel
from apps.system.models import User
# Create your models here.
class Visit(CommonBModel):
"""
访问项目
"""
VISIT_PURPOSE_CHOICES = (
(10, '参观'),
(20, '拜访'),
(30, '面试'),
(40, '开会')
)
purpose = models.PositiveSmallIntegerField('来访事由')
description = models.CharField('来访详述', max_length=200)
visit_time = models.DateTimeField('来访时间')
leave_time = models.DateTimeField('离开时间')
receptionist = models.ForeignKey(User, verbose_name='接待人', on_delete=models.CASCADE)
class VisitPeople(BaseModel):
"""
访客项目人员
"""
visit = models.ForeignKey(Visit, verbose_name='关联访问项目', on_delete=models.CASCADE)
visitor = models.ForeignKey(Employee, verbose_name='访客', on_delete=models.CASCADE)
is_manager = models.BooleanField('是否主访人', default=False)

3
apps/vm/tests.py Normal file
View File

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

3
apps/vm/views.py Normal file
View File

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View File

@ -1,4 +1,4 @@
# Generated by Django 3.2.12 on 2022-05-10 02:06 # Generated by Django 3.2.12 on 2022-06-06 07:58
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
@ -27,12 +27,14 @@ class Migration(migrations.Migration):
('sort', models.IntegerField(default=0, help_text='用于工单步骤接口时step上状态的顺序(因为存在网状情况,所以需要人为设定顺序),值越小越靠前', verbose_name='状态顺序')), ('sort', models.IntegerField(default=0, help_text='用于工单步骤接口时step上状态的顺序(因为存在网状情况,所以需要人为设定顺序),值越小越靠前', verbose_name='状态顺序')),
('type', models.IntegerField(choices=[(0, '普通'), (1, '开始'), (2, '结束')], default=0, help_text='0.普通类型 1.初始状态(用于新建工单时,获取对应的字段必填及transition信息) 2.结束状态(此状态下的工单不得再处理即没有对应的transition)', verbose_name='状态类型')), ('type', models.IntegerField(choices=[(0, '普通'), (1, '开始'), (2, '结束')], default=0, help_text='0.普通类型 1.初始状态(用于新建工单时,获取对应的字段必填及transition信息) 2.结束状态(此状态下的工单不得再处理即没有对应的transition)', verbose_name='状态类型')),
('enable_retreat', models.BooleanField(default=False, help_text='开启后允许工单创建人在此状态直接撤回工单到初始状态', verbose_name='允许撤回')), ('enable_retreat', models.BooleanField(default=False, help_text='开启后允许工单创建人在此状态直接撤回工单到初始状态', verbose_name='允许撤回')),
('participant_type', 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='参与者类型')), ('enable_deliver', models.BooleanField(default=False, verbose_name='允许转交')),
('participant_type', models.IntegerField(blank=True, choices=[(0, '无处理人'), (1, '个人'), (2, '多人'), (3, '部门'), (4, '角色'), (10, '岗位'), (6, '脚本'), (7, '工单的字段'), (9, '代码获取')], default=1, help_text='0.无处理人,1.个人,2.多人,3.部门,4.角色,5.变量(支持工单创建人,创建人的leader),6.脚本,7.工单的字段内容(如表单中的"测试负责人",需要为用户名或者逗号隔开的多个用户名),8.父工单的字段内容。 初始状态请选择类型5参与人填create_by', verbose_name='参与者类型')),
('participant', models.JSONField(blank=True, default=list, help_text='可以为空(无处理人的情况,如结束状态)、userid、userid列表\\部门id\\角色id\\变量(create_by,create_by_tl)\\脚本记录的id等包含子工作流的需要设置处理人为loonrobot', verbose_name='参与者')), ('participant', models.JSONField(blank=True, default=list, help_text='可以为空(无处理人的情况,如结束状态)、userid、userid列表\\部门id\\角色id\\变量(create_by,create_by_tl)\\脚本记录的id等包含子工作流的需要设置处理人为loonrobot', verbose_name='参与者')),
('state_fields', models.JSONField(default=dict, help_text='json格式字典存储,包括读写属性1只读2必填3可选, 4:隐藏 示例:{"create_time":1,"title":2, "sn":1}, 内置特殊字段participant_info.participant_name:当前处理人信息(部门名称、角色名称)state.state_name:当前状态的状态名,workflow.workflow_name:工作流名称', verbose_name='表单字段')), ('state_fields', models.JSONField(default=dict, help_text='json格式字典存储,包括读写属性1只读2必填3可选, 4:隐藏 示例:{"create_time":1,"title":2, "sn":1}, 内置特殊字段participant_info.participant_name:当前处理人信息(部门名称、角色名称)state.state_name:当前状态的状态名,workflow.workflow_name:工作流名称', verbose_name='表单字段')),
('distribute_type', models.IntegerField(choices=[(1, '主动接单'), (2, '直接处理'), (3, '随机分配'), (4, '全部处理')], default=1, help_text='1.主动接单(如果当前处理人实际为多人的时候,需要先接单才能处理) 2.直接处理(即使当前处理人实际为多人,也可以直接处理) 3.随机分配(如果实际为多人,则系统会随机分配给其中一个人) 4.全部处理(要求所有参与人都要处理一遍,才能进入下一步)', verbose_name='分配方式')), ('distribute_type', models.IntegerField(choices=[(1, '主动接单'), (2, '直接处理'), (3, '随机分配'), (4, '全部处理')], default=1, help_text='1.主动接单(如果当前处理人实际为多人的时候,需要先接单才能处理) 2.直接处理(即使当前处理人实际为多人,也可以直接处理) 3.随机分配(如果实际为多人,则系统会随机分配给其中一个人) 4.全部处理(要求所有参与人都要处理一遍,才能进入下一步)', verbose_name='分配方式')),
('filter_policy', models.IntegerField(choices=[(0, ''), (1, '和工单同属一及上级部门'), (2, '和创建人同属一及上级部门'), (3, '和上步处理人同属一及上级部门')], default=0, verbose_name='参与人过滤策略')), ('filter_policy', models.IntegerField(choices=[(0, ''), (1, '和工单同属一及上级部门'), (2, '和创建人同属一及上级部门'), (3, '和上步处理人同属一及上级部门')], default=0, verbose_name='参与人过滤策略')),
('participant_cc', models.JSONField(blank=True, default=list, help_text='抄送给(userid列表)', verbose_name='抄送给')), ('participant_cc', models.JSONField(blank=True, default=list, help_text='抄送给(userid列表)', verbose_name='抄送给')),
('on_reach_func', models.CharField(blank=True, max_length=100, null=True, verbose_name='到达时调用方法')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='state_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='state_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='state_update_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='state_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
], ],
@ -52,7 +54,7 @@ class Migration(migrations.Migration):
('ticket_data', models.JSONField(default=dict, help_text='工单自定义字段内容', verbose_name='工单数据')), ('ticket_data', models.JSONField(default=dict, help_text='工单自定义字段内容', verbose_name='工单数据')),
('in_add_node', models.BooleanField(default=False, help_text='是否处于加签状态下', verbose_name='加签状态中')), ('in_add_node', models.BooleanField(default=False, help_text='是否处于加签状态下', verbose_name='加签状态中')),
('script_run_last_result', models.BooleanField(default=True, verbose_name='脚本最后一次执行结果')), ('script_run_last_result', models.BooleanField(default=True, verbose_name='脚本最后一次执行结果')),
('participant_type', models.IntegerField(choices=[(0, '无处理人'), (1, '个人'), (2, '多人'), (4, '角色'), (6, '脚本'), (7, '工单的字段'), (9, '代码获取')], default=0, help_text='0.无处理人,1.个人,2.多人', verbose_name='当前处理人类型')), ('participant_type', models.IntegerField(choices=[(0, '无处理人'), (1, '个人'), (2, '多人'), (3, '部门'), (4, '角色'), (10, '岗位'), (6, '脚本'), (7, '工单的字段'), (9, '代码获取')], default=0, help_text='0.无处理人,1.个人,2.多人', verbose_name='当前处理人类型')),
('participant', models.JSONField(blank=True, default=list, help_text='可以为空(无处理人的情况,如结束状态)、userid、userid列表', verbose_name='当前处理人')), ('participant', models.JSONField(blank=True, default=list, help_text='可以为空(无处理人的情况,如结束状态)、userid、userid列表', verbose_name='当前处理人')),
('act_state', models.IntegerField(choices=[(0, '草稿中'), (1, '进行中'), (2, '被退回'), (3, '被撤回'), (4, '已完成'), (5, '已关闭')], default=1, help_text='当前工单的进行状态', verbose_name='进行状态')), ('act_state', models.IntegerField(choices=[(0, '草稿中'), (1, '进行中'), (2, '被退回'), (3, '被撤回'), (4, '已完成'), (5, '已关闭')], default=1, help_text='当前工单的进行状态', verbose_name='进行状态')),
('multi_all_person', models.JSONField(blank=True, default=dict, help_text='需要当前状态处理人全部处理时实际的处理结果json格式', verbose_name='全部处理的结果')), ('multi_all_person', models.JSONField(blank=True, default=dict, help_text='需要当前状态处理人全部处理时实际的处理结果json格式', verbose_name='全部处理的结果')),
@ -102,6 +104,7 @@ class Migration(migrations.Migration):
('condition_expression', models.JSONField(default=list, help_text='流转条件表达式,根据表达式中的条件来确定流转的下个状态,格式为[{"expression":"{days} > 3 and {days}<10", "target_state":11}] 其中{}用于填充工单的字段key,运算时会换算成实际的值当符合条件下个状态将变为target_state_id中的值,表达式只支持简单的运算或datetime/time运算.loonflow会以首次匹配成功的条件为准所以多个条件不要有冲突', max_length=1000, verbose_name='条件表达式')), ('condition_expression', models.JSONField(default=list, help_text='流转条件表达式,根据表达式中的条件来确定流转的下个状态,格式为[{"expression":"{days} > 3 and {days}<10", "target_state":11}] 其中{}用于填充工单的字段key,运算时会换算成实际的值当符合条件下个状态将变为target_state_id中的值,表达式只支持简单的运算或datetime/time运算.loonflow会以首次匹配成功的条件为准所以多个条件不要有冲突', max_length=1000, verbose_name='条件表达式')),
('attribute_type', models.IntegerField(choices=[(1, '同意'), (2, '拒绝'), (3, '其他')], default=1, help_text='属性类型1.同意2.拒绝3.其他', verbose_name='属性类型')), ('attribute_type', models.IntegerField(choices=[(1, '同意'), (2, '拒绝'), (3, '其他')], default=1, help_text='属性类型1.同意2.拒绝3.其他', verbose_name='属性类型')),
('field_require_check', models.BooleanField(default=True, help_text='默认在用户点击操作的时候需要校验工单表单的必填项,如果设置为否则不检查。用于如"退回"属性的操作,不需要填写表单内容', verbose_name='是否校验必填项')), ('field_require_check', models.BooleanField(default=True, help_text='默认在用户点击操作的时候需要校验工单表单的必填项,如果设置为否则不检查。用于如"退回"属性的操作,不需要填写表单内容', verbose_name='是否校验必填项')),
('on_submit_func', models.CharField(blank=True, max_length=100, null=True, verbose_name='提交操作调用方法')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transition_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transition_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('destination_state', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='dstate_transition', to='wf.state', verbose_name='目的状态')), ('destination_state', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='dstate_transition', to='wf.state', verbose_name='目的状态')),
('source_state', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sstate_transition', to='wf.state', verbose_name='源状态')), ('source_state', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sstate_transition', to='wf.state', verbose_name='源状态')),
@ -120,7 +123,7 @@ class Migration(migrations.Migration):
('update_time', models.DateTimeField(auto_now=True, 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='删除标记')), ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('suggestion', models.CharField(blank=True, default='', max_length=10000, verbose_name='处理意见')), ('suggestion', models.CharField(blank=True, default='', max_length=10000, verbose_name='处理意见')),
('participant_type', models.IntegerField(choices=[(0, '无处理人'), (1, '个人'), (2, '多人'), (4, '角色'), (6, '脚本'), (7, '工单的字段'), (9, '代码获取')], default=0, help_text='0.无处理人,1.个人,2.多人等', verbose_name='处理人类型')), ('participant_type', models.IntegerField(choices=[(0, '无处理人'), (1, '个人'), (2, '多人'), (3, '部门'), (4, '角色'), (10, '岗位'), (6, '脚本'), (7, '工单的字段'), (9, '代码获取')], default=0, help_text='0.无处理人,1.个人,2.多人等', verbose_name='处理人类型')),
('participant_str', models.CharField(blank=True, help_text='非人工处理的处理人相关信息', max_length=200, null=True, verbose_name='处理人')), ('participant_str', models.CharField(blank=True, help_text='非人工处理的处理人相关信息', max_length=200, null=True, verbose_name='处理人')),
('ticket_data', models.JSONField(blank=True, default=dict, help_text='可以用于记录当前表单数据json格式', verbose_name='工单数据')), ('ticket_data', models.JSONField(blank=True, default=dict, help_text='可以用于记录当前表单数据json格式', verbose_name='工单数据')),
('intervene_type', models.IntegerField(choices=[(0, '正常处理'), (1, '转交'), (2, '加签'), (3, '加签处理完成'), (4, '接单'), (5, '评论'), (6, '删除'), (7, '强制关闭'), (8, '强制修改状态'), (9, 'hook操作'), (10, '撤回'), (11, '抄送')], default=0, help_text='流转类型', verbose_name='干预类型')), ('intervene_type', models.IntegerField(choices=[(0, '正常处理'), (1, '转交'), (2, '加签'), (3, '加签处理完成'), (4, '接单'), (5, '评论'), (6, '删除'), (7, '强制关闭'), (8, '强制修改状态'), (9, 'hook操作'), (10, '撤回'), (11, '抄送')], default=0, help_text='流转类型', verbose_name='干预类型')),
@ -151,7 +154,7 @@ class Migration(migrations.Migration):
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, 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='删除标记')), ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('field_type', models.CharField(choices=[('string', '字符串'), ('int', '整型'), ('float', '浮点'), ('boolean', '布尔'), ('date', '日期'), ('datetime', '日期时间'), ('radio', '单选'), ('checkbox', '多选'), ('select', '单选下拉'), ('selects', '多选下拉'), ('cascader', '单选级联'), ('cascaders', '多选级联'), ('select_dg', '弹框单选'), ('select_dgs', '弹框多选'), ('textarea', '文本域'), ('file', '附件')], help_text='string, int, float, date, datetime, radio, checkbox, select, selects, cascader, cascaders, select_dg, select_dgs,textarea, file', max_length=50, verbose_name='类型')), ('field_type', models.CharField(choices=[('string', '字符串'), ('int', '整型'), ('float', '浮点'), ('boolean', '布尔'), ('date', '日期'), ('datetime', '日期时间'), ('radio', '单选'), ('checkbox', '多选'), ('select', '单选下拉'), ('selects', '多选下拉'), ('cascader', '单选级联'), ('cascaders', '多选级联'), ('select_dg', '弹框单选'), ('select_dgs', '弹框多选'), ('textarea', '文本域'), ('file', '附件'), ('table', '表格')], help_text='string, int, float, date, datetime, radio, checkbox, select, selects, cascader, cascaders, select_dg, select_dgs,textarea, file', max_length=50, verbose_name='类型')),
('field_key', models.CharField(help_text='字段类型请尽量特殊,避免与系统中关键字冲突', max_length=50, verbose_name='字段标识')), ('field_key', models.CharField(help_text='字段类型请尽量特殊,避免与系统中关键字冲突', max_length=50, verbose_name='字段标识')),
('field_name', models.CharField(max_length=50, verbose_name='字段名称')), ('field_name', models.CharField(max_length=50, verbose_name='字段名称')),
('sort', models.IntegerField(default=0, help_text='工单基础字段在表单中排序为:流水号0,标题20,状态id40,状态名41,创建人80,创建时间100,更新时间120.前端展示工单信息的表单可以根据这个id顺序排列', verbose_name='排序')), ('sort', models.IntegerField(default=0, help_text='工单基础字段在表单中排序为:流水号0,标题20,状态id40,状态名41,创建人80,创建时间100,更新时间120.前端展示工单信息的表单可以根据这个id顺序排列', verbose_name='排序')),
@ -163,7 +166,9 @@ class Migration(migrations.Migration):
('field_choice', models.JSONField(blank=True, default=list, help_text='选项值格式为list, 例["id":1, "name":"张三"]', verbose_name='选项值')), ('field_choice', models.JSONField(blank=True, default=list, help_text='选项值格式为list, 例["id":1, "name":"张三"]', verbose_name='选项值')),
('label', models.CharField(default='', help_text='处理特殊逻辑使用,比如sys_user用于获取用户作为选项', max_length=1000, verbose_name='标签')), ('label', models.CharField(default='', help_text='处理特殊逻辑使用,比如sys_user用于获取用户作为选项', max_length=1000, verbose_name='标签')),
('is_hidden', models.BooleanField(default=False, help_text='可用于携带不需要用户查看的字段信息', verbose_name='是否隐藏')), ('is_hidden', models.BooleanField(default=False, help_text='可用于携带不需要用户查看的字段信息', verbose_name='是否隐藏')),
('group', models.CharField(blank=True, max_length=100, null=True, verbose_name='字段分组')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='customfield_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='customfield_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='wf.customfield', verbose_name='父字段')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='customfield_update_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='customfield_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
('workflow', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wf.workflow', verbose_name='所属工作流')), ('workflow', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wf.workflow', verbose_name='所属工作流')),
], ],

View File

@ -43,12 +43,14 @@ class State(CommonAModel):
PARTICIPANT_TYPE_FIELD = 7 PARTICIPANT_TYPE_FIELD = 7
PARTICIPANT_TYPE_PARENT_FIELD = 8 PARTICIPANT_TYPE_PARENT_FIELD = 8
PARTICIPANT_TYPE_FORMCODE = 9 PARTICIPANT_TYPE_FORMCODE = 9
PARTICIPANT_TYPE_POST = 10
state_participanttype_choices = ( state_participanttype_choices = (
(0, '无处理人'), (0, '无处理人'),
(PARTICIPANT_TYPE_PERSONAL, '个人'), (PARTICIPANT_TYPE_PERSONAL, '个人'),
(PARTICIPANT_TYPE_MULTI, '多人'), (PARTICIPANT_TYPE_MULTI, '多人'),
# (PARTICIPANT_TYPE_DEPT, '部门'), (PARTICIPANT_TYPE_DEPT, '部门'),
(PARTICIPANT_TYPE_ROLE, '角色'), (PARTICIPANT_TYPE_ROLE, '角色'),
(PARTICIPANT_TYPE_POST, '岗位'),
# (PARTICIPANT_TYPE_VARIABLE, '变量'), # (PARTICIPANT_TYPE_VARIABLE, '变量'),
(PARTICIPANT_TYPE_ROBOT, '脚本'), (PARTICIPANT_TYPE_ROBOT, '脚本'),
(PARTICIPANT_TYPE_FIELD, '工单的字段'), (PARTICIPANT_TYPE_FIELD, '工单的字段'),
@ -83,6 +85,7 @@ class State(CommonAModel):
type = models.IntegerField('状态类型', default=0, choices=type_choices, type = models.IntegerField('状态类型', default=0, choices=type_choices,
help_text='0.普通类型 1.初始状态(用于新建工单时,获取对应的字段必填及transition信息) 2.结束状态(此状态下的工单不得再处理即没有对应的transition)') help_text='0.普通类型 1.初始状态(用于新建工单时,获取对应的字段必填及transition信息) 2.结束状态(此状态下的工单不得再处理即没有对应的transition)')
enable_retreat = models.BooleanField('允许撤回', default=False, help_text='开启后允许工单创建人在此状态直接撤回工单到初始状态') enable_retreat = models.BooleanField('允许撤回', default=False, help_text='开启后允许工单创建人在此状态直接撤回工单到初始状态')
enable_deliver = models.BooleanField('允许转交', default=False)
participant_type = models.IntegerField('参与者类型', choices=state_participanttype_choices, default=1, blank=True, participant_type = models.IntegerField('参与者类型', choices=state_participanttype_choices, default=1, blank=True,
help_text='0.无处理人,1.个人,2.多人,3.部门,4.角色,5.变量(支持工单创建人,创建人的leader),6.脚本,7.工单的字段内容(如表单中的"测试负责人",需要为用户名或者逗号隔开的多个用户名),8.父工单的字段内容。 初始状态请选择类型5参与人填create_by') help_text='0.无处理人,1.个人,2.多人,3.部门,4.角色,5.变量(支持工单创建人,创建人的leader),6.脚本,7.工单的字段内容(如表单中的"测试负责人",需要为用户名或者逗号隔开的多个用户名),8.父工单的字段内容。 初始状态请选择类型5参与人填create_by')
participant = models.JSONField('参与者', default=list, blank=True, participant = models.JSONField('参与者', default=list, blank=True,
@ -94,6 +97,7 @@ class State(CommonAModel):
help_text='1.主动接单(如果当前处理人实际为多人的时候,需要先接单才能处理) 2.直接处理(即使当前处理人实际为多人,也可以直接处理) 3.随机分配(如果实际为多人,则系统会随机分配给其中一个人) 4.全部处理(要求所有参与人都要处理一遍,才能进入下一步)') help_text='1.主动接单(如果当前处理人实际为多人的时候,需要先接单才能处理) 2.直接处理(即使当前处理人实际为多人,也可以直接处理) 3.随机分配(如果实际为多人,则系统会随机分配给其中一个人) 4.全部处理(要求所有参与人都要处理一遍,才能进入下一步)')
filter_policy = models.IntegerField('参与人过滤策略', default=0, choices=state_filter_choices) filter_policy = models.IntegerField('参与人过滤策略', default=0, choices=state_filter_choices)
participant_cc = models.JSONField('抄送给', default=list, blank=True, help_text='抄送给(userid列表)') participant_cc = models.JSONField('抄送给', default=list, blank=True, help_text='抄送给(userid列表)')
on_reach_func = models.CharField('到达时调用方法', max_length=100, null=True, blank=True)
class Transition(CommonAModel): class Transition(CommonAModel):
@ -148,6 +152,7 @@ class Transition(CommonAModel):
'属性类型', default=1, choices=attribute_type_choices, help_text='属性类型1.同意2.拒绝3.其他') '属性类型', default=1, choices=attribute_type_choices, help_text='属性类型1.同意2.拒绝3.其他')
field_require_check = models.BooleanField( field_require_check = models.BooleanField(
'是否校验必填项', default=True, help_text='默认在用户点击操作的时候需要校验工单表单的必填项,如果设置为否则不检查。用于如"退回"属性的操作,不需要填写表单内容') '是否校验必填项', default=True, help_text='默认在用户点击操作的时候需要校验工单表单的必填项,如果设置为否则不检查。用于如"退回"属性的操作,不需要填写表单内容')
on_submit_func = models.CharField('提交操作调用方法', max_length=100, null=True, blank=True)
class CustomField(CommonAModel): class CustomField(CommonAModel):
@ -168,7 +173,8 @@ class CustomField(CommonAModel):
('select_dg', '弹框单选'), ('select_dg', '弹框单选'),
('select_dgs', '弹框多选'), ('select_dgs', '弹框多选'),
('textarea', '文本域'), ('textarea', '文本域'),
('file', '附件') ('file', '附件'),
('table', '表格')
) )
workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='所属工作流') workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='所属工作流')
field_type = models.CharField('类型', max_length=50, choices=field_type_choices, field_type = models.CharField('类型', max_length=50, choices=field_type_choices,
@ -192,6 +198,8 @@ class CustomField(CommonAModel):
label = models.CharField('标签', max_length=1000, default='', help_text='处理特殊逻辑使用,比如sys_user用于获取用户作为选项') label = models.CharField('标签', max_length=1000, default='', help_text='处理特殊逻辑使用,比如sys_user用于获取用户作为选项')
# hook = models.CharField('hook', max_length=1000, default='', help_text='获取下拉选项用于动态选项值') # hook = models.CharField('hook', max_length=1000, default='', help_text='获取下拉选项用于动态选项值')
is_hidden = models.BooleanField('是否隐藏', default=False, help_text='可用于携带不需要用户查看的字段信息') is_hidden = models.BooleanField('是否隐藏', default=False, help_text='可用于携带不需要用户查看的字段信息')
group = models.CharField('字段分组', max_length=100, null=True, blank=True)
parent = models.ForeignKey('self', verbose_name='父字段', on_delete=models.SET_NULL, null=True, blank=True)
class Ticket(CommonAModel): class Ticket(CommonAModel):

View File

@ -26,7 +26,7 @@ class WorkflowSimpleSerializer(serializers.ModelSerializer):
class StateSimpleSerializer(serializers.ModelSerializer): class StateSimpleSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = State model = State
fields = ['id', 'name', 'type', 'distribute_type', 'enable_retreat'] fields = ['id', 'name', 'type', 'distribute_type', 'enable_retreat', 'enable_deliver']
class TransitionSerializer(serializers.ModelSerializer): class TransitionSerializer(serializers.ModelSerializer):
@ -199,6 +199,11 @@ class TicketHandleSerializer(serializers.Serializer):
suggestion = serializers.CharField(label="处理意见", required=False, allow_blank=True) suggestion = serializers.CharField(label="处理意见", required=False, allow_blank=True)
class TicketDeliverSerializer(serializers.Serializer):
target_user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), label='转交人')
suggestion = serializers.CharField(label="转交原因", required=False)
class TicketRetreatSerializer(serializers.Serializer): class TicketRetreatSerializer(serializers.Serializer):
suggestion = serializers.CharField(label="撤回原因", required=False) suggestion = serializers.CharField(label="撤回原因", required=False)

View File

@ -1,3 +1,4 @@
import importlib
from apps.wf.serializers import TicketSimpleSerializer from apps.wf.serializers import TicketSimpleSerializer
from apps.system.models import User from apps.system.models import User
from apps.wf.models import CustomField, State, Ticket, TicketFlow, Transition, Workflow from apps.wf.models import CustomField, State, Ticket, TicketFlow, Transition, Workflow
@ -157,11 +158,17 @@ class WfService(object):
destination_participant_type, destination_participant = state.participant_type, state.participant destination_participant_type, destination_participant = state.participant_type, state.participant
if destination_participant_type == State.PARTICIPANT_TYPE_FIELD: if destination_participant_type == State.PARTICIPANT_TYPE_FIELD:
destination_participant = new_ticket_data.get(destination_participant, 0) if destination_participant \ destination_participant = new_ticket_data.get(destination_participant, 0) if destination_participant \
in new_ticket_data else Ticket.ticket_data.get(destination_participant, 0) in new_ticket_data else Ticket.ticket_data.get(destination_participant, 0)
elif destination_participant_type == State.PARTICIPANT_TYPE_FORMCODE: # 代码获取 elif destination_participant_type == State.PARTICIPANT_TYPE_FORMCODE: # 代码获取
destination_participant = getattr(GetParticipants, destination_participant)( # if '.' in destination_participant:
state=state, ticket=ticket, new_ticket_data=new_ticket_data, hander=handler) module, func = destination_participant.rsplit(".", 1)
m = importlib.import_module(module)
f = getattr(m, func)
destination_participant = f(state=state, ticket=ticket, new_ticket_data=new_ticket_data, hander=handler)
# else:
# destination_participant = getattr(GetParticipants, destination_participant)(
# state=state, ticket=ticket, new_ticket_data=new_ticket_data, hander=handler)
elif destination_participant_type == State.PARTICIPANT_TYPE_DEPT: # 部门 elif destination_participant_type == State.PARTICIPANT_TYPE_DEPT: # 部门
destination_participant = list(User.objects.filter( destination_participant = list(User.objects.filter(
@ -269,6 +276,13 @@ class WfService(object):
source_state = ticket.state source_state = ticket.state
source_ticket_data = ticket.ticket_data source_ticket_data = ticket.ticket_data
# 提交时可能进行的操作
if transition.on_submit_func:
module, func = transition.on_submit_func.rsplit(".", 1)
m = importlib.import_module(module)
f = getattr(m, func)
f(ticket=ticket, transition=transition, new_ticket_data=new_ticket_data)
# 校验处理权限 # 校验处理权限
if not handler or not created: # 没有处理人意味着系统触发不校验处理权限 if not handler or not created: # 没有处理人意味着系统触发不校验处理权限
result = WfService.ticket_handle_permission_check(ticket, handler) result = WfService.ticket_handle_permission_check(ticket, handler)
@ -351,8 +365,35 @@ class WfService(object):
participant_type=0, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_CC, participant_type=0, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_CC,
participant=None, participant_cc=destination_state.participant_cc) participant=None, participant_cc=destination_state.participant_cc)
# 如果目标状态是脚本则执行
if destination_state.participant_type == State.PARTICIPANT_TYPE_ROBOT:
getattr(HandleScripts, destination_state.participant)(ticket)
return ticket return ticket
@classmethod
def task_ticket(cls, ticket: Ticket):
"""
执行任务(自定义任务或通知)
"""
# 如果目标状态是脚本则异步执行
state = ticket.state
if state.participant_type == State.PARTICIPANT_TYPE_ROBOT:
module, func = state.participant.rsplit(".", 1)
m = importlib.import_module(module)
f = getattr(m, func)
ticket.script_run_last_result = False
ticket.save()
f.delay(ticket_id=ticket.id, script_str=state.participant) # 里面要加入回调才能继续流转
# 如果目标状态有func,由func执行额外操作(比如发送通知)
if state.on_reach_func:
module, func = state.func.rsplit(".", 1)
m = importlib.import_module(module)
f = getattr(m, func)
f.delay(ticket_id=ticket.id) # 不用加入回调
else:
# wf默认发送通知
last_log = TicketFlow.objects.filter(ticket=ticket).order_by('-create_time').first()
if (last_log.state != state or
last_log.intervene_type == Transition.TRANSITION_INTERVENE_TYPE_DELIVER or
ticket.in_add_node):
# 如果状态变化或是转交加签的情况再发送通知
from tasks import send_ticket_notice
send_ticket_notice.delay(ticket_id=ticket.id)

15
apps/wf/tasks.py Normal file
View File

@ -0,0 +1,15 @@
# Create your tasks here
from __future__ import absolute_import, unicode_literals
from apps.utils.task import CustomTask
from celery import shared_task
from apps.wf.serializers import TicketDetailSerializer
@shared_task(base=CustomTask)
def send_ticket_notice(ticket):
"""
发送通知
"""
data = TicketDetailSerializer(instance=ticket).data

View File

@ -8,7 +8,7 @@ from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModel
RetrieveModelMixin, UpdateModelMixin RetrieveModelMixin, UpdateModelMixin
from apps.wf.serializers import CustomFieldCreateUpdateSerializer, CustomFieldSerializer, StateSerializer, \ from apps.wf.serializers import CustomFieldCreateUpdateSerializer, CustomFieldSerializer, StateSerializer, \
TicketAddNodeEndSerializer, TicketAddNodeSerializer, TicketCloseSerializer, \ TicketAddNodeEndSerializer, TicketAddNodeSerializer, TicketCloseSerializer, \
TicketCreateSerializer, TicketDestorySerializer, TicketFlowSerializer, \ TicketCreateSerializer, TicketDeliverSerializer, TicketDestorySerializer, TicketFlowSerializer, \
TicketHandleSerializer, TicketRetreatSerializer, \ TicketHandleSerializer, TicketRetreatSerializer, \
TicketSerializer, TransitionSerializer, WorkflowSerializer, \ TicketSerializer, TransitionSerializer, WorkflowSerializer, \
TicketListSerializer, TicketDetailSerializer TicketListSerializer, TicketDetailSerializer
@ -17,7 +17,7 @@ from rest_framework.decorators import action
from apps.wf.models import CustomField, Ticket, Workflow, State, Transition, TicketFlow from apps.wf.models import CustomField, Ticket, Workflow, State, Transition, TicketFlow
from apps.utils.mixins import CreateUpdateCustomMixin, CreateUpdateModelAMixin from apps.utils.mixins import CreateUpdateCustomMixin, CreateUpdateModelAMixin
from apps.wf.services import WfService from apps.wf.services import WfService
from rest_framework.exceptions import APIException from rest_framework.exceptions import ParseError
from rest_framework import status from rest_framework import status
from django.db.models import Count from django.db.models import Count
from .scripts import GetParticipants from .scripts import GetParticipants
@ -148,14 +148,15 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
return TicketListSerializer return TicketListSerializer
elif self.action == 'retrieve': elif self.action == 'retrieve':
return TicketDetailSerializer return TicketDetailSerializer
elif self.action == 'deliver':
return TicketDeliverSerializer
return super().get_serializer_class() return super().get_serializer_class()
def filter_queryset(self, queryset): def filter_queryset(self, queryset):
if not self.detail and not self.request.query_params.get('category', None): if not self.detail and not self.request.query_params.get('category', None):
raise APIException('请指定查询分类') raise ParseError('请指定查询分类')
return super().filter_queryset(queryset) return super().filter_queryset(queryset)
@transaction.atomic
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
""" """
新建工单 新建工单
@ -174,29 +175,30 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
for key, value in start_state.state_fields.items(): for key, value in start_state.state_fields.items():
if int(value) == State.STATE_FIELD_REQUIRED: if int(value) == State.STATE_FIELD_REQUIRED:
if key not in ticket_data and not ticket_data[key]: if key not in ticket_data and not ticket_data[key]:
raise APIException('字段{}必填'.format(key)) raise ParseError('字段{}必填'.format(key))
save_ticket_data[key] = ticket_data[key] save_ticket_data[key] = ticket_data[key]
elif int(value) == State.STATE_FIELD_OPTIONAL: elif int(value) == State.STATE_FIELD_OPTIONAL:
save_ticket_data[key] = ticket_data[key] save_ticket_data[key] = ticket_data[key]
with transaction.atomic():
ticket = serializer.save(state=start_state, ticket = serializer.save(state=start_state,
create_by=request.user, create_by=request.user,
create_time=timezone.now(), create_time=timezone.now(),
act_state=Ticket.TICKET_ACT_STATE_DRAFT, act_state=Ticket.TICKET_ACT_STATE_DRAFT,
belong_dept=request.user.dept, belong_dept=request.user.dept,
ticket_data=save_ticket_data) # 先创建出来 ticket_data=save_ticket_data) # 先创建出来
# 更新title和sn # 更新title和sn
title = vdata.get('title', '') title = vdata.get('title', '')
title_template = ticket.workflow.title_template title_template = ticket.workflow.title_template
if title_template: if title_template:
all_ticket_data = {**rdata, **ticket_data} all_ticket_data = {**rdata, **ticket_data}
title = title_template.format(**all_ticket_data) title = title_template.format(**all_ticket_data)
sn = WfService.get_ticket_sn(ticket.workflow) # 流水号 sn = WfService.get_ticket_sn(ticket.workflow) # 流水号
ticket.sn = sn ticket.sn = sn
ticket.title = title ticket.title = title
ticket.save() ticket.save()
ticket = WfService.handle_ticket(ticket=ticket, transition=transition, new_ticket_data=ticket_data, ticket = WfService.handle_ticket(ticket=ticket, transition=transition, new_ticket_data=ticket_data,
handler=request.user, created=True) handler=request.user, created=True)
WfService.task_ticket(ticket)
return Response(TicketSerializer(instance=ticket).data) return Response(TicketSerializer(instance=ticket).data)
@action(methods=['get'], detail=False, perms_map={'get': '*'}) @action(methods=['get'], detail=False, perms_map={'get': '*'})
@ -212,7 +214,6 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
return Response(ret) return Response(ret)
@action(methods=['post'], detail=True, perms_map={'post': '*'}) @action(methods=['post'], detail=True, perms_map={'post': '*'})
@transaction.atomic
def handle(self, request, pk=None): def handle(self, request, pk=None):
""" """
处理工单 处理工单
@ -223,12 +224,37 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
vdata = serializer.validated_data vdata = serializer.validated_data
new_ticket_data = ticket.ticket_data new_ticket_data = ticket.ticket_data
new_ticket_data.update(**vdata['ticket_data']) new_ticket_data.update(**vdata['ticket_data'])
with transaction.atomic():
ticket = WfService.handle_ticket(ticket=ticket, transition=vdata['transition'], ticket = WfService.handle_ticket(ticket=ticket, transition=vdata['transition'],
new_ticket_data=new_ticket_data, handler=request.user, new_ticket_data=new_ticket_data, handler=request.user,
suggestion=vdata['suggestion']) suggestion=vdata['suggestion'])
WfService.task_ticket(ticket)
return Response(TicketSerializer(instance=ticket).data) return Response(TicketSerializer(instance=ticket).data)
@action(methods=['post'], detail=True, perms_map={'post': '*'})
def deliver(self, request, pk=None):
"""
转交工单
"""
ticket = self.get_object()
rdata = request.data
serializer = self.get_serializer(data=rdata)
serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data # 校验之后的数据
if not ticket.state.enable_deliver:
raise ParseError('不允许转交')
with transaction.atomic():
ticket.participant_type = State.PARTICIPANT_TYPE_PERSONAL
ticket.participant = vdata['target_user']
ticket.save()
TicketFlow.objects.create(ticket=ticket, state=ticket.state,
ticket_data=WfService.get_ticket_all_field_value(ticket),
suggestion=vdata['suggestion'], participant_type=State.PARTICIPANT_TYPE_PERSONAL,
intervene_type=Transition.TRANSITION_INTERVENE_TYPE_DELIVER,
participant=request.user, transition=None)
WfService.task_ticket(ticket)
return Response()
@action(methods=['get'], detail=True, perms_map={'get': '*'}) @action(methods=['get'], detail=True, perms_map={'get': '*'})
def flowsteps(self, request, pk=None): def flowsteps(self, request, pk=None):
""" """
@ -277,7 +303,7 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
participant=request.user, transition=None) participant=request.user, transition=None)
return Response() return Response()
else: else:
raise APIException('无需接单') raise ParseError('无需接单')
@action(methods=['post'], detail=True, perms_map={'post': '*'}) @action(methods=['post'], detail=True, perms_map={'post': '*'})
def retreat(self, request, pk=None): def retreat(self, request, pk=None):
@ -286,9 +312,9 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
""" """
ticket = self.get_object() ticket = self.get_object()
if ticket.create_by != request.user: if ticket.create_by != request.user:
raise APIException('非创建人不可撤回') raise ParseError('非创建人不可撤回')
if not ticket.state.enable_retreat: if not ticket.state.enable_retreat:
raise APIException('该状态不可撤回') raise ParseError('该状态不可撤回')
start_state = WfService.get_workflow_start_state(ticket.workflow) start_state = WfService.get_workflow_start_state(ticket.workflow)
ticket.state = start_state ticket.state = start_state
ticket.participant_type = State.PARTICIPANT_TYPE_PERSONAL ticket.participant_type = State.PARTICIPANT_TYPE_PERSONAL
@ -324,6 +350,7 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL, suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL,
intervene_type=Transition.TRANSITION_INTERVENE_TYPE_ADD_NODE, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_ADD_NODE,
participant=request.user, transition=None) participant=request.user, transition=None)
WfService.task_ticket(ticket)
return Response() return Response()
@action(methods=['post'], detail=True, perms_map={'post': '*'}, serializer_class=TicketAddNodeEndSerializer) @action(methods=['post'], detail=True, perms_map={'post': '*'}, serializer_class=TicketAddNodeEndSerializer)

View File

@ -5,7 +5,7 @@ from celery import Celery
# set the default Django settings module for the 'celery' program. # set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings') os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings')
app = Celery('server') app = Celery('ehs')
# Using a string here means the worker doesn't have to serialize # Using a string here means the worker doesn't have to serialize
# the configuration object to child processes. # the configuration object to child processes.

View File

@ -55,7 +55,9 @@ INSTALLED_APPS = [
'apps.monitor', 'apps.monitor',
'apps.wf', 'apps.wf',
'apps.hrm', 'apps.hrm',
'apps.am' 'apps.am',
'apps.vm',
'apps.rpm'
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@ -141,10 +143,10 @@ USE_TZ = True
# https://docs.djangoproject.com/en/3.0/howto/static-files/ # https://docs.djangoproject.com/en/3.0/howto/static-files/
STATIC_URL = '/static/' STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'dist/static_collect') STATIC_ROOT = os.path.join(BASE_DIR, 'dist/static')
STATICFILES_DIRS = ( # STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'dist/static'), # os.path.join(BASE_DIR, 'dist/static'),
) # )
MEDIA_URL = '/media/' MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
@ -224,7 +226,7 @@ AUTHENTICATION_BACKENDS = (
# } # }
# celery配置,celery正常运行必须安装redis # celery配置,celery正常运行必须安装redis
CELERY_BROKER_URL = "redis://127.0.0.1:6379/1" # 任务存储 CELERY_BROKER_URL = "redis://localhost:6379/2" # 任务存储
CELERYD_MAX_TASKS_PER_CHILD = 100 # 每个worker最多执行100个任务就会被销毁可防止内存泄露 CELERYD_MAX_TASKS_PER_CHILD = 100 # 每个worker最多执行100个任务就会被销毁可防止内存泄露
CELERY_TIMEZONE = 'Asia/Shanghai' # 设置时区 CELERY_TIMEZONE = 'Asia/Shanghai' # 设置时区
CELERY_ENABLE_UTC = True # 启动时区设置 CELERY_ENABLE_UTC = True # 启动时区设置

View File

@ -31,6 +31,7 @@ schema_view = get_schema_view(
), ),
public=True, public=True,
permission_classes=[], permission_classes=[],
url=settings.BASE_URL
) )
urlpatterns = [ urlpatterns = [