代码重构
This commit is contained in:
parent
c114f2ccf4
commit
e096dd7ad3
|
@ -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='关联岗位'),
|
||||
),
|
||||
]
|
|
@ -1,28 +1,37 @@
|
|||
from django.db import models
|
||||
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.
|
||||
|
||||
|
||||
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)
|
||||
level = models.PositiveSmallIntegerField('区域等级')
|
||||
sort_str = models.CharField('排序字符', max_length=12, default='1')
|
||||
manager = models.ForeignKey(User, on_delete=models.CASCADE,
|
||||
verbose_name='区域负责人')
|
||||
visitor_no = models.BooleanField('不准许访客', default=True)
|
||||
rparty_no = models.BooleanField('不准许相关方', default=True)
|
||||
visitor_yes = models.BooleanField('准许访客人员', default=False)
|
||||
remployee_yes = models.BooleanField('准许相关方人员', default=False)
|
||||
employee_yes = models.BooleanField('准许全部员工', default=True)
|
||||
posts_access = models.ManyToManyField(Post, through='am.access')
|
||||
third_info = models.JSONField('三方信息', default=dict,
|
||||
null=False, blank=True)
|
||||
|
||||
|
||||
class Access(BaseModel):
|
||||
class Access(CommonADModel):
|
||||
"""
|
||||
准入权限
|
||||
岗位准入权限
|
||||
"""
|
||||
ACCESS_IN_YES = 10
|
||||
ACCESS_IN_NO = 20
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from apps.am.models import Area
|
||||
from apps.am.models import Access, Area
|
||||
from apps.utils.serializers import CustomModelSerializer
|
||||
|
||||
|
||||
|
@ -6,3 +6,15 @@ class AreaSimpleSerializer(CustomModelSerializer):
|
|||
class Meta:
|
||||
model = Area
|
||||
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']
|
||||
|
|
|
@ -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)),
|
||||
]
|
|
@ -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.
|
||||
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
|
||||
|
|
|
@ -7,7 +7,8 @@ from rest_framework.exceptions import APIException
|
|||
|
||||
|
||||
class ReloadServerGit(APIView):
|
||||
permission_classes = [IsAdminUser]
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
|
||||
def post(self, request):
|
||||
"""
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class EcmConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.ecm'
|
|
@ -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)
|
|
@ -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__'
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
|
@ -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)),
|
||||
]
|
|
@ -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)
|
||||
|
|
@ -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,
|
||||
},
|
||||
),
|
||||
]
|
|
@ -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='三方信息'),
|
||||
),
|
||||
]
|
|
@ -1,7 +1,7 @@
|
|||
from django.db import models
|
||||
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):
|
||||
|
@ -14,9 +14,15 @@ class Employee(CommonBModel):
|
|||
(JOB_ON, '在职'),
|
||||
(JOB_OFF, '离职'),
|
||||
)
|
||||
PEOPLE_TYPE_CHOICES = (
|
||||
('employee', '内部员工'),
|
||||
('remployee', '相关方人员'),
|
||||
('visitor', '访客')
|
||||
)
|
||||
type = models.CharField('人员类型', default='employee', max_length=10, choices=PEOPLE_TYPE_CHOICES)
|
||||
user = models.OneToOneField(User,
|
||||
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)
|
||||
phone = models.CharField('手机号', max_length=11, null=True, blank=True)
|
||||
email = models.EmailField('邮箱号', null=True, blank=True)
|
||||
|
@ -33,7 +39,7 @@ class Employee(CommonBModel):
|
|||
show_atwork = models.BooleanField('是否展示在岗状态', default=True)
|
||||
last_check_time = models.DateTimeField('打卡时间', null=True, blank=True)
|
||||
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:
|
||||
verbose_name = '员工补充信息'
|
||||
|
@ -69,3 +75,22 @@ class ClockRecord(CommonADModel):
|
|||
(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)
|
||||
|
|
|
@ -3,9 +3,9 @@ from rest_framework.serializers import ModelSerializer
|
|||
from rest_framework import serializers
|
||||
|
||||
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.hrm.models import ClockRecord, Employee, NotWorkRemark
|
||||
from apps.hrm.models import Certificate, ClockRecord, Employee, NotWorkRemark
|
||||
from apps.system.serializers import DeptSimpleSerializer, UserSimpleSerializer
|
||||
from django.db import transaction
|
||||
from apps.third.clients import dhClient
|
||||
|
@ -37,7 +37,7 @@ class EmployeeCreateUpdateSerializer(EmployeeBaseSerializer):
|
|||
model = Employee
|
||||
exclude = EXCLUDE_FIELDS + ['face_data',
|
||||
'is_atwork', 'last_check_time',
|
||||
'not_work_remark', 'third_info']
|
||||
'not_work_remark', 'third_info', 'type']
|
||||
extra_kwargs = {
|
||||
'phone': {'required': True},
|
||||
'number': {'required': True},
|
||||
|
@ -214,3 +214,15 @@ class NotWorkRemarkListSerializer(serializers.ModelSerializer):
|
|||
class Meta:
|
||||
model = NotWorkRemark
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class CertificateCreateUpdateSerializer(CustomModelSerializer):
|
||||
class Meta:
|
||||
model = Certificate
|
||||
exclude = EXCLUDE_FIELDS
|
||||
|
||||
|
||||
class CertificateSerializer(CustomModelSerializer):
|
||||
class Meta:
|
||||
model = Certificate
|
||||
fields = '__all__'
|
||||
|
|
|
@ -7,9 +7,10 @@ from apps.hrm.models import Employee
|
|||
@receiver(post_save, sender=User)
|
||||
def updateEmployee(sender, instance, created, **kwargs):
|
||||
# if created:
|
||||
# 如果账号所属部门有变动, 更新关联人员的所属部门
|
||||
# 如果账号所属部门有变动, 更新关联人员的所属部门, 只限内部人员
|
||||
if not instance.is_superuser:
|
||||
ep = Employee.objects.filter(user=instance).first()
|
||||
if ep:
|
||||
if ep and ep.type == 'employee':
|
||||
if ep.belong_dept and ep.belong_dept != instance.belong_dept:
|
||||
ep.belong_dept = instance.belong_dept
|
||||
ep.save()
|
||||
|
|
|
@ -12,8 +12,8 @@ from rest_framework.response import Response
|
|||
from apps.hrm.errors import NO_NEED_LEVEL_REMARK
|
||||
from apps.hrm.filters import (ClockRecordFilterSet, EmployeeFilterSet,
|
||||
NotWorkRemarkFilterSet)
|
||||
from apps.hrm.models import ClockRecord, Employee, NotWorkRemark
|
||||
from apps.hrm.serializers import (ChannelAuthoritySerializer,
|
||||
from apps.hrm.models import Certificate, ClockRecord, Employee, NotWorkRemark
|
||||
from apps.hrm.serializers import (CertificateCreateUpdateSerializer, CertificateSerializer, ChannelAuthoritySerializer,
|
||||
ClockRecordListSerializer,
|
||||
EmployeeCreateUpdateSerializer,
|
||||
EmployeeNotWorkRemarkSerializer,
|
||||
|
@ -182,3 +182,10 @@ class NotWorkRemarkViewSet(ListModelMixin, CustomGenericViewSet):
|
|||
serializer_class = NotWorkRemarkListSerializer
|
||||
filterset_class = NotWorkRemarkFilterSet
|
||||
ordering = ['-pk']
|
||||
|
||||
|
||||
class CertificateViewSet(CustomModelViewSet):
|
||||
queryset = Certificate.objects.filter(employee__type='employee')
|
||||
create_serializer_class = CertificateCreateUpdateSerializer
|
||||
update_serializer_class = CertificateCreateUpdateSerializer
|
||||
serializer_class = CertificateSerializer
|
||||
|
|
|
@ -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.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import uuid
|
||||
|
||||
|
||||
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='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, 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)),
|
||||
('response_ms', models.PositiveIntegerField(default=0)),
|
||||
('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_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()),
|
||||
('host', models.URLField()),
|
||||
('method', models.CharField(max_length=10)),
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
]
|
|
@ -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,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class OpmConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.opm'
|
|
@ -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)
|
|
@ -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__"
|
|
@ -0,0 +1,5 @@
|
|||
from apps.wf.models import Ticket
|
||||
|
||||
|
||||
def create_opt(ticket: Ticket):
|
||||
pass
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
|
@ -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
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class RpmConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.rpm'
|
|
@ -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)
|
|
@ -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']
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
|
@ -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()
|
|
@ -1,5 +1,4 @@
|
|||
from django.contrib import admin
|
||||
from simple_history.admin import SimpleHistoryAdmin
|
||||
from .models import User, Dept, Role, Permission, DictType, Dictionary, File
|
||||
# Register your models here.
|
||||
admin.site.register(User)
|
||||
|
@ -7,5 +6,5 @@ admin.site.register(Dept)
|
|||
admin.site.register(Role)
|
||||
admin.site.register(Permission)
|
||||
admin.site.register(DictType)
|
||||
admin.site.register(Dictionary, SimpleHistoryAdmin)
|
||||
admin.site.register(Dictionary)
|
||||
admin.site.register(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
|
||||
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='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, 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='姓名')),
|
||||
('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='头像')),
|
||||
],
|
||||
options={
|
||||
|
@ -55,7 +56,7 @@ class Migration(migrations.Migration):
|
|||
('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='名称')),
|
||||
('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='排序标记')),
|
||||
('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='创建人')),
|
||||
|
@ -94,11 +95,14 @@ class Migration(migrations.Migration):
|
|||
('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=32, unique=True, verbose_name='名称')),
|
||||
('code', models.CharField(blank=True, max_length=32, null=True, unique=True, verbose_name='岗位标识')),
|
||||
('name', models.CharField(max_length=32, 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='描述')),
|
||||
('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='创建人')),
|
||||
('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={
|
||||
'verbose_name': '职位/岗位',
|
||||
|
@ -132,8 +136,8 @@ class Migration(migrations.Migration):
|
|||
('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=32, unique=True, verbose_name='角色')),
|
||||
('code', models.CharField(blank=True, max_length=32, null=True, unique=True, verbose_name='角色标识')),
|
||||
('name', models.CharField(max_length=32, 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='描述')),
|
||||
('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='功能权限')),
|
||||
|
@ -145,15 +149,20 @@ class Migration(migrations.Migration):
|
|||
'ordering': ['code'],
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='post',
|
||||
name='roles',
|
||||
field=models.ManyToManyField(related_name='post_roles', to='system.Role', verbose_name='关联角色'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='post',
|
||||
name='update_by',
|
||||
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='最后编辑人'),
|
||||
migrations.CreateModel(
|
||||
name='PostRole',
|
||||
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='删除标记')),
|
||||
('data_range', models.PositiveSmallIntegerField(choices=[(10, '全部'), (30, '同级及以下'), (40, '本级及以下'), (50, '本级'), (60, '仅本人')], default=40, 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(
|
||||
name='File',
|
||||
|
@ -184,7 +193,7 @@ class Migration(migrations.Migration):
|
|||
('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=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='创建人')),
|
||||
('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='最后编辑人')),
|
||||
|
@ -220,6 +229,11 @@ class Migration(migrations.Migration):
|
|||
name='posts',
|
||||
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(
|
||||
model_name='user',
|
||||
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'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Dict',
|
||||
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='创建时间')),
|
||||
|
@ -248,10 +262,10 @@ class Migration(migrations.Migration):
|
|||
('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='dict_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='父')),
|
||||
('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='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={
|
||||
'verbose_name': '字典',
|
||||
|
|
|
@ -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='手机号'),
|
||||
),
|
||||
]
|
|
@ -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',
|
||||
),
|
||||
]
|
|
@ -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',
|
||||
),
|
||||
]
|
|
@ -1,8 +1,17 @@
|
|||
import enum
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
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):
|
||||
"""
|
||||
功能权限:目录,菜单,按钮
|
||||
|
@ -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)
|
||||
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,
|
||||
on_delete=models.SET_NULL, verbose_name='父')
|
||||
sort = models.PositiveSmallIntegerField('排序标记', default=1)
|
||||
|
@ -82,22 +85,10 @@ class Post(CommonAModel):
|
|||
name = models.CharField('名称', max_length=32)
|
||||
code = models.CharField('岗位标识', max_length=32, null=True, blank=True)
|
||||
description = models.CharField('描述', max_length=50, blank=True, null=True)
|
||||
roles = models.ManyToManyField(Role, verbose_name='关联角色', related_name='post_roles')
|
||||
POST_DATA_ALL = 10
|
||||
POST_DATA_CUSTOM = 20
|
||||
POST_DATA_SAMELEVE_AND_BELOW = 30
|
||||
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)
|
||||
parent = models.ForeignKey('self', null=True, blank=True,
|
||||
on_delete=models.SET_NULL, verbose_name='父')
|
||||
min_hour = models.PositiveSmallIntegerField('最小在岗时间', default=0)
|
||||
max_hour = models.PositiveSmallIntegerField('最长在岗时间', default=12)
|
||||
|
||||
class Meta:
|
||||
verbose_name = '职位/岗位'
|
||||
|
@ -108,17 +99,32 @@ class Post(CommonAModel):
|
|||
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):
|
||||
"""
|
||||
用户
|
||||
"""
|
||||
type = models.CharField('账号类型', max_length=10, default='employee')
|
||||
name = models.CharField('姓名', max_length=20, null=True, blank=True)
|
||||
phone = models.CharField('手机号', max_length=11, null=True, blank=True)
|
||||
avatar = models.CharField(
|
||||
'头像', default='/media/default/avatar.png', max_length=100, null=True, blank=True)
|
||||
superior = models.ForeignKey(
|
||||
'self', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='上级主管')
|
||||
posts = models.ManyToManyField(Post, through='system.userpost', related_name='user_posts')
|
||||
depts = models.ManyToManyField(Dept, through='system.userpost')
|
||||
roles = models.ManyToManyField(Role, verbose_name='关联角色')
|
||||
|
||||
class Meta:
|
||||
verbose_name = '用户信息'
|
||||
|
|
|
@ -5,7 +5,7 @@ from django_celery_results.models import TaskResult
|
|||
from apps.system.errors import USERNAME_EXIST
|
||||
from apps.utils.serializers import CustomModelSerializer
|
||||
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)
|
||||
from rest_framework.exceptions import ParseError
|
||||
from django.db import transaction
|
||||
|
@ -224,9 +224,6 @@ class DeptSerializer(CustomModelSerializer):
|
|||
"""
|
||||
组织架构序列化
|
||||
"""
|
||||
type = serializers.ChoiceField(
|
||||
choices=Dept.dept_type_choices, default='部门')
|
||||
|
||||
class Meta:
|
||||
model = Dept
|
||||
fields = '__all__'
|
||||
|
@ -367,6 +364,26 @@ class UserPostCreateSerializer(CustomModelSerializer):
|
|||
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):
|
||||
posts_ = UserPostSerializer(source='post', read_only=True)
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from django.urls import path, include
|
||||
from .views import FileViewSet, PTaskViewSet, PTaskResultViewSet, TaskList, \
|
||||
from .views import FileViewSet, PTaskViewSet, PTaskResultViewSet, PostRoleViewSet, TaskList, \
|
||||
UserPostViewSet, UserViewSet, DeptViewSet, \
|
||||
PermissionViewSet, RoleViewSet, PostViewSet, \
|
||||
DictTypeViewSet, DictViewSet
|
||||
|
@ -21,6 +21,7 @@ router.register('ptask_result', PTaskResultViewSet, basename="ptask_result")
|
|||
# router.register('qschedule', QScheduleViewSet, basename="qschedule")
|
||||
# router.register('qtask_result', QTaskResultViewSet, basename="qtask_result")
|
||||
router.register('user_post', UserPostViewSet, basename='user_post')
|
||||
router.register('post_role', PostRoleViewSet, basename='post_role')
|
||||
|
||||
router2 = routers.DefaultRouter()
|
||||
router2.register('file', FileViewSet, basename='file')
|
||||
|
|
|
@ -22,12 +22,12 @@ from apps.utils.permission import ALL_PERMS, get_user_perms_map
|
|||
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
|
||||
from server.celery import app as celery_app
|
||||
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)
|
||||
from .serializers import (DeptCreateUpdateSerializer, DeptSerializer, DictCreateUpdateSerializer,
|
||||
DictSerializer, DictTypeCreateUpdateSerializer, DictTypeSerializer,
|
||||
FileSerializer, PasswordChangeSerializer, PermissionCreateUpdateSerializer,
|
||||
PermissionSerializer, PostCreateUpdateSerializer, PostSerializer,
|
||||
PermissionSerializer, PostCreateUpdateSerializer, PostRoleCreateSerializer, PostRoleSerializer, PostSerializer,
|
||||
PTaskSerializer, PTaskCreateUpdateSerializer, PTaskResultSerializer,
|
||||
RoleCreateUpdateSerializer, RoleSerializer, TaskRunSerializer,
|
||||
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
|
||||
create_serializer_class = DeptCreateUpdateSerializer
|
||||
update_serializer_class = DeptCreateUpdateSerializer
|
||||
|
@ -327,6 +327,18 @@ class RoleViewSet(CustomModelViewSet):
|
|||
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):
|
||||
"""用户/岗位关系
|
||||
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
),
|
||||
]
|
|
@ -95,6 +95,14 @@ xxapis = {
|
|||
"ibeacon_list": {
|
||||
"url": "/api/devicesV3/ibeacons",
|
||||
"method": "post"
|
||||
},
|
||||
"rail_create": {
|
||||
"url": '/api/railsV2/add',
|
||||
"method": "post"
|
||||
},
|
||||
"rail_update": {
|
||||
"url": '/api/railsV2/edit',
|
||||
"method": "post"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ from rest_framework.exceptions import APIException, ParseError
|
|||
|
||||
from apps.utils.errors import DH_REQUEST_ERROR
|
||||
from apps.utils.tools import print_roundtrip
|
||||
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
|
||||
|
||||
|
@ -69,6 +70,8 @@ class DhClient:
|
|||
|
||||
def request(self, url: str, method: str, params=dict(), json=dict(), timeout=10,
|
||||
file_path_rela=None, raise_exception=True):
|
||||
if self is None:
|
||||
raise ParseError('大华对接未启用')
|
||||
if self.isGetingToken:
|
||||
req_num = 0
|
||||
while True:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from django.core.cache import cache
|
||||
from rest_framework.permissions import BasePermission
|
||||
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
|
||||
|
||||
ALL_PERMS = [
|
||||
|
@ -11,7 +11,7 @@ ALL_PERMS = [
|
|||
|
||||
def get_user_perms_map(user):
|
||||
"""
|
||||
获取权限字典,可用redis存取
|
||||
获取权限字典,可用redis存取(包括功能和数据权限)
|
||||
"""
|
||||
user_perms_map = {}
|
||||
if user.is_superuser:
|
||||
|
@ -20,16 +20,19 @@ def get_user_perms_map(user):
|
|||
objs = UserPost.objects.filter(user=user)
|
||||
for i in objs:
|
||||
dept_id = str(i.dept.id)
|
||||
if i.post.roles: # 岗位下有角色
|
||||
for perm in Permission.objects.filter(role__perms__in=i.post.roles):
|
||||
for pr in PostRole.objects.filter(post=i.post).exists():
|
||||
"""
|
||||
岗位角色
|
||||
"""
|
||||
for perm in Permission.objects.filter(role__perms=pr.role):
|
||||
if perm.codes:
|
||||
for code in perm.codes:
|
||||
if code in user_perms_map:
|
||||
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
|
||||
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)
|
||||
return user_perms_map
|
||||
|
||||
|
@ -109,20 +112,20 @@ class RbacDataMixin:
|
|||
new_queryset = queryset.none()
|
||||
for dept_id, data_range in user_perms_map[action_str].items:
|
||||
dept = Dept.objects.get(id=dept_id)
|
||||
if data_range == Post.POST_DATA_ALL:
|
||||
if data_range == DataFilter.ALL:
|
||||
return queryset
|
||||
elif data_range == Post.POST_DATA_SAMELEVE_AND_BELOW:
|
||||
elif data_range == DataFilter.SAMELEVE_AND_BELOW:
|
||||
if dept.parent:
|
||||
belong_depts = get_child_queryset2(dept.parent)
|
||||
else:
|
||||
belong_depts = get_child_queryset2(dept)
|
||||
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)
|
||||
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)
|
||||
elif data_range == Post.POST_DATA_THISLEVEL:
|
||||
elif data_range == DataFilter.MYSELF:
|
||||
queryset = queryset.filter(create_by=user)
|
||||
new_queryset = new_queryset | queryset
|
||||
return new_queryset
|
||||
|
|
|
@ -2,12 +2,12 @@ import time
|
|||
from threading import Thread
|
||||
|
||||
import requests
|
||||
# from django.conf import settings
|
||||
from server import settings
|
||||
from rest_framework.exceptions import APIException, ParseError
|
||||
from django.conf import settings
|
||||
|
||||
from apps.utils.errors import SP_REQUEST_ERROR
|
||||
from apps.utils.tools import print_roundtrip
|
||||
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
|
||||
|
||||
|
@ -69,6 +69,8 @@ class SpClient:
|
|||
|
||||
def request(self, url: str, method: str, params=dict(), json=dict(), timeout=10,
|
||||
file_path_rela=None, raise_exception=True):
|
||||
if self is None:
|
||||
raise ParseError('音响对接未启用')
|
||||
if self.isGetingToken:
|
||||
req_num = 0
|
||||
while True:
|
||||
|
|
|
@ -8,7 +8,7 @@ from rest_framework.permissions import IsAuthenticated
|
|||
from rest_framework.response import Response
|
||||
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.mixins import CustomDestoryModelMixin, MyLoggingMixin
|
||||
from apps.utils.permission import ALL_PERMS, RbacPermission, get_user_perms_map
|
||||
|
@ -78,20 +78,20 @@ class CustomGenericViewSet(MyLoggingMixin, GenericViewSet):
|
|||
new_queryset = queryset.none()
|
||||
for dept_id, data_range in user_perms_map[action_str].items:
|
||||
dept = Dept.objects.get(id=dept_id)
|
||||
if data_range == Post.POST_DATA_ALL:
|
||||
if data_range == DataFilter.ALL:
|
||||
return queryset
|
||||
elif data_range == Post.POST_DATA_SAMELEVE_AND_BELOW:
|
||||
elif data_range == DataFilter.SAMELEVE_AND_BELOW:
|
||||
if dept.parent:
|
||||
belong_depts = get_child_queryset2(dept.parent)
|
||||
else:
|
||||
belong_depts = get_child_queryset2(dept)
|
||||
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)
|
||||
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)
|
||||
elif data_range == Post.POST_DATA_THISLEVEL:
|
||||
elif data_range == DataFilter.MYSELF:
|
||||
queryset = queryset.filter(create_by=user)
|
||||
new_queryset = new_queryset | queryset
|
||||
return new_queryset
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import time
|
||||
from threading import Thread
|
||||
|
||||
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.tools import print_roundtrip
|
||||
from django.conf import settings
|
||||
import time
|
||||
from rest_framework.exceptions import APIException, ParseError
|
||||
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
|
||||
|
||||
|
@ -58,6 +61,8 @@ class XxClient:
|
|||
self.t.join()
|
||||
|
||||
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
|
||||
json['username'] = self.username
|
||||
json['buildId'] = settings.XX_BUILDID
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
|
@ -0,0 +1,7 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class VmConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.vm'
|
||||
verbose_name = "访客管理"
|
|
@ -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)
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
|
@ -0,0 +1,3 @@
|
|||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
|
@ -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.db import migrations, models
|
||||
|
@ -27,12 +27,14 @@ class Migration(migrations.Migration):
|
|||
('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='状态类型')),
|
||||
('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='参与者')),
|
||||
('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='分配方式')),
|
||||
('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='抄送给')),
|
||||
('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='创建人')),
|
||||
('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='工单数据')),
|
||||
('in_add_node', models.BooleanField(default=False, help_text='是否处于加签状态下', 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='当前处理人')),
|
||||
('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='全部处理的结果')),
|
||||
|
@ -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='条件表达式')),
|
||||
('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='是否校验必填项')),
|
||||
('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='创建人')),
|
||||
('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='源状态')),
|
||||
|
@ -120,7 +123,7 @@ class Migration(migrations.Migration):
|
|||
('update_time', models.DateTimeField(auto_now=True, 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='处理意见')),
|
||||
('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='处理人')),
|
||||
('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='干预类型')),
|
||||
|
@ -151,7 +154,7 @@ class Migration(migrations.Migration):
|
|||
('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='删除标记')),
|
||||
('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_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='排序')),
|
||||
|
@ -163,7 +166,9 @@ class Migration(migrations.Migration):
|
|||
('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='标签')),
|
||||
('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='创建人')),
|
||||
('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='最后编辑人')),
|
||||
('workflow', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wf.workflow', verbose_name='所属工作流')),
|
||||
],
|
||||
|
|
|
@ -43,12 +43,14 @@ class State(CommonAModel):
|
|||
PARTICIPANT_TYPE_FIELD = 7
|
||||
PARTICIPANT_TYPE_PARENT_FIELD = 8
|
||||
PARTICIPANT_TYPE_FORMCODE = 9
|
||||
PARTICIPANT_TYPE_POST = 10
|
||||
state_participanttype_choices = (
|
||||
(0, '无处理人'),
|
||||
(PARTICIPANT_TYPE_PERSONAL, '个人'),
|
||||
(PARTICIPANT_TYPE_MULTI, '多人'),
|
||||
# (PARTICIPANT_TYPE_DEPT, '部门'),
|
||||
(PARTICIPANT_TYPE_DEPT, '部门'),
|
||||
(PARTICIPANT_TYPE_ROLE, '角色'),
|
||||
(PARTICIPANT_TYPE_POST, '岗位'),
|
||||
# (PARTICIPANT_TYPE_VARIABLE, '变量'),
|
||||
(PARTICIPANT_TYPE_ROBOT, '脚本'),
|
||||
(PARTICIPANT_TYPE_FIELD, '工单的字段'),
|
||||
|
@ -83,6 +85,7 @@ class State(CommonAModel):
|
|||
type = models.IntegerField('状态类型', default=0, choices=type_choices,
|
||||
help_text='0.普通类型 1.初始状态(用于新建工单时,获取对应的字段必填及transition信息) 2.结束状态(此状态下的工单不得再处理,即没有对应的transition)')
|
||||
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,
|
||||
help_text='0.无处理人,1.个人,2.多人,3.部门,4.角色,5.变量(支持工单创建人,创建人的leader),6.脚本,7.工单的字段内容(如表单中的"测试负责人",需要为用户名或者逗号隔开的多个用户名),8.父工单的字段内容。 初始状态请选择类型5,参与人填create_by')
|
||||
participant = models.JSONField('参与者', default=list, blank=True,
|
||||
|
@ -94,6 +97,7 @@ class State(CommonAModel):
|
|||
help_text='1.主动接单(如果当前处理人实际为多人的时候,需要先接单才能处理) 2.直接处理(即使当前处理人实际为多人,也可以直接处理) 3.随机分配(如果实际为多人,则系统会随机分配给其中一个人) 4.全部处理(要求所有参与人都要处理一遍,才能进入下一步)')
|
||||
filter_policy = models.IntegerField('参与人过滤策略', default=0, choices=state_filter_choices)
|
||||
participant_cc = models.JSONField('抄送给', default=list, blank=True, help_text='抄送给(userid列表)')
|
||||
on_reach_func = models.CharField('到达时调用方法', max_length=100, null=True, blank=True)
|
||||
|
||||
|
||||
class Transition(CommonAModel):
|
||||
|
@ -148,6 +152,7 @@ class Transition(CommonAModel):
|
|||
'属性类型', default=1, choices=attribute_type_choices, help_text='属性类型,1.同意,2.拒绝,3.其他')
|
||||
field_require_check = models.BooleanField(
|
||||
'是否校验必填项', default=True, help_text='默认在用户点击操作的时候需要校验工单表单的必填项,如果设置为否则不检查。用于如"退回"属性的操作,不需要填写表单内容')
|
||||
on_submit_func = models.CharField('提交操作调用方法', max_length=100, null=True, blank=True)
|
||||
|
||||
|
||||
class CustomField(CommonAModel):
|
||||
|
@ -168,7 +173,8 @@ class CustomField(CommonAModel):
|
|||
('select_dg', '弹框单选'),
|
||||
('select_dgs', '弹框多选'),
|
||||
('textarea', '文本域'),
|
||||
('file', '附件')
|
||||
('file', '附件'),
|
||||
('table', '表格')
|
||||
)
|
||||
workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='所属工作流')
|
||||
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用于获取用户作为选项')
|
||||
# hook = models.CharField('hook', max_length=1000, default='', 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):
|
||||
|
|
|
@ -26,7 +26,7 @@ class WorkflowSimpleSerializer(serializers.ModelSerializer):
|
|||
class StateSimpleSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = State
|
||||
fields = ['id', 'name', 'type', 'distribute_type', 'enable_retreat']
|
||||
fields = ['id', 'name', 'type', 'distribute_type', 'enable_retreat', 'enable_deliver']
|
||||
|
||||
|
||||
class TransitionSerializer(serializers.ModelSerializer):
|
||||
|
@ -199,6 +199,11 @@ class TicketHandleSerializer(serializers.Serializer):
|
|||
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):
|
||||
suggestion = serializers.CharField(label="撤回原因", required=False)
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import importlib
|
||||
from apps.wf.serializers import TicketSimpleSerializer
|
||||
from apps.system.models import User
|
||||
from apps.wf.models import CustomField, State, Ticket, TicketFlow, Transition, Workflow
|
||||
|
@ -160,8 +161,14 @@ class WfService(object):
|
|||
in new_ticket_data else Ticket.ticket_data.get(destination_participant, 0)
|
||||
|
||||
elif destination_participant_type == State.PARTICIPANT_TYPE_FORMCODE: # 代码获取
|
||||
destination_participant = getattr(GetParticipants, destination_participant)(
|
||||
state=state, ticket=ticket, new_ticket_data=new_ticket_data, hander=handler)
|
||||
# if '.' in destination_participant:
|
||||
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: # 部门
|
||||
destination_participant = list(User.objects.filter(
|
||||
|
@ -269,6 +276,13 @@ class WfService(object):
|
|||
source_state = ticket.state
|
||||
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: # 没有处理人意味着系统触发不校验处理权限
|
||||
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=None, participant_cc=destination_state.participant_cc)
|
||||
|
||||
# 如果目标状态是脚本则执行
|
||||
if destination_state.participant_type == State.PARTICIPANT_TYPE_ROBOT:
|
||||
getattr(HandleScripts, destination_state.participant)(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)
|
||||
|
|
|
@ -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
|
||||
|
|
@ -8,7 +8,7 @@ from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModel
|
|||
RetrieveModelMixin, UpdateModelMixin
|
||||
from apps.wf.serializers import CustomFieldCreateUpdateSerializer, CustomFieldSerializer, StateSerializer, \
|
||||
TicketAddNodeEndSerializer, TicketAddNodeSerializer, TicketCloseSerializer, \
|
||||
TicketCreateSerializer, TicketDestorySerializer, TicketFlowSerializer, \
|
||||
TicketCreateSerializer, TicketDeliverSerializer, TicketDestorySerializer, TicketFlowSerializer, \
|
||||
TicketHandleSerializer, TicketRetreatSerializer, \
|
||||
TicketSerializer, TransitionSerializer, WorkflowSerializer, \
|
||||
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.utils.mixins import CreateUpdateCustomMixin, CreateUpdateModelAMixin
|
||||
from apps.wf.services import WfService
|
||||
from rest_framework.exceptions import APIException
|
||||
from rest_framework.exceptions import ParseError
|
||||
from rest_framework import status
|
||||
from django.db.models import Count
|
||||
from .scripts import GetParticipants
|
||||
|
@ -148,14 +148,15 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
return TicketListSerializer
|
||||
elif self.action == 'retrieve':
|
||||
return TicketDetailSerializer
|
||||
elif self.action == 'deliver':
|
||||
return TicketDeliverSerializer
|
||||
return super().get_serializer_class()
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
if not self.detail and not self.request.query_params.get('category', None):
|
||||
raise APIException('请指定查询分类')
|
||||
raise ParseError('请指定查询分类')
|
||||
return super().filter_queryset(queryset)
|
||||
|
||||
@transaction.atomic
|
||||
def create(self, request, *args, **kwargs):
|
||||
"""
|
||||
新建工单
|
||||
|
@ -174,11 +175,11 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
for key, value in start_state.state_fields.items():
|
||||
if int(value) == State.STATE_FIELD_REQUIRED:
|
||||
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]
|
||||
elif int(value) == State.STATE_FIELD_OPTIONAL:
|
||||
save_ticket_data[key] = ticket_data[key]
|
||||
|
||||
with transaction.atomic():
|
||||
ticket = serializer.save(state=start_state,
|
||||
create_by=request.user,
|
||||
create_time=timezone.now(),
|
||||
|
@ -197,6 +198,7 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
ticket.save()
|
||||
ticket = WfService.handle_ticket(ticket=ticket, transition=transition, new_ticket_data=ticket_data,
|
||||
handler=request.user, created=True)
|
||||
WfService.task_ticket(ticket)
|
||||
return Response(TicketSerializer(instance=ticket).data)
|
||||
|
||||
@action(methods=['get'], detail=False, perms_map={'get': '*'})
|
||||
|
@ -212,7 +214,6 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
return Response(ret)
|
||||
|
||||
@action(methods=['post'], detail=True, perms_map={'post': '*'})
|
||||
@transaction.atomic
|
||||
def handle(self, request, pk=None):
|
||||
"""
|
||||
处理工单
|
||||
|
@ -223,12 +224,37 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
vdata = serializer.validated_data
|
||||
new_ticket_data = ticket.ticket_data
|
||||
new_ticket_data.update(**vdata['ticket_data'])
|
||||
|
||||
with transaction.atomic():
|
||||
ticket = WfService.handle_ticket(ticket=ticket, transition=vdata['transition'],
|
||||
new_ticket_data=new_ticket_data, handler=request.user,
|
||||
suggestion=vdata['suggestion'])
|
||||
WfService.task_ticket(ticket)
|
||||
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': '*'})
|
||||
def flowsteps(self, request, pk=None):
|
||||
"""
|
||||
|
@ -277,7 +303,7 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
participant=request.user, transition=None)
|
||||
return Response()
|
||||
else:
|
||||
raise APIException('无需接单')
|
||||
raise ParseError('无需接单')
|
||||
|
||||
@action(methods=['post'], detail=True, perms_map={'post': '*'})
|
||||
def retreat(self, request, pk=None):
|
||||
|
@ -286,9 +312,9 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
"""
|
||||
ticket = self.get_object()
|
||||
if ticket.create_by != request.user:
|
||||
raise APIException('非创建人不可撤回')
|
||||
raise ParseError('非创建人不可撤回')
|
||||
if not ticket.state.enable_retreat:
|
||||
raise APIException('该状态不可撤回')
|
||||
raise ParseError('该状态不可撤回')
|
||||
start_state = WfService.get_workflow_start_state(ticket.workflow)
|
||||
ticket.state = start_state
|
||||
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,
|
||||
intervene_type=Transition.TRANSITION_INTERVENE_TYPE_ADD_NODE,
|
||||
participant=request.user, transition=None)
|
||||
WfService.task_ticket(ticket)
|
||||
return Response()
|
||||
|
||||
@action(methods=['post'], detail=True, perms_map={'post': '*'}, serializer_class=TicketAddNodeEndSerializer)
|
||||
|
|
|
@ -5,7 +5,7 @@ from celery import Celery
|
|||
# set the default Django settings module for the 'celery' program.
|
||||
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
|
||||
# the configuration object to child processes.
|
||||
|
|
|
@ -55,7 +55,9 @@ INSTALLED_APPS = [
|
|||
'apps.monitor',
|
||||
'apps.wf',
|
||||
'apps.hrm',
|
||||
'apps.am'
|
||||
'apps.am',
|
||||
'apps.vm',
|
||||
'apps.rpm'
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
@ -141,10 +143,10 @@ USE_TZ = True
|
|||
# https://docs.djangoproject.com/en/3.0/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, 'dist/static_collect')
|
||||
STATICFILES_DIRS = (
|
||||
os.path.join(BASE_DIR, 'dist/static'),
|
||||
)
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, 'dist/static')
|
||||
# STATICFILES_DIRS = (
|
||||
# os.path.join(BASE_DIR, 'dist/static'),
|
||||
# )
|
||||
|
||||
MEDIA_URL = '/media/'
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||
|
@ -224,7 +226,7 @@ AUTHENTICATION_BACKENDS = (
|
|||
# }
|
||||
|
||||
# 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个任务就会被销毁,可防止内存泄露
|
||||
CELERY_TIMEZONE = 'Asia/Shanghai' # 设置时区
|
||||
CELERY_ENABLE_UTC = True # 启动时区设置
|
||||
|
|
|
@ -31,6 +31,7 @@ schema_view = get_schema_view(
|
|||
),
|
||||
public=True,
|
||||
permission_classes=[],
|
||||
url=settings.BASE_URL
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
|
|
Loading…
Reference in New Issue