from rest_framework.exceptions import ParseError, APIException import logging import time from apps.hrm.models import ClockRecord, Employee from apps.third.errors import DH_REQUEST_ERROR from apps.third.models import TDevice from apps.third.tapis import dhapis from apps.third.dahua import dhClient from apps.utils.tools import rannum, ranstr from datetime import datetime, timedelta from django.utils import timezone from dateutil import tz from threading import Thread from django.conf import settings myLogger = logging.getLogger('log') class HrmService: @classmethod def sync_dahua_employee(cls, ep: Employee, old_photo='', start_time=None, end_time=None): """同步大华信息(员工/卡片/门禁) Args: ep (Employee): 人员实例 old_photo (str, optional): 原照片地址. Defaults to ''. start_time (_type_, optional): 开人脸卡起始时间. Defaults to None. end_time (_type_, optional): 开人脸卡结束时间. Defaults to None. Returns: _type_: _description_ """ if not settings.DAHUA_ENABLED: # 如果大华没启用, 直接返回 return dh_id = ep.third_info.get('dh_id', None) dh_photo = ep.third_info.get('dh_photo', None) dh_face_card = ep.third_info.get('dh_face_card', None) departmentId = 1 if ep.belong_dept: try: departmentId = ep.belong_dept.third_info['dh_id'] except Exception: pass if ep.third_info.get('dh_id', None): # 如果有大华信息 dh_id = ep.third_info['dh_id'] dh_photo = ep.third_info['dh_photo'] json_data = { "service": "ehs", "id": dh_id, "name": ep.name, "code": ep.number if ep.number else ranstr(6), "paperType": 111, "paperNumber": ep.id_number, "paperAddress": "default", "departmentId": departmentId, "phone": ep.phone, "email": ep.email, "sex": 1 if ep.gender == '男' else 2, "biosignatureTypeList": [3], "personBiosignatures": [{ "type": 3, "index": 1, "path": dh_photo }] } if ep.photo != old_photo and ep.photo: _, res = dhClient.request(**dhapis['person_img_upload'], file_path_rela=ep.photo) dh_photo = res["fileUrl"] json_data.update( { "biosignatureTypeList": [3], "personBiosignatures": [{ "type": 3, "index": 1, "path": dh_photo }] } ) dhClient.request(**dhapis['person_update'], json=json_data) ep = cls.save(ep, data={'dh_id': dh_id, 'dh_photo': dh_photo}) else: _, res = dhClient.request(**dhapis['person_gen_id']) dh_id = res['id'] json_data = { "service": "ehs", "id": dh_id, "name": ep.name, "code": ep.number if ep.number else ranstr(6), "paperType": 111, "paperNumber": ep.id_number, "paperAddress": "default", "departmentId": departmentId, "phone": ep.phone, "email": ep.email, "sex": 1 if ep.gender == '男' else 2 } _, res = dhClient.request(**dhapis['person_img_upload'], file_path_rela=ep.photo) dh_photo = res["fileUrl"] json_data.update( { "biosignatureTypeList": [3], "personBiosignatures": [{ "type": 3, "index": 1, "path": dh_photo }] } ) _, res = dhClient.request(**dhapis['person_add'], json=json_data) ep = cls.save(ep, data={'dh_id': dh_id, 'dh_photo': dh_photo}) # 开人脸卡 dh_face_card = cls.open_face_card( ep=ep, dh_id=dh_id, departmentId=departmentId, start_time=start_time, end_time=end_time) # 授予门禁权限 dh_dchannels = cls.door_auth(ep=ep) return {'dh_id': dh_id, 'dh_photo': dh_photo, 'dh_face_card': dh_face_card, 'dh_dchannels': dh_dchannels} @classmethod def open_face_card(cls, ep, dh_id, departmentId, start_time, end_time): """开人脸卡/有卡就更新卡时间 """ if ep.third_info.get('dh_face_card', None): cardNumber = ep.third_info.get('dh_face_card') # 如果有人脸卡就执行更新操作 if start_time is None: # 如果时间段未提供,跳过更新操作 pass else: startDate = timezone.localtime(start_time).strftime("%Y-%m-%d %H:%M:%S") endDate = timezone.localtime(end_time).strftime("%Y-%m-%d %H:%M:%S") json_data = { "cardNumber": cardNumber, "startDate": startDate, "endDate": endDate, "departmentId": departmentId, } _, res = dhClient.request(**dhapis['card_update'], json=json_data) cls.save(ep, data={'dh_face_card_start': startDate, 'dh_face_card_end': endDate}) return cardNumber else: _, res = dhClient.request(**dhapis['card_gen_id']) cardId = res['id'] cardNumber = str(ep.id)[3:8] + rannum(5) if start_time is None: # 如果未规定时间范围, 默认1小时 now = datetime.now() startDate = now.strftime("%Y-%m-%d %H:%M:%S") endDate = (now+timedelta(minutes=60)).strftime("%Y-%m-%d %H:%M:%S") else: startDate = timezone.localtime(start_time).strftime("%Y-%m-%d %H:%M:%S") endDate = timezone.localtime(end_time).strftime("%Y-%m-%d %H:%M:%S") json_data = { "id": cardId, "cardNumber": cardNumber, "category": 0, "cardType": 0, "personId": dh_id, "departmentId": departmentId, "startDate": startDate, "endDate": endDate } _, res = dhClient.request(**dhapis['card_add'], json=json_data) time.sleep(6) # 等待确保生成卡片 cls.save(ep, data={'dh_face_card': cardNumber, 'dh_face_card_start': startDate, 'dh_face_card_end': endDate}) return cardNumber @classmethod def door_auth(cls, ep: Employee, dh_dchannels: list = []): """授予门禁权限 """ from apps.third.models import TDevice dh_face_card = ep.third_info.get('dh_face_card', None) if dh_dchannels: pass else: # 查找可授予的门禁通道号 dh_dchannels = list(TDevice.objects.filter(type=TDevice.DEVICE_DCHANNEL, access_list__contains=ep.type).values_list('code', flat=True)) if dh_dchannels: details = [] for i in dh_dchannels: details.append({ "privilegeType": 1, "resouceCode": i }) json_data = { "cardNumbers": [dh_face_card], "timeQuantumId": 1, "cardPrivilegeDetails": details } is_ok, res = dhClient.request(**dhapis['card_door_authority'], json=json_data, raise_exception=False) if is_ok == 'fail' and '44999999' in res['code']: time.sleep(14) dhClient.request(**dhapis['card_door_authority'], json=json_data) elif is_ok == 'fail': raise ParseError(**res) elif is_ok == 'error': raise APIException(**DH_REQUEST_ERROR) cls.save(ep, data={'dh_dchannels': dh_dchannels}) return dh_dchannels @classmethod def save(cls, ep: Employee, data: dict): """更新third_info """ ti = ep.third_info ti.update(data) ep.third_info = ti ep.save() return ep @classmethod def swipe(cls, data: dict): """ 实时刷卡事件/用于记录考勤 有身份证号的 """ id_number = data['info']['extend'].get('paperNumber', None) if id_number: nodeCode = data['info']['extend']['acsChannelCode'] swip_time = data['info']['extend']['swingTime'] e_type = data['info']['extend']['enterOrExit'] cls.swipe_next(nodeCode, id_number, swip_time, e_type, data['info']['extend']) @classmethod def swipe_offline(cls, data:dict): """离线刷卡推送记录处理 """ for i in data['infoArray']: id_number = i.get('paperNumber', None) if id_number: nodeCode = i['acsChannelCode'] swip_time = i['swingTime'] e_type = i['enterOrExit'] cls.swipe_next(nodeCode, id_number, swip_time, e_type, i) @classmethod def swipe_next(cls, nodeCode: str, id_number: str, swip_time: str, e_type:int, detail:dict): from apps.vm.models import Visit from apps.rpm.models import Rpj # 进行相关方/访客项目更新 Visit.objects.filter(state=Visit.V_ENTER, visitors__employee__id_number=id_number).update( state=Visit.V_WORKING) Rpj.objects.filter(state=Rpj.RPJ_ENTER, remployees__employee__id_number=id_number).update( state=Rpj.RPJ_WORKING) # 打卡处理 device = TDevice.objects.filter(code=nodeCode).first() ep = Employee.objects.filter(id_number=id_number, type__in=["employee", "remployee"]).first() if device and device.is_clock and ep: tzinfo = tz.gettz('Asia/Shanghai') s_time_f = datetime.strptime(swip_time, "%Y-%m-%d %H:%M:%S").replace(tzinfo=tzinfo) first_time = datetime(year=s_time_f.year, month=s_time_f.month, day=s_time_f.day, hour=0, minute=0, second=0, tzinfo=tzinfo) end_time = datetime(year=s_time_f.year, month=s_time_f.month, day=s_time_f.day, hour=23, minute=59, second=59, tzinfo=tzinfo) card_type = 30 trigger = 'door' if e_type == 1: card_type = 10 elif e_type == 2: card_type = 20 else: time_10_x = datetime(year=s_time_f.year, month=s_time_f.month, day=s_time_f.day, hour=3, minute=0, second=0, tzinfo=tzinfo) time_10_y = datetime(year=s_time_f.year, month=s_time_f.month, day=s_time_f.day, hour=11, minute=0, second=0, tzinfo=tzinfo) time_20_x = datetime(year=s_time_f.year, month=s_time_f.month, day=s_time_f.day, hour=16, minute=0, second=0, tzinfo=tzinfo) time_20_y = datetime(year=s_time_f.year, month=s_time_f.month, day=s_time_f.day, hour=23, minute=0, second=0, tzinfo=tzinfo) if time_10_x < s_time_f < time_10_y: card_type = 10 elif time_20_x < s_time_f < time_20_y: card_type = 20 trigger = 'panel' # 先直接创建记录 cr = ClockRecord.objects.filter(employee=ep, create_time=s_time_f).first() if cr: pass else: cr = ClockRecord() cr.employee = ep cr.create_time = s_time_f cr.type = card_type cr.exception_type = None cr.trigger = trigger cr.detail = detail cr.save() if card_type == 10: # 查找当天的进门记录 cr_10_q = ClockRecord.objects.filter( type=10, employee=ep, create_time__gte=first_time, create_time__lte=end_time) first_obj = cr_10_q.order_by('create_time').first() cr_10_q.exclude(id=first_obj.id).update(type=30, exception_type=None) elif card_type == 20: # 查找当天的出门记录 cr_20_q = ClockRecord.objects.filter( type=20, employee=ep, create_time__gte=first_time, create_time__lte=end_time) last_obj = cr_20_q.order_by('-create_time').first() cr_20_q.exclude(id=last_obj.id).update(type=30, exception_type=None) # 判断是否有异常 # 找到最近的上下打卡时间不一定是当天的 cr_e = ClockRecord.objects.filter(create_time__lt=last_obj.create_time, employee=ep, type__in=[10, 20]).order_by('-create_time').first() if cr_e and last_obj: time_d = last_obj.create_time - cr_e.create_time if cr_e.type == 10: if time_d < timedelta(hours=7): last_obj.type = ClockRecord.E_TYPE_LESS last_obj.save() elif time_d > timedelta(hours=14): last_obj.type = ClockRecord.E_TYPE_MORE last_obj.save() elif time_d > timedelta(hours=10): last_obj.type = ClockRecord.E_TYPE_ADD last_obj.save() elif cr_e.type == 20: last_obj.type = ClockRecord.E_TYPE_MISS last_obj.save() # 记录在岗情况 last_obj_t = ClockRecord.objects.filter(employee=ep, type__in=[10,20]).order_by('-create_time').first() if last_obj_t: update_fields = {'last_check_time': last_obj_t.create_time} if last_obj_t.type == 10: update_fields['is_atwork'] = True else: update_fields['is_atwork'] = False Employee.objects.filter(id=ep.id).update(**update_fields) # 此处可触发安全帽事件逻辑