You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
566 lines
26 KiB
566 lines
26 KiB
|
10 months ago
|
import datetime
|
||
|
|
import logging
|
||
|
|
import os
|
||
|
|
import traceback
|
||
|
|
from collections import Counter
|
||
|
|
|
||
|
|
import xlrd3
|
||
|
|
from django.apps import apps
|
||
|
|
from django.core.exceptions import ObjectDoesNotExist
|
||
|
|
from django.db import transaction
|
||
|
|
from django.db.models import Q, Count
|
||
|
|
from django.http import FileResponse
|
||
|
|
from django_filters.rest_framework import DjangoFilterBackend
|
||
|
|
from rest_framework.decorators import action
|
||
|
|
from rest_framework.filters import SearchFilter, OrderingFilter
|
||
|
|
from rest_framework.generics import get_object_or_404
|
||
|
|
from rest_framework.permissions import IsAuthenticated
|
||
|
|
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
|
||
|
|
|
||
|
|
from ChaCeRndTrans.basic import CCAIResponse
|
||
|
|
from ChaCeRndTrans.code import SERVER_ERROR, BAD, OK, NOT_FOUND
|
||
|
|
from ChaCeRndTrans.settings import MEDIA_ROOT, SHOW_UPLOAD_PATH, FILE_PATH
|
||
|
|
from rbac.models import Company
|
||
|
|
from staff.models import Staff, Dept
|
||
|
|
from staff.serializers.Serializer import DeptSerializer, DeptModifySerializer
|
||
|
|
from utils.custom import CustomViewBase, RbacPermission, CommonPagination, req_operate_by_user, asyncDeleteFile, \
|
||
|
|
MyCustomError
|
||
|
|
|
||
|
|
logger = logging.getLogger('error')
|
||
|
|
|
||
|
|
|
||
|
|
class DeptCustomView(CustomViewBase):
|
||
|
|
'''
|
||
|
|
部门管理,增删改查
|
||
|
|
'''
|
||
|
|
perms_map = (
|
||
|
|
{"*": "admin"}, {"*": "comadmin"}, {"*": "dept_all"}, {"get": "dept_list"},
|
||
|
|
{"post": "dept_create"},
|
||
|
|
{"put": "dept_edit"}, {"delete": "dept_delete"}
|
||
|
|
)
|
||
|
|
queryset = Dept.objects.all()
|
||
|
|
serializer_class = DeptSerializer
|
||
|
|
pagination_class = CommonPagination
|
||
|
|
filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter)
|
||
|
|
search_fields = ("name",)
|
||
|
|
ordering_fields = ("id",)
|
||
|
|
authentication_classes = (JSONWebTokenAuthentication,)
|
||
|
|
permission_classes = (RbacPermission,)
|
||
|
|
|
||
|
|
# 关联部门的模型
|
||
|
|
modelNameList = {'Equipment': "设备", 'Building': "建筑物", 'Inassets': "无形资产", 'Longterm': "长期待摊", 'Rentequip': "租赁设备", 'Rentbuild': "租赁建筑"}
|
||
|
|
remark = [{'app': 'asset', 'model': modelName, 'info': info} for modelName, info in modelNameList.items()]
|
||
|
|
remark.append({'app': 'staff', 'model': 'Staff', 'info': '员工'})
|
||
|
|
|
||
|
|
def get_serializer_class(self):
|
||
|
|
# 根据请求类型动态变更serializer
|
||
|
|
if self.action == "create":
|
||
|
|
return DeptSerializer
|
||
|
|
elif self.action == "list":
|
||
|
|
return DeptSerializer
|
||
|
|
return DeptModifySerializer
|
||
|
|
|
||
|
|
def get_queryset(self):
|
||
|
|
# query = self.request.query_params.get('q', None) # q为实际的搜索词
|
||
|
|
# field = self.request.query_params.get('field', None) # field为具体字段
|
||
|
|
companyMid = self.request.query_params.get('companyMid', None) # field为具体字段
|
||
|
|
|
||
|
|
queryset = Dept.objects.all()
|
||
|
|
|
||
|
|
# if query and field:
|
||
|
|
# # 根据传递的字段决定搜索的字段
|
||
|
|
# if field == 'subjectCode':
|
||
|
|
# queryset = queryset.filter(subjectCode__icontains=query)
|
||
|
|
# elif field == 'subjectName':
|
||
|
|
# queryset = queryset.filter(subjectName__icontains=query)
|
||
|
|
if companyMid:
|
||
|
|
queryset = queryset.filter(companyMid=companyMid)
|
||
|
|
return queryset
|
||
|
|
|
||
|
|
def list(self, request, format=None):
|
||
|
|
"""
|
||
|
|
列表
|
||
|
|
"""
|
||
|
|
pagination = {}
|
||
|
|
try:
|
||
|
|
params = request.GET
|
||
|
|
keyword = params.get('keyword') # 关键词
|
||
|
|
is_rnd = params.get('is_rnd') # 是否研发部
|
||
|
|
|
||
|
|
companyMid = params.get('companyMid')
|
||
|
|
page_size = params.get('size')
|
||
|
|
page = params.get('page')
|
||
|
|
sort = params.get('sort')
|
||
|
|
param = []
|
||
|
|
order_by = 'N.id desc'
|
||
|
|
if sort:
|
||
|
|
order_by = sort
|
||
|
|
if page is None:
|
||
|
|
page = 1
|
||
|
|
if page_size is None:
|
||
|
|
page_size = 10
|
||
|
|
start_index = (int(page) - 1) * int(page_size)
|
||
|
|
|
||
|
|
where = 'WHERE 1=1 '
|
||
|
|
if not companyMid:
|
||
|
|
return CCAIResponse('公司信息缺失', BAD)
|
||
|
|
if companyMid:
|
||
|
|
where += 'AND N.companyMid = %s '
|
||
|
|
param.append(companyMid)
|
||
|
|
if is_rnd:
|
||
|
|
where += 'AND N.is_rnd = %s '
|
||
|
|
param.append(is_rnd)
|
||
|
|
if keyword:
|
||
|
|
where += 'AND N.name like %s '
|
||
|
|
param.append('%' + keyword + '%')
|
||
|
|
|
||
|
|
sql = 'SELECT N.id, name, pid_id, (SELECT name from chace_rnd.staff_dept where id = N.pid_id) AS parentName, ' \
|
||
|
|
'(SELECT COUNT(id) FROM chace_rnd.staff_staff where dept = N.id) AS count, is_rnd, ' \
|
||
|
|
'CreateBy, UpdateBy, CreateByUid, UpdateByUid, CreateDateTime, UpdateDateTime, companyMid ' \
|
||
|
|
'FROM chace_rnd.staff_dept AS N ' \
|
||
|
|
'%s ORDER BY %s LIMIT %s,%s ' % (where, order_by, start_index, page_size)
|
||
|
|
query_rows = Dept.objects.raw(sql, param)
|
||
|
|
|
||
|
|
count_sql = """ select N.id, count(N.id) as count
|
||
|
|
from chace_rnd.staff_dept AS N %s """ % where
|
||
|
|
count_result = Dept.objects.raw(count_sql, param)
|
||
|
|
|
||
|
|
count = 0
|
||
|
|
if len(count_result) > 0:
|
||
|
|
count = count_result[0].count
|
||
|
|
rows = []
|
||
|
|
for item in query_rows:
|
||
|
|
item.__dict__.pop('_state')
|
||
|
|
item.__dict__['CreateDateTime'] = item.__dict__['CreateDateTime'].strftime('%Y-%m-%d')
|
||
|
|
item.__dict__['UpdateDateTime'] = item.__dict__['UpdateDateTime'].strftime('%Y-%m-%d')
|
||
|
|
rows.append(item.__dict__)
|
||
|
|
|
||
|
|
pagination = {
|
||
|
|
"page": page,
|
||
|
|
"page_size": page_size
|
||
|
|
}
|
||
|
|
|
||
|
|
return CCAIResponse(rows, count=count, pagination=pagination)
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("user: %s, get dept list failed: \n%s" % (request.user.id, traceback.format_exc()))
|
||
|
|
return CCAIResponse("获取部门管理列表失败", SERVER_ERROR)
|
||
|
|
|
||
|
|
def create(self, request, *args, **kwargs):
|
||
|
|
try:
|
||
|
|
data = req_operate_by_user(request)
|
||
|
|
companyMid = request.query_params.get('companyMid', None)
|
||
|
|
if not companyMid:
|
||
|
|
return CCAIResponse("Missing company information", BAD)
|
||
|
|
is_com_exist = Company.objects.filter(MainId=companyMid).exists()
|
||
|
|
if is_com_exist:
|
||
|
|
data['companyMid'] = companyMid
|
||
|
|
serializer = self.get_serializer(data=data)
|
||
|
|
serializer.is_valid(raise_exception=True)
|
||
|
|
self.perform_create(serializer)
|
||
|
|
# headers = self.get_success_headers(serializer.data)
|
||
|
|
return CCAIResponse(data="success")
|
||
|
|
else:
|
||
|
|
return CCAIResponse("Company information error", BAD)
|
||
|
|
except MyCustomError as e:
|
||
|
|
return CCAIResponse(e.message, BAD)
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("user: %s, staff create failed: \n%s" % (request.user.id, traceback.format_exc()))
|
||
|
|
return CCAIResponse("create failed", SERVER_ERROR)
|
||
|
|
|
||
|
|
def update(self, request, *args, **kwargs):
|
||
|
|
from rest_framework.exceptions import ValidationError
|
||
|
|
try:
|
||
|
|
companyMid = request.query_params.get('companyMid', None)
|
||
|
|
data = req_operate_by_user(request)
|
||
|
|
data['companyMid'] = companyMid
|
||
|
|
partial = kwargs.pop('partial', False) # True:所有字段全部更新, False:仅更新提供的字段
|
||
|
|
instance = self.get_object()
|
||
|
|
|
||
|
|
children_ids = get_all_children(instance.id)
|
||
|
|
param_pid = data.get('pid')
|
||
|
|
if param_pid and (param_pid in children_ids or param_pid == instance.id):
|
||
|
|
return CCAIResponse("不可选自身与自身的下级部门作为上级部门", BAD)
|
||
|
|
|
||
|
|
serializer = self.get_serializer(instance, data=data, partial=partial)
|
||
|
|
serializer.is_valid(raise_exception=True)
|
||
|
|
self.perform_update(serializer)
|
||
|
|
|
||
|
|
if getattr(instance, '_prefetched_objects_cache', None):
|
||
|
|
# If 'prefetch_related' has been applied to a queryset, we need to
|
||
|
|
# forcibly invalidate the prefetch cache on the instance.
|
||
|
|
instance._prefetched_objects_cache = {}
|
||
|
|
return CCAIResponse(data="success")
|
||
|
|
except ValidationError as e:
|
||
|
|
logger.error(
|
||
|
|
"user: %s, update dept cause:can not set self pid \n%s" % (request.user.id, traceback.format_exc()))
|
||
|
|
return CCAIResponse("can not set self as parent", BAD)
|
||
|
|
except MyCustomError as e:
|
||
|
|
return CCAIResponse(e.message, BAD)
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("user: %s, update dept failed: \n%s" % (request.user.id, traceback.format_exc()))
|
||
|
|
return CCAIResponse("update dept failed", SERVER_ERROR)
|
||
|
|
|
||
|
|
def destroy(self, request, *args, **kwargs):
|
||
|
|
try:
|
||
|
|
instance = self.get_object()
|
||
|
|
# 查询是否存在关联
|
||
|
|
is_association = self.check_association(deptId=instance.id)
|
||
|
|
if is_association:
|
||
|
|
return CCAIResponse(is_association, BAD)
|
||
|
|
self.perform_destroy(instance)
|
||
|
|
return CCAIResponse(data="delete resource success")
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("user: %s, delete dept failed: \n%s" % (request.user.id, traceback.format_exc()))
|
||
|
|
return CCAIResponse("delete dept failed", SERVER_ERROR)
|
||
|
|
|
||
|
|
def check_association(self, deptId):
|
||
|
|
"""
|
||
|
|
检查部门关联的状态,遍历查询所有与部门关联的表
|
||
|
|
return False-不存在关联 True-存在关联
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
isExist = False # 不存在关联
|
||
|
|
info = None
|
||
|
|
code = None # 存在关联的人员或设备的编码
|
||
|
|
for item in self.remark: # 遍历查询是否存在关联,一旦存在关联就不给删除
|
||
|
|
Model = apps.get_model(app_label=item.get('app'), model_name=item.get('model'))
|
||
|
|
if not isExist:
|
||
|
|
if 'staff' == item.get('app'):
|
||
|
|
isExist = Model.objects.filter(dept=deptId).first()
|
||
|
|
else:
|
||
|
|
isExist = Model.objects.filter(departmentId=deptId).first()
|
||
|
|
|
||
|
|
if isExist:
|
||
|
|
info = item.get('info')
|
||
|
|
code = getattr(isExist, 'code' if 'staff' != item.get('app') else 'idCode')
|
||
|
|
else:
|
||
|
|
break
|
||
|
|
if isExist:
|
||
|
|
return f'{info}:{code} 与部门存在关联!'
|
||
|
|
else:
|
||
|
|
return False
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("check staff association failed: \n%s" % traceback.format_exc())
|
||
|
|
raise Exception('检查员工关联状态失败')
|
||
|
|
|
||
|
|
@action(methods=['delete'], detail=False)
|
||
|
|
def multiple_delete(self, request, *args, **kwargs):
|
||
|
|
try:
|
||
|
|
delete_id = request.query_params.get('ids', None)
|
||
|
|
if not delete_id:
|
||
|
|
return CCAIResponse("参数不对啊!", NOT_FOUND)
|
||
|
|
for i in delete_id.split(','):
|
||
|
|
# 查询是否存在关联
|
||
|
|
is_association = self.check_association(deptId=i)
|
||
|
|
if not is_association: # 不存在关联,才删除
|
||
|
|
get_object_or_404(self.queryset, pk=int(i)).delete()
|
||
|
|
else: # 存在关联,不删除,跳过
|
||
|
|
return CCAIResponse(is_association, BAD)
|
||
|
|
return CCAIResponse("批量删除成功", OK)
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("multiple delete dept failed: \n%s" % traceback.format_exc())
|
||
|
|
return CCAIResponse("批量删除失败", SERVER_ERROR)
|
||
|
|
|
||
|
|
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated],
|
||
|
|
url_name='download', url_path='download')
|
||
|
|
def download(self, request, *args, **kwargs):
|
||
|
|
"""导入模板下载"""
|
||
|
|
try:
|
||
|
|
file = open(MEDIA_ROOT + '/部门批量导入模板.xlsx', 'rb')
|
||
|
|
response = FileResponse(file)
|
||
|
|
response['Content-Type'] = 'application/octet-stream'
|
||
|
|
response['Content-Disposition'] = 'attachment;filename="部门批量导入模板.xlsx"'
|
||
|
|
return response
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("user: %s, get dept template file failed: \n%s" % (request.user.id, traceback.format_exc()))
|
||
|
|
return CCAIResponse("下载部门批量导入模板失败", SERVER_ERROR)
|
||
|
|
|
||
|
|
|
||
|
|
@action(methods=["post"], detail=False, permission_classes=[IsAuthenticated],
|
||
|
|
url_path="import_excel", url_name="import_excel")
|
||
|
|
def import_excel(self, request):
|
||
|
|
"""导入excel表数据"""
|
||
|
|
try:
|
||
|
|
params = request.data
|
||
|
|
companyMid = request.query_params.get('companyMid', None)
|
||
|
|
file_name = params.get('file_name')
|
||
|
|
|
||
|
|
if not companyMid or not file_name:
|
||
|
|
return CCAIResponse('Missing params', BAD)
|
||
|
|
|
||
|
|
file_paths = file_name.split(",")
|
||
|
|
if len(file_paths) >= 2:
|
||
|
|
return CCAIResponse("部门数据暂不支持同时多文件上传", BAD)
|
||
|
|
excel_file = file_paths[0].replace(SHOW_UPLOAD_PATH, FILE_PATH)
|
||
|
|
with xlrd3.open_workbook(excel_file) as data:
|
||
|
|
sheets = list(data.sheets())
|
||
|
|
if len(sheets) > 1:
|
||
|
|
return CCAIResponse("部门数据暂不支持同时多表格上传", BAD)
|
||
|
|
|
||
|
|
table = sheets[0] # 只读取第一张表
|
||
|
|
rows = table.nrows
|
||
|
|
check_value = table.row_values(0)
|
||
|
|
if (check_value[0] != '部门名称' and check_value[1] != '是否研发部门' and check_value[2] != '上级部门') or (len(check_value) != 3):
|
||
|
|
return CCAIResponse("文件不符合模板标准", SERVER_ERROR)
|
||
|
|
|
||
|
|
# 查询该公司已存在的部门记录
|
||
|
|
dept_tree_list = []
|
||
|
|
db_depts = self.get_queryset().order_by('pid')
|
||
|
|
deptId_dept_map = {d.id: d for d in db_depts}
|
||
|
|
db_depts_name = {x.name: deptid for deptid, x in deptId_dept_map.items()}
|
||
|
|
for d in db_depts:
|
||
|
|
if not d.pid:
|
||
|
|
dept_tree_list.append(d)
|
||
|
|
|
||
|
|
dept_col_data = [str(dname).strip() for dname in table.col_values(0)[1:] if dname and str(dname).strip()] # 获取部门列数据
|
||
|
|
deptCounter = Counter(dept_col_data)
|
||
|
|
error_dept_name_set = [item for item, count in deptCounter.items() if count > 1]
|
||
|
|
if error_dept_name_set:
|
||
|
|
return CCAIResponse(f"部门名称列存在重复{','.join(error_dept_name_set)}", BAD)
|
||
|
|
dept_parent_col_data = [str(pName).strip() for pName in table.col_values(2)[1:] if pName and str(pName).strip()] # 获取上级部门列数据
|
||
|
|
error_dept_name_set = set(db_depts_name.keys()) - (set(db_depts_name.keys()) - set(dept_col_data))
|
||
|
|
if error_dept_name_set:
|
||
|
|
return CCAIResponse(f"部门已存在:{','.join(error_dept_name_set)}", BAD)
|
||
|
|
error_dept_name_set = set(dept_parent_col_data) - (set(db_depts_name.keys()) | set(dept_col_data))
|
||
|
|
if error_dept_name_set:
|
||
|
|
return CCAIResponse(f"未知部门:{','.join(error_dept_name_set)}", BAD)
|
||
|
|
# 数据库事务
|
||
|
|
with transaction.atomic():
|
||
|
|
try:
|
||
|
|
for row in range(1, rows):
|
||
|
|
row_values = table.row_values(row) # 每一行的数据
|
||
|
|
|
||
|
|
# 校验必填项
|
||
|
|
if not row_values[0] or str(row_values[0]).strip() == '':
|
||
|
|
raise MyCustomError("部门名称必填!")
|
||
|
|
if len(str(row_values[0]).strip()) > 20:
|
||
|
|
raise MyCustomError("部门名称过长,请控制在20字符内!")
|
||
|
|
|
||
|
|
is_rnd = False
|
||
|
|
if row_values[1]:
|
||
|
|
is_rnd_str = str(row_values[1]).strip()
|
||
|
|
if is_rnd_str and is_rnd_str not in ['是', '否', '不是']:
|
||
|
|
raise MyCustomError(f"部门研发属性:{str(row_values[1])}错误,请在「'是', '否', '不是'」中选择!")
|
||
|
|
if '是' == is_rnd_str:
|
||
|
|
is_rnd = True
|
||
|
|
|
||
|
|
parent_dept_name = None
|
||
|
|
parent_id = None
|
||
|
|
if row_values[2] and str(row_values[2]).strip():
|
||
|
|
parent_dept_name = str(row_values[2]).strip()
|
||
|
|
if parent_dept_name:
|
||
|
|
if parent_dept_name == str(row_values[0]).strip():
|
||
|
|
raise MyCustomError(f"部门:{parent_dept_name},不可以以自身为上级")
|
||
|
|
parent_id = db_depts_name.get(parent_dept_name)
|
||
|
|
|
||
|
|
dept = Dept()
|
||
|
|
dept.name = str(row_values[0]).strip()
|
||
|
|
dept.is_rnd = is_rnd
|
||
|
|
dept.CreateBy = request.user.name
|
||
|
|
dept.CreateByUid = request.user.id
|
||
|
|
dept.CreateDateTime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||
|
|
dept.companyMid = companyMid
|
||
|
|
if parent_dept_name:
|
||
|
|
dept.parent_dept_name = parent_dept_name
|
||
|
|
if parent_id:
|
||
|
|
dept.pid = deptId_dept_map.get(parent_id)
|
||
|
|
dept.save()
|
||
|
|
deptId_dept_map[dept.id] = dept
|
||
|
|
db_depts_name[dept.name] = dept.id
|
||
|
|
if not parent_dept_name:
|
||
|
|
dept_tree_list.append(dept)
|
||
|
|
|
||
|
|
# 查看是否存在未设置pid的部门
|
||
|
|
for deptId, d in deptId_dept_map.items():
|
||
|
|
if hasattr(d, 'parent_dept_name') and not d.pid:
|
||
|
|
# if d.pid.id not in deptId_dept_map:
|
||
|
|
if d.parent_dept_name not in db_depts_name:
|
||
|
|
raise MyCustomError("数据有误!")
|
||
|
|
d.pid = deptId_dept_map.get(db_depts_name.get(d.parent_dept_name))
|
||
|
|
d.save()
|
||
|
|
if check_circular_association(request, list(deptId_dept_map.values())):
|
||
|
|
raise MyCustomError("存在循环上下级关系!")
|
||
|
|
except MyCustomError as e:
|
||
|
|
transaction.set_rollback(True)
|
||
|
|
return CCAIResponse(e.message, BAD)
|
||
|
|
# 异步删除文件
|
||
|
|
try:
|
||
|
|
if os.path.exists(MEDIA_ROOT + '/' + excel_file): # 如果文件存在
|
||
|
|
# 删除文件,可使用以下两种方法。
|
||
|
|
os.remove(MEDIA_ROOT + '/' + excel_file)
|
||
|
|
except Exception as e:
|
||
|
|
logger.error(
|
||
|
|
"user: %s, export ccw user xlsx file failed: \n%s" % (request.user.id, traceback.format_exc()))
|
||
|
|
|
||
|
|
return CCAIResponse(data="success")
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("user: %s, import dept file failed: \n%s" % (request.user.id, traceback.format_exc()))
|
||
|
|
return CCAIResponse("导入部门记录失败", SERVER_ERROR)
|
||
|
|
|
||
|
|
@classmethod
|
||
|
|
def changeCount(cls, method, deptId):
|
||
|
|
"""
|
||
|
|
改变部门关联数量
|
||
|
|
method: 1-新增关联 2-取消关联
|
||
|
|
return: True操作成功 False操作失败
|
||
|
|
"""
|
||
|
|
if not deptId or not method:
|
||
|
|
return False
|
||
|
|
try:
|
||
|
|
dept = Dept.objects.get(id=deptId)
|
||
|
|
except Dept.DoesNotExist:
|
||
|
|
logger.error("dept do not exist: \n%s" % (traceback.format_exc(),))
|
||
|
|
return False
|
||
|
|
else:
|
||
|
|
try:
|
||
|
|
if 1 == method: # 新增关联
|
||
|
|
dept.relCount += 1
|
||
|
|
elif 2 == method:
|
||
|
|
if dept.relCount > 0: # 大于0才取消关联
|
||
|
|
dept.relCount -= 1
|
||
|
|
else:
|
||
|
|
return False
|
||
|
|
dept.save()
|
||
|
|
return True
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("change dept relCount failed: \n%s" % (traceback.format_exc(),))
|
||
|
|
return False
|
||
|
|
|
||
|
|
@action(methods=["get"], detail=False, permission_classes=[IsAuthenticated],
|
||
|
|
url_path="getDeptMap", url_name="getDeptMap")
|
||
|
|
def getDeptMap(self, request):
|
||
|
|
try:
|
||
|
|
params = request.GET
|
||
|
|
companyMid = params.get('companyMid')
|
||
|
|
if not companyMid:
|
||
|
|
return CCAIResponse("Missing company information", BAD)
|
||
|
|
if companyMid:
|
||
|
|
query = Q(companyMid=companyMid)
|
||
|
|
dept = Dept.objects.filter(query).values('id', 'name')
|
||
|
|
return CCAIResponse(dept)
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("user: %s, getDeptMap failed: \n%s" % (request.user.id, traceback.format_exc()))
|
||
|
|
return CCAIResponse("getDeptMap failed", SERVER_ERROR)
|
||
|
|
|
||
|
|
@action(methods=["get"], detail=False, permission_classes=[IsAuthenticated],
|
||
|
|
url_path="tree", url_name="tree")
|
||
|
|
def tree(self, request, format=None):
|
||
|
|
"""
|
||
|
|
树状列表
|
||
|
|
"""
|
||
|
|
params = request.GET
|
||
|
|
companyMid = params.get('companyMid')
|
||
|
|
if not companyMid:
|
||
|
|
return CCAIResponse("Missing company information", BAD)
|
||
|
|
# 统计员工表不同部门人数
|
||
|
|
sql = 'SELECT id, dept, COUNT(id) AS count FROM chace_rnd.staff_staff WHERE companyMid = %s and status = 0 GROUP BY dept '
|
||
|
|
result = Staff.objects.raw(sql, [companyMid])
|
||
|
|
# result = Staff.objects.filter(companyMid=companyMid, status=0).values('dept').annotate(count=Count('id'))
|
||
|
|
deptCountDict = {item.dept: item.count for item in result}
|
||
|
|
|
||
|
|
queryset = self.filter_queryset(self.get_queryset())
|
||
|
|
page = self.paginate_queryset(queryset)
|
||
|
|
serializer = self.get_serializer(queryset, many=True)
|
||
|
|
tree_dict = {}
|
||
|
|
tree_data = []
|
||
|
|
try:
|
||
|
|
# 初始赋值字典与count
|
||
|
|
for item in serializer.data:
|
||
|
|
if item['id'] in deptCountDict:
|
||
|
|
item['count'] = deptCountDict[item['id']]
|
||
|
|
else:
|
||
|
|
item['count'] = 0
|
||
|
|
tree_dict[item["id"]] = item
|
||
|
|
# 设置树状结构
|
||
|
|
for i in tree_dict:
|
||
|
|
if tree_dict[i]["pid"]:
|
||
|
|
pid = tree_dict[i]["pid"]
|
||
|
|
parent = tree_dict[pid]
|
||
|
|
parent.setdefault("children", []).append(tree_dict[i])
|
||
|
|
else:
|
||
|
|
tree_data.append(tree_dict[i])
|
||
|
|
# 统计部门人数 1:递归 2:多层遍历
|
||
|
|
for dept in tree_data: # 第一层部门
|
||
|
|
if 'children' in dept: # 若存在下级部门,调用方法递归累加最终数量
|
||
|
|
self.getCountFromTree(dept)
|
||
|
|
|
||
|
|
results = tree_data
|
||
|
|
except KeyError:
|
||
|
|
results = serializer.data
|
||
|
|
if page is not None:
|
||
|
|
return self.get_paginated_response(results)
|
||
|
|
return CCAIResponse(results)
|
||
|
|
|
||
|
|
def getCountFromTree(self, parentDept):
|
||
|
|
"""
|
||
|
|
(递归)设置并返回当前本部门的下级部门的总人数
|
||
|
|
"""
|
||
|
|
sum = 0 # 子部门人数统计
|
||
|
|
for dept in parentDept['children']:
|
||
|
|
if 'children' in dept: # 存在下级部门,递归调用,设置当前部门人数,并返回上级,本部门人数
|
||
|
|
deptCount = self.getCountFromTree(dept)
|
||
|
|
sum += deptCount
|
||
|
|
else: # 最底层部门不需要设置,只需要累计到上层
|
||
|
|
sum += dept['count']
|
||
|
|
parentDept['count'] += sum # 本部门 + 子级部门
|
||
|
|
return parentDept['count']
|
||
|
|
|
||
|
|
# def getCountFromTree(self, parentDept):
|
||
|
|
# """
|
||
|
|
# (模拟栈)设置并返回当前本部门的下级部门的总人数
|
||
|
|
# """
|
||
|
|
# stack = [parentDept] # 使用栈来模拟递归的过程
|
||
|
|
# # results = [] # 用于保存每次调用的结果
|
||
|
|
# while stack:
|
||
|
|
# parentDept = stack.pop()
|
||
|
|
# sum = 0 # 子部门人数统计
|
||
|
|
# for dept in parentDept['children']:
|
||
|
|
# if 'children' in dept: # 存在下级部门,递归调用,设置当前部门人数,并返回上级,本部门人数
|
||
|
|
# stack.append(dept)
|
||
|
|
# # deptCount = self.getCountFromTree(dept)
|
||
|
|
# # sum += deptCount
|
||
|
|
# else: # 最底层部门不需要设置,只需要累计到上层
|
||
|
|
# sum += dept['count']
|
||
|
|
# parentDept['count'] += sum # 本部门 + 子级部门
|
||
|
|
# # results.append(parentDept['count'])
|
||
|
|
|
||
|
|
|
||
|
|
def get_all_children(deptId, max_depth=50, current_depth=0):
|
||
|
|
# 如果设置了最大深度并且当前深度已经达到或超过最大深度,则返回空列表
|
||
|
|
if current_depth >= max_depth:
|
||
|
|
return []
|
||
|
|
|
||
|
|
children = []
|
||
|
|
sub_depts = Dept.objects.filter(pid=deptId)
|
||
|
|
for sub_dept in sub_depts:
|
||
|
|
children.append(sub_dept.id)
|
||
|
|
# 在递归调用时增加当前深度
|
||
|
|
children.extend(get_all_children(sub_dept.id, max_depth, current_depth + 1))
|
||
|
|
return children
|
||
|
|
|
||
|
|
def check_circular_association(request, dept_list):
|
||
|
|
"""
|
||
|
|
检查部门循环上下级, 存在循环问题会返回True,否则返回False
|
||
|
|
deptId_dept_map: [ Node1, Node2, ... ]
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
if not dept_list or 0 == len(dept_list):
|
||
|
|
return False
|
||
|
|
|
||
|
|
for node in dept_list:
|
||
|
|
nodeList = [node, ]
|
||
|
|
n = 0
|
||
|
|
while node.pid:
|
||
|
|
n += 1
|
||
|
|
if n >= 50:
|
||
|
|
return True
|
||
|
|
if node.pid in nodeList:
|
||
|
|
return True
|
||
|
|
node = node.pid
|
||
|
|
nodeList.append(node)
|
||
|
|
|
||
|
|
return False
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("user: %s, check_circular_association failed: \n%s" % (request.user.id, traceback.format_exc()))
|
||
|
|
return CCAIResponse("check_circular_association failed", SERVER_ERROR)
|