809 lines
30 KiB
Python
Executable File
809 lines
30 KiB
Python
Executable File
|
|
from django.conf import settings
|
|
from django.db import transaction
|
|
from django.utils import timezone
|
|
from rest_framework import serializers
|
|
from rest_framework.decorators import action
|
|
from rest_framework.exceptions import ParseError
|
|
from rest_framework.mixins import ListModelMixin
|
|
from rest_framework.permissions import AllowAny, IsAuthenticated
|
|
from rest_framework.response import Response
|
|
|
|
from apps.hrm.errors import NO_NEED_LEVEL_REMARK
|
|
from apps.hrm.filters import (CertificateFilterSet, ClockRecordFilterSet, EmployeeFilterSet,
|
|
NotWorkRemarkFilterSet)
|
|
from apps.hrm.models import (Certificate, ClockRecord, Employee, NotWorkRemark, Attendance, Resignation,
|
|
EmpNeed, EmpJoin, Leave, EmployeeTransfer, Probation, EmpPersonInfo, EmpContract)
|
|
from apps.hrm.serializers import (CertificateCreateUpdateSerializer, CertificateSerializer, ChannelAuthoritySerializer, EmpJoinSerializer,
|
|
ClockRecordListSerializer,
|
|
EmployeeCreateUpdateSerializer, EmployeeDetailSerializer, EmployeeImproveSerializer,
|
|
EmployeeNotWorkRemarkSerializer,EmpPersonInfoSerializer,
|
|
EmployeeSerializer,
|
|
ClockRecordSimpleSerializer, ClockRecordCreateSerializer,
|
|
NotWorkRemarkListSerializer, CorrectSerializer, AttendanceSerializer,
|
|
ResignationSerializer, EmpNeedSerializer, LeaveSerializer, TransferSerializer, ProbationSerializer, EmpContractSerializer)
|
|
from apps.hrm.services import HrmService
|
|
|
|
from apps.third.dahua import dhClient
|
|
from apps.third.tapis import dhapis
|
|
from openpyxl import load_workbook
|
|
from django.db import transaction
|
|
from django.core.exceptions import ValidationError
|
|
from datetime import datetime
|
|
from apps.utils.export import export_excel
|
|
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet, EuModelViewSet
|
|
from apps.utils.mixins import BulkCreateModelMixin, BulkDestroyModelMixin, CustomListModelMixin, RetrieveModelMixin
|
|
from apps.wf.models import Ticket
|
|
from apps.wf.mixins import TicketMixin
|
|
from apps.system.models import Post, Dept
|
|
from django.db.models import DateField
|
|
from datetime import datetime, date
|
|
from openpyxl.utils.datetime import from_excel
|
|
import os
|
|
import re
|
|
epTypeOptions = {'employee': '正式员工', 'remployee': '相关方',
|
|
'visitor': '访客', 'driver': '货车司机'}
|
|
epStateOptions = {10: '在职', 20: '离职', 30: '退休'}
|
|
crOptions = {10: '上班打卡', 20: '下班打卡', 30: ''}
|
|
crEoptions = {10: '在岗时间短', 20: '在岗时间长', 30: '缺卡', 40: '加班'}
|
|
|
|
HEAD_MAP = {
|
|
"所属部门": "belong_dept",
|
|
"车间": "workshop",
|
|
"职务": "position",
|
|
"职务聘任日期": "office_date",
|
|
"类别": "type",
|
|
"性别": "gender",
|
|
"身份证号码:姓名": "name",
|
|
"身份证号码:身份证号": "id_number",
|
|
"银行卡号码": "bank_card",
|
|
"入职日期": "start_date",
|
|
"合同到期日": "contract_end_date",
|
|
"转正日期": "regular_date",
|
|
"出生日期": "birthday",
|
|
"年龄": "age",
|
|
"工龄": "work_years",
|
|
"政治面貌": "partisan",
|
|
"入党时间": "join_partisan_date",
|
|
"民族": "nation",
|
|
"婚姻状况": "marriage",
|
|
"户口性质": "hukou_type",
|
|
"籍贯": "birthplace",
|
|
"户籍地址": "hukou_address",
|
|
"现住地址": "address",
|
|
"枣庄市职称": "zhuanzhi",
|
|
"枣庄市职称证书:图片_1": "zhuanzhi_img_1",
|
|
"枣庄市职称证书:图片_2": "zhuanzhi_img_2",
|
|
"获得枣庄市职称日期": "zhuanzhi_date",
|
|
"总院/集团职称": "zyjt_zhuanzhi",
|
|
"总院/集团职称证书:图片_1": "zyjt_zhuanzhi_img_1",
|
|
"获得职称日期": "zyjt_zhuanzhi_date",
|
|
"职业技能等级": "skill_rank",
|
|
"获得技能等级证书日期": "skill_rank_date",
|
|
"全日制最高学历": "full_edu",
|
|
"全日制最高学历学校名称": "full_edu_school",
|
|
"全日制最高学历所学专业": "full_edu_major",
|
|
"全日制最高学历入学时间-毕业时间": "full_edu_time",
|
|
"非全日制最高学历": "part_edu",
|
|
"非全日制最高学历学校名称": "part_edu_school",
|
|
"非全日制最高学历所学专业": "part_edu_major",
|
|
"非全日制最高学历入学时间-毕业时间": "part_edu_time",
|
|
"本人联系电话": "phone",
|
|
"紧急联系人姓名": "emergency_contact",
|
|
"紧急联系人电话": "emergency_phone",
|
|
"所获荣誉": "honor",
|
|
"首次缴纳社保日期": "first_social_security_date",
|
|
"是否为退役军人": "is_veteran",
|
|
"个人邮箱":"email",
|
|
}
|
|
|
|
def cell_value(cell):
|
|
val = cell.value
|
|
if isinstance(val, datetime):
|
|
return val.date()
|
|
return val.strip() if isinstance(val, str) else val
|
|
|
|
class EmployeeViewSet(CustomModelViewSet):
|
|
"""
|
|
人员管理
|
|
"""
|
|
queryset = Employee.objects.exclude(user__is_superuser=True)
|
|
select_related_fields = ['user']
|
|
filterset_class = EmployeeFilterSet
|
|
serializer_class = EmployeeSerializer
|
|
retrieve_serializer_class = EmployeeDetailSerializer
|
|
create_serializer_class = EmployeeCreateUpdateSerializer
|
|
update_serializer_class = EmployeeCreateUpdateSerializer
|
|
partial_update_serializer_class = EmployeeCreateUpdateSerializer
|
|
search_fields = ['name', 'number',
|
|
'user__username', 'id_number', 'id', 'phone']
|
|
ordering = ['-pk']
|
|
|
|
# def filter_queryset(self, queryset):
|
|
# if not self.detail:
|
|
# self.request.query_params._mutable = True
|
|
# self.request.query_params.setdefault('type', 'employee')
|
|
# return super().filter_queryset(queryset)
|
|
|
|
@action(methods=['get'], detail=False, perms_map={'get': '*'},
|
|
serializer_class=serializers.Serializer)
|
|
def info(self, request, pk=None):
|
|
"""个人信息
|
|
|
|
个人信息
|
|
"""
|
|
user = request.user
|
|
ep, _ = Employee.objects.get_or_create(user=user,
|
|
defaults={
|
|
"user": user,
|
|
"name": user.name,
|
|
"phone": user.phone,
|
|
"belong_dept": user.belong_dept,
|
|
"post": user.post,
|
|
"type": user.type
|
|
})
|
|
return Response(EmployeeSerializer(instance=ep).data)
|
|
|
|
@action(methods=['post'], detail=False, permission_classes=[IsAuthenticated],
|
|
serializer_class=EmployeeImproveSerializer)
|
|
@transaction.atomic
|
|
def improve_info(self, request, *args, **kwargs):
|
|
"""完善个人信息
|
|
|
|
完善个人信息
|
|
"""
|
|
user = request.user
|
|
ep = user.employee
|
|
serializer = EmployeeImproveSerializer(instance=ep, data=request.data)
|
|
serializer.is_valid(raise_exception=True)
|
|
vdata = serializer.validated_data
|
|
if vdata.get('photo', None):
|
|
dhClient.request(**dhapis['person_img_upload'],
|
|
file_path_rela=vdata['photo'])
|
|
serializer.save()
|
|
if ep.type == 'remployee':
|
|
from apps.rpm.services import sync_to_rep
|
|
sync_to_rep(ep)
|
|
elif ep.type in ['visitor', 'driver']:
|
|
from apps.vm.services import sync_to_visitor
|
|
sync_to_visitor(ep)
|
|
return Response()
|
|
|
|
@action(methods=['post'], detail=True, perms_map={'post': 'employee.notworkremark'},
|
|
serializer_class=EmployeeNotWorkRemarkSerializer)
|
|
def not_work_remark(self, request, pk=None):
|
|
"""
|
|
填写离岗说明
|
|
"""
|
|
serializer = self.get_serializer(data=request.data)
|
|
serializer.is_valid(raise_exception=True)
|
|
obj = self.get_object()
|
|
if not obj.is_atwork:
|
|
remark = request.data.get('not_work_remark', '')
|
|
obj.not_work_remark = remark
|
|
obj.save()
|
|
now = timezone.now()
|
|
instance, created = NotWorkRemark.objects.get_or_create(
|
|
not_work_date=now.date(),
|
|
user=obj.user,
|
|
defaults={
|
|
"not_work_date": now.date(),
|
|
"user": obj.user,
|
|
"remark": remark,
|
|
"create_by": request.user,
|
|
}
|
|
)
|
|
if not created:
|
|
instance.remark = remark
|
|
instance.update_by = request.user
|
|
instance.save()
|
|
return Response()
|
|
raise ParseError(**NO_NEED_LEVEL_REMARK)
|
|
|
|
@transaction.atomic
|
|
@action(methods=['post'], detail=True, perms_map={'post': 'employee.channel_authority'},
|
|
serializer_class=serializers.Serializer)
|
|
def channel_authority(self, request, pk=None):
|
|
"""重新门禁授权
|
|
|
|
重新门禁授权
|
|
"""
|
|
obj = self.get_object()
|
|
if obj.third_info.get('dh_face_card', None):
|
|
HrmService.door_auth(ep=obj)
|
|
else:
|
|
raise ParseError('该员工缺少主卡')
|
|
# objs = Employee.objects.filter(pk__in=vdata['pks'], third_info__dh_face_card__isnull=False)
|
|
# infos = objs.values('third_info')
|
|
# cards = []
|
|
# for i in infos:
|
|
# if isinstance(i['third_info'], dict) and 'dh_face_card' in i['third_info']:
|
|
# cards.append(i['third_info']['dh_face_card'])
|
|
# details = []
|
|
# for i in vdata['channels']:
|
|
# details.append({
|
|
# "privilegeType": 1,
|
|
# "resouceCode": i
|
|
# })
|
|
# if cards and details:
|
|
# json_data = {
|
|
# "cardNumbers": cards,
|
|
# "timeQuantumId": 1,
|
|
# "cardPrivilegeDetails": details
|
|
# }
|
|
# dhClient.request(**dhapis['card_door_authority'], json=json_data)
|
|
# for i in objs:
|
|
# i.third_info['dh_channels'] = vdata['channels']
|
|
# Employee.objects.bulk_update(objs, fields=['third_info'])
|
|
return Response()
|
|
|
|
@transaction.atomic
|
|
@action(methods=['post'], detail=False, perms_map={'post': 'employee.face_bind_1'},
|
|
serializer_class=serializers.Serializer)
|
|
def face_bind_1(self, request, pk=None):
|
|
"""同步人脸库1
|
|
|
|
全部人脸库
|
|
"""
|
|
# 获取设备
|
|
# json_data = {
|
|
# "pageNum": 1,
|
|
# "pageSize": 1000,
|
|
# "ownerCodes": ['001'],
|
|
# "showChildNodeData": 1,
|
|
# # "isOnline":1
|
|
# }
|
|
# _, res = dhClient.request(**dhapis['dev_page'], json=json_data)
|
|
# devs = []
|
|
# if res['pageData']:
|
|
# for i in res['pageData']:
|
|
# devs.append(i['deviceCode'])
|
|
# 编辑人像库
|
|
json_data = {
|
|
"groupid": settings.DAHUA_FACEGROUPID_1,
|
|
"groupname": "全体人员",
|
|
"groupdetail": "全体人员",
|
|
"grouptype": 3,
|
|
"deviceCodeList": [settings.DAHUA_IVSS_CODE],
|
|
"syncState": 0
|
|
}
|
|
dhClient.request(**dhapis['face_group_update'], json=json_data)
|
|
# 人像绑定
|
|
dhClient.face_bind()
|
|
return Response()
|
|
|
|
@action(methods=['get'], detail=False, perms_map={'get': 'employee.face_bind_1'},
|
|
serializer_class=serializers.Serializer)
|
|
def face_status_1(self, request, pk=None):
|
|
"""人像下发状态
|
|
|
|
人像下发状态
|
|
"""
|
|
params = {'id': settings.DAHUA_FACEGROUPID_1}
|
|
_, res = dhClient.request(**dhapis['face_group_info'], params=params)
|
|
return Response(res)
|
|
|
|
@action(methods=['get'], detail=False, perms_map={'get': '*'},
|
|
serializer_class=serializers.Serializer)
|
|
def export_excel(self, request, pk=None):
|
|
"""导出excel
|
|
导出excel
|
|
"""
|
|
field_data = ['人员类型', '人员', '手机号', '身份证号', '所属部门', '在职状态', '定位卡号']
|
|
queryset = self.filter_queryset(self.get_queryset())
|
|
if queryset.count() > 1000:
|
|
raise ParseError('数据量超过1000,请筛选后导出')
|
|
odata = EmployeeSerializer(queryset, many=True).data
|
|
# 处理数据
|
|
data = []
|
|
for i in odata:
|
|
data.append(
|
|
[epTypeOptions[i['type']],
|
|
i['name'],
|
|
i['phone'],
|
|
i['id_number'],
|
|
i.get('belong_dept_name', ''),
|
|
epStateOptions[i['job_state']],
|
|
i['blt_'].get('code', '') if 'blt_' in i and i['blt_'] else '']
|
|
)
|
|
return Response({'path': export_excel(field_data, data, '人员信息')})
|
|
|
|
@action(methods=['post'], detail=False, perms_map={'post': 'employee.import_excel'},
|
|
serializer_class=serializers.Serializer)
|
|
def import_excel(self, request, pk=None):
|
|
import logging
|
|
myLogger = logging.getLogger('log')
|
|
"""导入excel"""
|
|
file_path = request.data.get('file_path')
|
|
relative_path = file_path.lstrip('/')
|
|
abs_path = os.path.join(settings.BASE_DIR, relative_path)
|
|
wb = load_workbook(abs_path, data_only=True)
|
|
sheet = wb.active
|
|
|
|
rows = list(sheet.iter_rows())
|
|
if len(rows) < 2:
|
|
raise ParseError('Excel 没有数据')
|
|
|
|
# 获取表头
|
|
headers = [cell_value(c) for c in rows[0]]
|
|
field_index = {}
|
|
for idx, header in enumerate(headers):
|
|
if header in HEAD_MAP:
|
|
field_index[HEAD_MAP[header]] = idx
|
|
|
|
# 外键缓存
|
|
dept_map = {d.name: d.id for d in Dept.objects.all()}
|
|
TYPE_MAPPING = {
|
|
'光芯': 'employee',
|
|
'外包': 'remployee'
|
|
}
|
|
success = 0
|
|
errors = []
|
|
|
|
# 获取模型字段
|
|
model_fields = {f.name: f for f in Employee._meta.fields}
|
|
|
|
for row_num, row in enumerate(rows[1:], start=2):
|
|
try:
|
|
data = {}
|
|
for field, idx in field_index.items():
|
|
raw_value = cell_value(row[idx])
|
|
model_field = model_fields.get(field)
|
|
value = convert_field_value(model_field, raw_value, row_num)
|
|
data[field] = value
|
|
|
|
# 移除可能的id字段
|
|
if 'id' in data:
|
|
del data['id']
|
|
|
|
# 获取唯一标识
|
|
id_number = data.get("id_number")
|
|
name = data.get("name")
|
|
|
|
if not id_number or not name:
|
|
raise ParseError(f'第{row_num}行,身份证号或姓名为空')
|
|
|
|
myLogger.info(f"处理第{row_num}行:{name} - {id_number}")
|
|
# 处理人员类型
|
|
if 'type' in data and data['type']:
|
|
excel_type = data['type']
|
|
if excel_type in TYPE_MAPPING:
|
|
data['type'] = TYPE_MAPPING[excel_type]
|
|
else:
|
|
raise ParseError(f'第{row_num}行,人员类型"{excel_type}"无效,有效类型:{", ".join(TYPE_MAPPING.keys())}')
|
|
# 处理部门外键
|
|
if 'belong_dept' in data and data['belong_dept']:
|
|
dept_name = data.pop('belong_dept')
|
|
if dept_name not in dept_map:
|
|
raise ParseError(f'第{row_num}行,部门"{dept_name}"不存在')
|
|
data['belong_dept_id'] = dept_map[dept_name]
|
|
|
|
# 数据验证
|
|
if data.get('phone'):
|
|
if not re.match(r'^1[3-9]\d{9}$', data['phone']):
|
|
raise ParseError(f'第{row_num}行,手机号格式不正确')
|
|
|
|
if data.get('id_number'):
|
|
if not re.match(r'^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$', data['id_number']):
|
|
raise ParseError(f'第{row_num}行,身份证号格式不正确')
|
|
|
|
# 查找或更新
|
|
try:
|
|
with transaction.atomic():
|
|
obj, created = Employee.objects.update_or_create(
|
|
id_number=id_number,
|
|
name=name,
|
|
defaults=data
|
|
)
|
|
except Exception as e:
|
|
raise
|
|
if created:
|
|
myLogger.info(f"✅ 第{row_num}行新增成功:{name}")
|
|
else:
|
|
myLogger.info(f"✅ 第{row_num}行更新成功:{name}")
|
|
|
|
success += 1
|
|
|
|
except Exception as e:
|
|
error_msg = f'第{row_num}行处理失败:{str(e)}'
|
|
myLogger.error(f"❌ {error_msg}")
|
|
errors.append({'row': row_num, 'error': str(e)})
|
|
|
|
myLogger.info(f"\n处理完成:成功{success}条,失败{len(errors)}条")
|
|
|
|
return Response({'success': success, 'errors': errors})
|
|
|
|
|
|
def convert_field_value(model_field, value, row_num):
|
|
"""
|
|
根据模型字段类型自动转换 Excel 值
|
|
"""
|
|
from django.db.models import DateField, BooleanField, IntegerField, FloatField, CharField
|
|
if value in [None, ""]:
|
|
return None
|
|
|
|
# ===== 日期字段 =====
|
|
if isinstance(model_field, CharField):
|
|
if isinstance(value, str):
|
|
if 'e' in value.lower():
|
|
try:
|
|
return str(int(float(value)))
|
|
except ValueError:
|
|
pass
|
|
if '.' in value:
|
|
try:
|
|
return str(int(float(value)))
|
|
except ValueError:
|
|
pass
|
|
return str(value)
|
|
|
|
if isinstance(model_field, DateField):
|
|
|
|
# 已经是 date
|
|
if isinstance(value, date):
|
|
return value
|
|
|
|
# datetime
|
|
if isinstance(value, datetime):
|
|
return value.date()
|
|
|
|
# Excel 数字日期
|
|
if isinstance(value, (int, float)):
|
|
try:
|
|
return from_excel(value).date()
|
|
except Exception:
|
|
raise ParseError(f'第{row_num}行,字段 {model_field.name} 日期格式错误')
|
|
|
|
# 字符串日期
|
|
if isinstance(value, str):
|
|
value = value.strip()
|
|
formats = [
|
|
"%Y-%m-%d",
|
|
"%Y/%m/%d",
|
|
"%Y.%m.%d",
|
|
"%Y%m%d",
|
|
"%Y年%m月%d日",
|
|
]
|
|
for fmt in formats:
|
|
try:
|
|
return datetime.strptime(value, fmt).date()
|
|
except ValueError:
|
|
continue
|
|
|
|
raise ParseError(f'第{row_num}行,字段 {model_field.name} 日期格式不正确')
|
|
|
|
raise ParseError(f'第{row_num}行,字段 {model_field.name} 日期类型错误')
|
|
|
|
# ===== 布尔字段 =====
|
|
if isinstance(model_field, BooleanField):
|
|
|
|
if value in [True, 1, '1']:
|
|
return True
|
|
if value in [False, 0, '0']:
|
|
return False
|
|
|
|
if isinstance(value, str):
|
|
value = value.strip()
|
|
if value in ['是', 'Y', 'y', 'yes', 'YES', 'True', 'true']:
|
|
return True
|
|
if value in ['否', 'N', 'n', 'no', 'NO', 'False', 'false']:
|
|
return False
|
|
|
|
raise ParseError(f'第{row_num}行,字段 {model_field.name} 只能填写 是 或 否')
|
|
|
|
# ===== 整数字段 =====
|
|
if isinstance(model_field, IntegerField):
|
|
try:
|
|
return int(value)
|
|
except:
|
|
raise ParseError(f'第{row_num}行,字段 {model_field.name} 必须为整数')
|
|
|
|
# ===== 浮点字段 =====
|
|
if isinstance(model_field, FloatField):
|
|
try:
|
|
return float(value)
|
|
except:
|
|
raise ParseError(f'第{row_num}行,字段 {model_field.name} 必须为数字')
|
|
return value
|
|
|
|
class AttendanceViewSet(CustomModelViewSet):
|
|
"""
|
|
list: 到岗记录
|
|
|
|
到岗记录
|
|
"""
|
|
queryset = Attendance.objects.all()
|
|
serializer_class = AttendanceSerializer
|
|
select_related_fields = ['user',
|
|
'user__belong_dept', 'user__post', 'shift', 'team']
|
|
filterset_fields = ['user',
|
|
'user__belong_dept', 'user__post', 'state', 'work_date', 'user__name', 'user__phone']
|
|
search_fields = ['user__name', 'user__phone']
|
|
ordering = ['-work_date', 'shift', '-create_time']
|
|
ordering_fields = ['work_date', 'shift', 'create_time']
|
|
|
|
|
|
class ClockRecordViewSet(BulkCreateModelMixin, ListModelMixin, BulkDestroyModelMixin, CustomGenericViewSet):
|
|
"""
|
|
打卡记录
|
|
"""
|
|
perms_map = {'get': '*', 'post': 'clockrecord.create',
|
|
'delete': 'clockrecord.delete'}
|
|
queryset = ClockRecord.objects.all()
|
|
select_related_fields = ['employee']
|
|
search_fields = ['employee__name', 'employee__number', 'employee__phone']
|
|
serializer_class = ClockRecordListSerializer
|
|
create_serializer_class = ClockRecordCreateSerializer
|
|
filterset_class = ClockRecordFilterSet
|
|
ordering = ['-create_time']
|
|
|
|
@action(methods=['get'], detail=False, perms_map={'get': '*'},
|
|
serializer_class=serializers.Serializer)
|
|
def export_excel(self, request, pk=None):
|
|
"""导出excel
|
|
导出excel
|
|
"""
|
|
field_data = ['人员类型', '人员', '编号', '身份证号',
|
|
'所属部门', '触发形式', '打卡时间', '打卡推测', '异常推测']
|
|
queryset = self.filter_queryset(self.get_queryset())
|
|
odata = ClockRecordListSerializer(queryset, many=True).data
|
|
# 处理数据
|
|
data = []
|
|
for i in odata:
|
|
data.append(
|
|
[epTypeOptions[i['employee_']['type']],
|
|
i['employee_']['name'],
|
|
i['employee_']['number'],
|
|
i['employee_']['id_number'],
|
|
i['employee_'].get('belong_dept_name', ''),
|
|
i['detail'].get('deviceName', None),
|
|
i['create_time'],
|
|
crOptions[i['type']],
|
|
crEoptions[i['exception_type']] if i['exception_type'] else '']
|
|
)
|
|
return Response({'path': export_excel(field_data, data, '打卡记录')})
|
|
|
|
@action(methods=['post'], detail=False, perms_map={'post': '*'},
|
|
serializer_class=serializers.Serializer, logging_methods=[])
|
|
def dahua(self, request):
|
|
"""
|
|
大华刷脸分页带my_info
|
|
|
|
大华刷脸分页带my_info
|
|
"""
|
|
request.data.update({
|
|
"openType": "61",
|
|
})
|
|
_, res = dhClient.request(**dhapis['swipe_list'], json=request.data)
|
|
ids = []
|
|
if res.get('pageData', None):
|
|
for i in res['pageData']:
|
|
ids.append(i['id'])
|
|
crs_info = ClockRecordSimpleSerializer(
|
|
instance=ClockRecord.objects.filter(detail__id__in=ids), many=True).data
|
|
crs_dict = {}
|
|
for i in crs_info:
|
|
crs_dict[i['detail']['id']] = i
|
|
for i in res['pageData']:
|
|
i['my_info'] = {}
|
|
if i['id'] in crs_dict:
|
|
i['my_info'] = crs_dict[i['id']]
|
|
return Response(res)
|
|
|
|
# @action(methods=['post'], detail=False, perms_map={'post': '*'},
|
|
# serializer_class=CorrectSerializer)
|
|
# def correct_swip(self, request, pk=None):
|
|
# """
|
|
# 重跑一段时间的打卡记录
|
|
|
|
# 重跑一段时间的打卡记录
|
|
# """
|
|
# sr = CorrectSerializer(data=request.data)
|
|
# sr.is_valid(raise_exception=True)
|
|
# vdata = sr.validated_data
|
|
# from apps.hrm.tasks import correct_swip_task
|
|
# correct_swip_task.delay(vdata['start_time'], vdata['end_time'])
|
|
# return Response()
|
|
|
|
# @action(methods=['post'], detail=False, perms_map={'post': '*'},
|
|
# serializer_class=CorrectSerializer)
|
|
# def correct_enter_or_exit(self, request, pk=None):
|
|
# """
|
|
# 变更一段时间日志刷脸类型
|
|
|
|
# 变更一段时间日志刷脸类型
|
|
# """
|
|
# from apps.monitor.models import DrfRequestLog
|
|
# sr = CorrectSerializer(data=request.data)
|
|
# sr.is_valid(raise_exception=True)
|
|
# vdata = sr.validated_data
|
|
# for i in DrfRequestLog.objects.filter(path='/api/third/dahua/c_swip/', data__contains='办公楼考勤面板'
|
|
# , create_time__gte=vdata['start_time']
|
|
# , create_time__lte=vdata['end_time']).filter(data__contains = "'enterOrExit': 1" ):
|
|
# data = i.data
|
|
# i.data = data.replace("'enterOrExit': 1", "'enterOrExit': 3")
|
|
# i.save()
|
|
# return Response()
|
|
|
|
|
|
class NotWorkRemarkViewSet(ListModelMixin, CustomGenericViewSet):
|
|
"""
|
|
离岗说明
|
|
"""
|
|
perms_map = {'get': '*'}
|
|
queryset = NotWorkRemark.objects.all()
|
|
select_related_fields = ['user']
|
|
serializer_class = NotWorkRemarkListSerializer
|
|
filterset_class = NotWorkRemarkFilterSet
|
|
ordering = ['-pk']
|
|
|
|
|
|
class CertificateViewSet(CustomModelViewSet):
|
|
queryset = Certificate.objects.all()
|
|
create_serializer_class = CertificateCreateUpdateSerializer
|
|
update_serializer_class = CertificateCreateUpdateSerializer
|
|
serializer_class = CertificateSerializer
|
|
filterset_class = CertificateFilterSet
|
|
search_fields = ['name', 'number', 'employee__name']
|
|
|
|
def perform_create(self, serializer):
|
|
ins: Certificate = serializer.save()
|
|
ins.get_state(need_update=True)
|
|
|
|
def perform_update(self, serializer):
|
|
ins: Certificate = serializer.save()
|
|
ins.get_state(need_update=True)
|
|
|
|
|
|
class ResignationViewSet(TicketMixin, EuModelViewSet):
|
|
"""
|
|
离职记录
|
|
"""
|
|
select_related_fields = ['employee', 'employee__belong_dept', 'employee__post']
|
|
queryset = Resignation.objects.all()
|
|
serializer_class = ResignationSerializer
|
|
search_fields = ["employee__name"]
|
|
workflow_key = "wf_resignation"
|
|
|
|
def gen_other_ticket_data(self, instance):
|
|
return {"employee_name": instance.employee.name}
|
|
|
|
@staticmethod
|
|
def update_handle_date(ticket: Ticket, transition, new_ticket_data: dict):
|
|
handle_date = new_ticket_data.get("handle_date", None)
|
|
if handle_date:
|
|
resignation = Resignation.objects.get(ticket=ticket)
|
|
resignation.handle_date = handle_date
|
|
resignation.save()
|
|
else:
|
|
raise ParseError("请填写办理离职的交接日期")
|
|
|
|
@staticmethod
|
|
def make_job_off(ticket: Ticket, transition, new_ticket_data: dict):
|
|
resignation = Resignation.objects.get(ticket=ticket)
|
|
emp = resignation.employee
|
|
emp.job_state = Employee.JOB_OFF
|
|
emp.save(update_fields=['job_state'])
|
|
user = emp.user
|
|
user.is_deleted = True
|
|
user.save(update_fields=['is_deleted'])
|
|
|
|
|
|
class EmpNeedViewSet(TicketMixin, EuModelViewSet):
|
|
"""
|
|
员工需求
|
|
"""
|
|
queryset = EmpNeed.objects.all()
|
|
serializer_class = EmpNeedSerializer
|
|
filterset_fields = ['dept_need']
|
|
search_fields = ["dept_need__name", "post_need"]
|
|
workflow_key = "wf_empneed"
|
|
|
|
def gen_other_ticket_data(self, instance):
|
|
return {"post_need": instance.post_need}
|
|
|
|
|
|
class EmpJoinViewSet(TicketMixin, EuModelViewSet):
|
|
"""
|
|
员工入职
|
|
"""
|
|
queryset = EmpJoin.objects.all()
|
|
serializer_class = EmpJoinSerializer
|
|
workflow_key = "wf_empjoin"
|
|
|
|
def gen_other_ticket_data(self, instance):
|
|
return {"dept_name": instance.dept_need.name if instance.dept_need else None}
|
|
|
|
@staticmethod
|
|
def approve(ticket: Ticket, transition, new_ticket_data: dict):
|
|
person = new_ticket_data.get("person", None)
|
|
EmpJoin.objects.filter(ticket=ticket).update(person=person)
|
|
if not person:
|
|
raise ParseError("请选择人员")
|
|
serializer = EmpPersonInfoSerializer(data=person, many=True)
|
|
serializer.is_valid(raise_exception=True)
|
|
serializer.save()
|
|
|
|
|
|
class LeaveViewSet(TicketMixin, EuModelViewSet):
|
|
"""
|
|
员工请假
|
|
"""
|
|
select_related_fields = [
|
|
'employee',
|
|
'employee__belong_dept',
|
|
'ticket',
|
|
]
|
|
queryset = Leave.objects.all()
|
|
serializer_class = LeaveSerializer
|
|
filterset_fields = ['leave_type', 'employee__belong_dept']
|
|
search_fields = ["employee__name", "leave_type"]
|
|
workflow_key = "wf_leave"
|
|
|
|
def gen_other_ticket_data(self, instance):
|
|
return {"hour": instance.hour if instance.hour else None}
|
|
|
|
|
|
class EmpPersonInfoViewSet(CustomModelViewSet, EuModelViewSet):
|
|
"""
|
|
入职人员信息
|
|
"""
|
|
queryset = EmpPersonInfo.objects.all()
|
|
serializer_class = EmpPersonInfoSerializer
|
|
filterset_fields = ['post']
|
|
search_fields = ['name', 'post']
|
|
|
|
|
|
class TransferViewSet(TicketMixin, EuModelViewSet):
|
|
"""
|
|
员工岗位调动
|
|
"""
|
|
select_related_fields = [
|
|
'employee',
|
|
'employee__belong_dept',
|
|
'employee__post',
|
|
'ticket',
|
|
]
|
|
queryset = EmployeeTransfer.objects.all()
|
|
serializer_class = TransferSerializer
|
|
filterset_fields = ['employee__belong_dept']
|
|
search_fields = ["employee__name", "employee__post", "employee__belong_dept"]
|
|
workflow_key = "wf_transfer"
|
|
|
|
def gen_other_ticket_data(self, instance):
|
|
return {"name": instance.employee.name if instance.employee.name else None}
|
|
|
|
class ProbationViewSet(TicketMixin, EuModelViewSet):
|
|
"""
|
|
员工转正
|
|
"""
|
|
select_related_fields = [
|
|
'empperson',
|
|
'ticket',
|
|
'reg_dept',
|
|
'reg_post'
|
|
]
|
|
queryset = Probation.objects.all()
|
|
serializer_class = ProbationSerializer
|
|
filterset_fields = ['empperson__name', 'reg_dept']
|
|
search_fields = ["empperson__name", "reg_post__name", "reg_dept__name"]
|
|
workflow_key = "wf_probation"
|
|
|
|
def gen_other_ticket_data(self, instance):
|
|
return {"name": instance.empperson.name if instance.empperson.name else None}
|
|
|
|
|
|
class EmpContractViewSet(TicketMixin, EuModelViewSet):
|
|
"""
|
|
员工合同
|
|
"""
|
|
queryset = EmpContract.objects.all()
|
|
serializer_class = EmpContractSerializer
|
|
filterset_fields = ['employee', 'employee__belong_dept','employee__name', 'employee__post']
|
|
search_fields = ["employee__name", "employee__post", "employee__belong_dept"]
|
|
workflow_key = "wf_contract"
|
|
|
|
def gen_other_ticket_data(self, instance):
|
|
return {"name": instance.employee.name if instance.employee.name else None}
|
|
|