
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, Workflow, State
from apps.wf.mixins import TicketMixin
from apps.wf.services import WfService
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
        """
        # 导出列定义: (表头, 取值函数)。覆盖人员的全部业务字段。
        def _cell(v):
            return '' if v is None else v
        columns = [
            ('人员类型', lambda i: epTypeOptions.get(i['type'], i['type'])),
            ('姓名', lambda i: i['name']),
            ('性别', lambda i: i['gender']),
            ('人员编号', lambda i: i['number']),
            ('手机号', lambda i: i['phone']),
            ('个人邮箱', lambda i: i['email']),
            ('身份证号', lambda i: i['id_number']),
            ('所属部门', lambda i: i.get('belong_dept_name', '')),
            ('所属岗位', lambda i: i.get('post_name', '')),
            ('车间', lambda i: i['workshop']),
            ('职务', lambda i: i['position']),
            ('职务聘任日期', lambda i: i['office_date']),
            ('在职状态', lambda i: epStateOptions.get(i['job_state'], i['job_state'])),
            ('入职日期', lambda i: i['start_date']),
            ('转正日期', lambda i: i['regular_date']),
            ('合同到期日', lambda i: i['contract_end_date']),
            ('离职日期', lambda i: i['end_date']),
            ('银行卡号', lambda i: i['bank_card']),
            ('出生年月日', lambda i: i['birthday']),
            ('年龄', lambda i: i['age']),
            ('工龄', lambda i: i['work_years']),
            ('学历', lambda i: i['qualification']),
            ('政治面貌', lambda i: i['partisan']),
            ('入党时间', lambda i: i['join_partisan_date']),
            ('民族', lambda i: i['nation']),
            ('婚姻状况', lambda i: i['marriage']),
            ('户口性质', lambda i: i['hukou_type']),
            ('籍贯', lambda i: i['birthplace']),
            ('户籍地址', lambda i: i['hukou_address']),
            ('现住地址', lambda i: i['address']),
            ('枣庄市职称', lambda i: i['zhuanzhi']),
            ('获得枣庄市职称日期', lambda i: i['zhuanzhi_date']),
            ('总院/集团职称', lambda i: i['zyjt_zhuanzhi']),
            ('获得职称日期', lambda i: i['zyjt_zhuanzhi_date']),
            ('职业技能等级', lambda i: i['skill_rank']),
            ('获得技能等级证书日期', lambda i: i['skill_rank_date']),
            ('全日制最高学历', lambda i: i['full_edu']),
            ('全日制最高学历学校名称', lambda i: i['full_edu_school']),
            ('全日制最高学历专业', lambda i: i['full_edu_major']),
            ('全日制最高学历毕业时间', lambda i: i['full_edu_time']),
            ('非全日制最高学历', lambda i: i['part_edu']),
            ('非全日制最高学历学校名称', lambda i: i['part_edu_school']),
            ('非全日制最高学历专业', lambda i: i['part_edu_major']),
            ('非全日制最高学历毕业时间', lambda i: i['part_edu_time']),
            ('紧急联系人姓名', lambda i: i['emergency_contact']),
            ('紧急联系人电话', lambda i: i['emergency_phone']),
            ('所获荣誉', lambda i: i['honor']),
            ('首次缴纳社保日期', lambda i: i['first_social_security_date']),
            ('是否为退役军人', lambda i: '是' if i['is_veteran'] else '否'),
            ('定位卡号', lambda i: i['blt_'].get('code', '') if i.get('blt_') else ''),
            ('创建时间', lambda i: (i.get('create_time') or '')[:19].replace('T', ' ')),
        ]
        field_data = [c[0] for c in columns]
        queryset = self.filter_queryset(self.get_queryset()).select_related(
            'belong_dept', 'post', 'blt')
        odata = EmployeeSerializer(queryset, many=True).data
        # 处理数据
        data = []
        for i in odata:
            data.append([_cell(getter(i)) for _, getter in columns])
        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 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())}')
                # 处理部门外键：填了就校验是否存在并赋值；为空时不动（新增场景的必填在下方创建处校验）
                dept_name = data.pop('belong_dept', None)
                if dept_name:
                    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():
                        name_matches = list(Employee.objects.filter(name=name))
                        if len(name_matches) <= 1:
                            existing = name_matches[0] if name_matches else None
                            # 同名但身份证号不一致，视为不同人员，新建
                            if existing and id_number and existing.id_number \
                                    and existing.id_number != id_number:
                                existing = None
                        else:
                            # 存在重名，必须用身份证号区分
                            if not id_number:
                                raise ParseError(f'第{row_num}行，姓名"{name}"存在重名，必须填写身份证号')
                            existing = next(
                                (e for e in name_matches if e.id_number == id_number), None)

                        if existing:
                            # 用 Excel 中填写了值的列覆盖数据库已有数据；空单元格保持原值不变
                            updated_fields = []
                            for field_name, value in data.items():
                                if value in [None, '']:
                                    continue
                                if getattr(existing, field_name, None) != value:
                                    setattr(existing, field_name, value)
                                    updated_fields.append(field_name)
                            if updated_fields:
                                existing.save(update_fields=updated_fields + ['update_time'])
                                myLogger.info(f"✅ 第{row_num}行更新成功：{name}，更新字段：{updated_fields}")
                            else:
                                myLogger.info(f"⏭️ 第{row_num}行无变化：{name}")
                            created = False
                        else:
                            # 新增人员时所属部门必填
                            if 'belong_dept_id' not in data:
                                raise ParseError(f'第{row_num}行，新增人员时所属部门不能为空')
                            Employee.objects.create(**data)
                            created = True
                except Exception as e:
                    raise
                if created:
                    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}

    def perform_create(self, serializer):
        result = serializer.save()
        is_batch = isinstance(result, list)
        instances = result if is_batch else [result]
        handler = self.request.user

        workflow_key = self.get_workflow_key(instances[0])
        if not workflow_key:
            raise ParseError('工作流异常:必须赋值workflow_key')
        try:
            wf = Workflow.objects.get(key=workflow_key)
        except Exception as e:
            raise ParseError(f'工作流{workflow_key}异常:{e}')

        # 批量时创建一个工单，所有记录共享
        if is_batch:
            names = [ins.employee.name for ins in instances if ins.employee]
            ticket_data = {
                "t_model": "EmpContract",
                "t_id": str(instances[0].id),
                "batch": True,
                "count": len(instances),
                "name": f"批量合同变更({len(instances)}人)",
                "employees": [
                    {"name": ins.employee.name, "id": str(ins.id)} for ins in instances if ins.employee
                ],
            }
        else:
            ticket_data = self.gen_ticket_data(instances[0])

        ticket = WfService.handle_ticket(ticket=None, transition=None, workflow=wf, new_ticket_data=ticket_data,
                            handler=handler, oinfo={})
        for ins in instances:
            ins.ticket = ticket
            ins.save(update_fields=['ticket'])

        if self.ticket_auto_submit_on_create:
            source_state = WfService.get_workflow_start_state(wf)
            transitions = WfService.get_state_transitions(source_state)
            if transitions.count() == 1:
                transition = transitions.first()
                WfService.handle_ticket(ticket=ticket, transition=transition, new_ticket_data=ticket_data,
                                handler=handler, oinfo={})
            else:
                raise ParseError(f'工作流{workflow_key}异常:有多个或无后续状态;不可处理')

