From 811f96fe83201df4a992f4235a73d63c1c9eb6ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=B9=E5=89=8D=E6=98=8E?= <909355014@qq.com> Date: Wed, 10 Aug 2022 15:13:21 +0800 Subject: [PATCH] =?UTF-8?q?=E9=97=A8=E7=A6=81=E5=92=8C=E5=AE=9A=E4=BD=8D?= =?UTF-8?q?=E5=9C=A8=E7=BA=BF=E7=A6=BB=E7=BA=BF=E6=89=93=E5=8D=A1=E8=AE=B0?= =?UTF-8?q?=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/am/tasks.py | 12 +- apps/am/views.py | 4 +- .../ecm/migrations/0013_auto_20220810_1512.py | 25 ++ apps/ecm/models.py | 4 +- apps/ecm/service.py | 269 ++++++++++++------ apps/ecm/tasks.py | 23 +- apps/hrm/filters.py | 2 +- .../hrm/migrations/0006_auto_20220810_1512.py | 51 ++++ apps/hrm/models.py | 16 +- apps/hrm/services.py | 58 +++- apps/hrm/views.py | 2 +- apps/opm/services.py | 3 +- apps/third/dahua.py | 18 +- apps/third/tapis.py | 10 +- apps/third/views.py | 4 +- apps/utils/my_rsa.py | 13 + apps/wf/services.py | 7 - requirements.txt | 1 + 18 files changed, 403 insertions(+), 119 deletions(-) create mode 100644 apps/ecm/migrations/0013_auto_20220810_1512.py create mode 100644 apps/hrm/migrations/0006_auto_20220810_1512.py create mode 100644 apps/utils/my_rsa.py diff --git a/apps/am/tasks.py b/apps/am/tasks.py index f13525f7..95d24cce 100644 --- a/apps/am/tasks.py +++ b/apps/am/tasks.py @@ -11,19 +11,19 @@ def cache_areas_info(): """ 缓存区域信息 """ - area_fix_list = [] - for i in Area.objects.filter(type=Area.AREA_TYPE_FIX).exclude(third_info__xx_rail=None): + area_list = [] + for i in Area.objects.filter(is_hidden=False).exclude(third_info__xx_rail=None).order_by('number'): points = [] for item in i.third_info['xx_rail']['detail']['polygon']['points']: points.append((item['x'], item['y'])) - area_dict = { 'id': i.id, + 'type': i.type, 'floor_no': i.third_info['xx_rail']['detail']['floorNo'], 'polygon': Polygon(points), 'stay_minute_min': i.stay_minute_min, 'stay_minute_max': i.stay_minute_max } - area_fix_list.append(area_dict) - cache.set('area_fix_list', area_fix_list, timeout=None) - return area_fix_list + area_list.append(area_dict) + cache.set('area_list', area_list, timeout=None) + return area_list diff --git a/apps/am/views.py b/apps/am/views.py index 8bfaedf2..1296a5a3 100755 --- a/apps/am/views.py +++ b/apps/am/views.py @@ -1,5 +1,6 @@ from apps.am.models import Access, Area from apps.am.serializers import AccessCreateSerializer, AccessSerializer, AreaCreateUpdateSerializer, AreaSerializer +from apps.am.tasks import cache_areas_info from apps.utils.viewsets import CustomModelViewSet, CustomGenericViewSet from django.db import transaction from rest_framework.decorators import action @@ -12,7 +13,7 @@ from rest_framework.mixins import ListModelMixin, CreateModelMixin, DestroyModel # Create your views here. class AreaViewSet(CustomModelViewSet): - queryset = Area.objects.all() + queryset = Area.objects.all().order_by('number') create_serializer_class = AreaCreateUpdateSerializer update_serializer_class = AreaCreateUpdateSerializer serializer_class = AreaSerializer @@ -40,6 +41,7 @@ class AreaViewSet(CustomModelViewSet): third_info['xx_rail'] = rail_info obj.third_info = third_info obj.save() + cache_areas_info.delay() return Response() diff --git a/apps/ecm/migrations/0013_auto_20220810_1512.py b/apps/ecm/migrations/0013_auto_20220810_1512.py new file mode 100644 index 00000000..a4145b8f --- /dev/null +++ b/apps/ecm/migrations/0013_auto_20220810_1512.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.12 on 2022-08-10 07:12 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('am', '0004_auto_20220713_1408'), + ('ecm', '0012_alter_eventdo_cate'), + ] + + operations = [ + migrations.AlterField( + model_name='event', + name='area', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='am.area', verbose_name='发生区域'), + ), + migrations.AlterField( + model_name='eventcate', + name='code', + field=models.CharField(max_length=20, unique=True, verbose_name='标识'), + ), + ] diff --git a/apps/ecm/models.py b/apps/ecm/models.py index 58111bb3..de6a7dac 100644 --- a/apps/ecm/models.py +++ b/apps/ecm/models.py @@ -15,7 +15,7 @@ class EventCate(CommonAModel): (10, '监控'), (20, '定位') ) - code = models.CharField('标识', max_length=10, unique=True) + code = models.CharField('标识', max_length=20, unique=True) name = models.CharField('名称', max_length=20, unique=True) priority = models.PositiveSmallIntegerField('优先级', default=1, help_text='1-99') trigger = models.PositiveSmallIntegerField('触发方式', default=10, choices=EVENT_TRIGGER_CHOICES) @@ -80,7 +80,7 @@ class Event(CommonBModel): cates = models.ManyToManyField(EventCate, verbose_name='关联事件种类', through='ecm.eventdo') face_img = models.CharField('人脸照', max_length=1000, null=True, blank=True) global_img = models.CharField('全景照', max_length=1000, null=True, blank=True) - area = models.ForeignKey(Area, verbose_name='发生区域', on_delete=models.CASCADE) + area = models.ForeignKey(Area, verbose_name='发生区域', on_delete=models.CASCADE, null=True, blank=True) vchannel = models.ForeignKey(TDevice, verbose_name='抓拍设备', on_delete=models.CASCADE, null=True, blank=True) location = models.JSONField('事件点位坐标', default=dict, null=False, blank=True) obj_cate = models.CharField('发生对象', max_length=20, help_text='people(人员)/...') diff --git a/apps/ecm/service.py b/apps/ecm/service.py index 1c9bc9db..3e8ff822 100644 --- a/apps/ecm/service.py +++ b/apps/ecm/service.py @@ -1,10 +1,11 @@ +from apps.opm.models import Operation, Opl, OplWorker from apps.utils.sms import send_sms import requests from apps.am.models import Access, Area from apps.am.tasks import cache_areas_info from apps.ecm.models import AlgoChannel, Event, EventCate, Eventdo, NotifySetting, Remind -from apps.hrm.models import Employee +from apps.hrm.models import ClockRecord, Employee from apps.system.models import User from apps.third.models import TDevice from apps.utils.queryset import get_child_queryset2, get_parent_queryset @@ -58,6 +59,8 @@ def get_ep_default(): """ return { 'area_fix_id': None, # 当前所在固定区域ID + 'area_temp_id': None, # 当前所在临时区域ID + 'xx_detail': {}, # 寻息定位的详细信息 'time0': None, # 定位首次出现时间戳 "time1": None, # 首次在该区域时间戳 "time2": int(time.time()), # 当前时间戳 @@ -82,8 +85,9 @@ def notify_event(event: Event): """ # 生成通知文本 ep = event.employee + obj_cate = event.obj_cate params = {'area': event.area.name, 'employee': '', 'event': ''} - if ep: + if obj_cate == 'people' and ep: ep_name = ep.name ep_type = '员工' if ep.type == 'rempoyee': @@ -91,8 +95,10 @@ def notify_event(event: Event): elif ep.type == 'visitor': ep_type = '访客' params['employee'] = ep_type + ep_name - else: + elif obj_cate == 'people': params['employee'] = '未知人员' + elif obj_cate == 'area': + params['employee'] = '区域' for i in event.cates.all(): params['event'] = params['event'] + i.name + ',' event.voice_msg = '位于{}的{},{}请及时处理'.format(params['area'], params['employee'], params['event']) @@ -113,10 +119,11 @@ def save_voice_and_speak(event: Event): v_p, v_num = main_cate.voice_person, main_cate.voice_num _, event.voice, _ = generate_voice(event.voice_msg, v_p) event.save() - sps = list(TDevice.objects.filter(area=event.area, type=TDevice.DEVICE_SPEAKER).values_list('code', flat=True)) - if len(sps) == 0: # 如果当前区域没有喇叭就找覆盖区的喇叭 - sps = list(TDevice.objects.filter(areas=event.area, - type=TDevice.DEVICE_SPEAKER).values_list('code', flat=True)) + if event.area: # 如果事件存在发生区域 + sps = list(TDevice.objects.filter(area=event.area, type=TDevice.DEVICE_SPEAKER).values_list('code', flat=True)) + if len(sps) == 0: # 如果当前区域没有喇叭就找覆盖区的喇叭 + sps = list(TDevice.objects.filter(areas=event.area, + type=TDevice.DEVICE_SPEAKER).values_list('code', flat=True)) # 查找固定喇叭 for m in event.cates.all(): for n in m.speakers.all(): @@ -212,7 +219,6 @@ def dispatch_dahua_event(data: dict): alarm_type = data['info']['alarmType'] vchannel = TDevice.objects.filter(code=vchannel_code).first() event = None - print(data) if alarm_type in [1001003, 1001000] and vchannel: # 内部人员/或陌生人报警 # 查看加载的算法 algo_codes = list(AlgoChannel.objects.filter(vchannel=vchannel).exclude( @@ -260,47 +266,37 @@ def dispatch_xunxi_event(data: dict): rail_out(data=data.data) elif data.type == 'onKeyAlarm': # 一键呼救 - one_key_alarm(data=data) + handle_xx_event(name='one_key_alarm', data=data) elif data.type == 'location': # 定位信息 loc_change(data=data) elif data.type == 'onOffLine': if data.data.online: # 标签定位在线 - pass + blt_online(data=data.data) else: # 标签定位离线 - pass + blt_offline(data=data.data) elif data.type == 'lowpower': # 低电量 - low_power(data=data.data) + handle_xx_event(name='low_power', data=data) elif data.type == 'bltOnOffLineV2': if data.data.online: # 标签通信在线 - blt_online(data=data.data) + pass else: # 标签通信离线 - blt_offline(data=data.data) + pass def rail_in(data): - """围栏进入事件 + """围栏进入 """ # 找到所在围栏 area = Area.objects.filter(third_info__xx_rail__id=data['railId']).first() # 找到进入对象 blts = TDevice.objects.filter(code=data['userId']).first() if area and blts and blts.employee: # 如果是人 - if area.type == Area.AREA_TYPE_FIX: # 如果是固定区域 - # 更新人员位置信息缓存 - key_str = 'ep_{}'.format(blts.employee.id) - ep_loc_dict = cache.get_or_set( - key_str, get_ep_default(), timeout=None - ) - if ep_loc_dict['area_fix_id'] != area.id: # 如果区域未变化则不动 - ep_loc_dict['time1'] = ep_loc_dict['time2'] - ep_loc_dict['area_fix_id'] = area.id - cache.set(key_str, ep_loc_dict) ep_blt = blts.employee # 标签绑定人员 if ep_blt: for i in Access.objects.filter(area=area).order_by('sort'): @@ -310,28 +306,28 @@ def rail_in(data): if ep_blt in eps_access and i.type == Access.ACCESS_IN_YES: return elif ep_blt in eps_access and i.type == Access.ACCESS_IN_NO: - # 触发非法进入事件 - pass + # 触发违规进入事件 + handle_xx_event_2('i_enter', ep=ep_blt, area=area) elif i.dept: # 如果是按部门设定的 if i.dept.type == 'dept': # 如果是内部部门 depts = get_child_queryset2(i.dept) if ep_blt.belong_dept in depts and i.type == Access.ACCESS_IN_YES: return elif ep_blt.belong_dept in depts and i.type == Access.ACCESS_IN_NO: - # 触发非法进入事件 - pass + # 触发违规进入事件 + handle_xx_event_2('i_enter', ep=ep_blt, area=area) elif i.dept.type == 'rparty': # 如果是相关方 if ep_blt.belong_dept == i.dept and i.type == Access.ACCESS_IN_YES: return elif ep_blt.belong_dept == i.dept and i.type == Access.ACCESS_IN_NO: - # 触发非法进入事件 - pass + # 触发违规进入事件 + handle_xx_event_2('i_enter', ep=ep_blt, area=area) elif i.employee: # 如果是按人设定的 if ep_blt == i.employee and i.type == Access.ACCESS_IN_YES: return elif ep_blt == i.employee and i.type == Access.ACCESS_IN_NO: - # 触发非法进入事件 - pass + # 触发违规进入事件 + handle_xx_event_2('i_enter', ep=ep_blt, area=area) # 通用权限设置过滤 if ep_blt.type == 'employee' and area.employee_yes: return @@ -340,72 +336,185 @@ def rail_in(data): elif ep_blt.type == 'visitor' and area.visitor_yes: return else: - # 触发非法进入事件 - pass - elif area and (not blts): - # 触发未知标签进入事件 - pass + # 触发违规进入事件 + handle_xx_event_2('i_enter', ep=ep_blt, area=area) + # elif area and (not blts): + # # 触发未知标签进入事件 + # e_i_enter(ep=ep_blt, area=area) def rail_out(data): pass -def low_power(data): - # 有绑定对象再提示低电量 - blts = TDevice.objects.filter(code=data['userId']).first() - if blts.employee: - # 触发低电量提醒事件 - pass - - -def one_key_alarm(data): - pass - - def loc_change(data): blts = TDevice.objects.filter(code=data['userId']).first() if blts.employee: # 从缓存查询人员位置信息 - time2 = int(time.time()) key_str = 'ep_{}'.format(blts.employee.id) ep_loc_dict = cache.get_or_set( key_str, get_ep_default(), timeout=None ) + ep_loc_dict['time2'] = int(time.time()) + ep_loc_dict['xx_detail'] = data + + area_fix, area_temp = get_area_from_point(data['xMillimeter'], data['yMillimeter'], data['floorNo']) + time2 = int(time.time()) + ep_loc_dict['area_temp_id'] = area_temp['id'] if area_temp else None ep_loc_dict['time2'] = time2 - # 从缓存里获取固定区域列表信息 - area_fix_list = cache.get('area_fix_list', None) - if not area_fix_list: - area_fix_list = cache_areas_info() - if ep_loc_dict.get('area_fix_id', None): - # 如果存在所在固定区域 - area_info = get_area_info_from_cache(ep_loc_dict['area_fix_id'], area_fix_list) - if area_info: - # 在该固定区域停留时间(分钟) - stay_minute = int((ep_loc_dict['time2']-ep_loc_dict['time1'])/60) - # 判断停留时间是否合理 - # 先通过自定义权限过滤(暂未做) - # 再经过通用设置过滤 - if 0 < stay_minute < area_info['stay_minute_min']: - # 触发离岗事件 - return - elif area_info['stay_minute_max'] < stay_minute: - # 触发超时滞留事件 - return + if area_fix and ep_loc_dict['area_fix_id'] == area_fix['id']: + # 如果停留在该区域 + cache.set(key_str, ep_loc_dict) + # 在该固定区域停留时间(分钟) + stay_minute = int((ep_loc_dict['time2']-ep_loc_dict['time1'])/60) + # 判断停留时间是否合理 + # 先通过自定义权限过滤(暂未做) + # 再经过通用设置过滤 + if 0 < stay_minute < area_fix['stay_minute_min']: + # 触发离岗事件 + handle_xx_event_2('leave_area', ep=blts.employee, area=Area.objects.get(id=area_fix['id'])) + elif area_fix['stay_minute_max'] < stay_minute: + # 触发超时滞留事件 + handle_xx_event_2('stand_area', ep=blts.employee, area=Area.objects.get(id=area_fix['id'])) else: - for i in area_fix_list: - if data['floorNo'] == i['floor_no']: - point = Point(data['xMillimeter'], data['yMillimeter']) - if i['polygon'].intersects(point): # 如果点在多边形中 - ep_loc_dict['time1'] = time2 - ep_loc_dict['are_id'] = i['area_fix_id'] - ep_loc_dict['time2'] = time2 - cache.set(key_str, ep_loc_dict) + ep_loc_dict['time1'] = time2 + ep_loc_dict['area_fix_id'] = area_fix['id'] if area_fix else None + cache.set(key_str, ep_loc_dict) + + +def handle_xx_event(name: str, data: dict): + # 有绑定对象再提示事件 + blts = TDevice.objects.filter(code=data['userId']).first() + if blts.employee: + # 触发事件 + cate = EventCate.objects.filter(code=name).first() + if cate: + event = Event() + # 查询定位信息 + key_str = 'ep_{}'.format(blts.employee.id) + ep_loc_dict = cache.get(key_str, None) + if ep_loc_dict: + if ep_loc_dict['area_fix_id']: + event.area = Area.objects.get(id=ep_loc_dict['area_fix_id']) + event.location = ep_loc_dict['xx_detail'] + event.obj_cate = 'people' + event.employee = blts.employee + event.happen_time = timezone.now() + event.save() + Eventdo.objects.get_or_create(cate=cate, event=event, defaults={ + 'cate': cate, + 'event': event + }) + notify_event(event) + + +def handle_xx_event_2(name: str, ep: Employee, area: Area): + # 违规进入事件特殊处理 + # 找寻该区域下审批和进行的作业, 本厂或相关方人员, 如是就不触发 + if name == 'i_enter' and ep.type in ['employee', 'remployee']: + ops = Operation.objects.filter(area=area, state__in=[Operation.OP_AUDIT, Operation.OP_WAIT, Operation.OP_WORK]) + if OplWorker.objects.filter(opl__operation__in=ops, worker__employee=ep).exists(): + # 如果是作业人员 + return + elif ops.filter(coordinator__employee=ep).exists(): + # 如果是协调员 + return + elif Opl.objects.filter(operation=ops, charger__employee=ep).exists(): + # 如果是作业负责人 + return + elif Opl.objects.filter(operation=ops, monitor__employee=ep).exists(): + # 如果是作业监护人 + return + cate = EventCate.objects.filter(code=name).first() + if cate: + event = Event() + event.area = area + # 查询定位信息 + key_str = 'ep_{}'.format(ep.id) + ep_loc_dict = cache.get(key_str, None) + if ep_loc_dict: + event.location = ep_loc_dict['xx_detail'] + event.obj_cate = 'people' + event.employee = ep + event.happen_time = timezone.now() + event.save() + Eventdo.objects.get_or_create(cate=cate, event=event, defaults={ + 'cate': cate, + 'event': event + }) + notify_event(event) def blt_online(data): - pass + # 定位在线 + blts = TDevice.objects.filter(code=data['userId']).first() + if blts.employee: + ep = blts.employee + if ep.type == 'employee' and ep.is_at_work is False: + # 上班打卡 + now = timezone.now() + cr_10 = ClockRecord.objects.filter(type=10, employee=ep, create_time__year=now.year, + create_time__month=now.month, create_time__day=now.day).first() + if cr_10: + if now < cr_10.create_by: + cr_10.create_by = now + cr_10.trigger = 'location' + cr_10.detail = data + cr_10.save() + else: + cr_10 = ClockRecord() + cr_10.type = 10 + cr_10.employee = ep + cr_10.trigger = 'location' + cr_10.detail = data + cr_10.save() + ep.is_at_work = True + ep.save() def blt_offline(data): - pass + blts = TDevice.objects.filter(code=data['userId']).first() + # 定位离线 + if blts.employee: + ep = blts.employee + if ep.type == 'employee' and ep.is_at_work: + # 下班打卡 + now = timezone.now() + cr_20 = ClockRecord.objects.filter(type=20, employee=ep, create_time__year=now.year, + create_time__month=now.month, create_time__day=now.day).first() + if cr_20: + if now > cr_20.create_by: + cr_20.create_by = now + cr_20.trigger = 'location' + cr_20.detail = data + cr_20.save() + else: + cr_20 = ClockRecord() + cr_20.type = 20 + cr_20.employee = ep + cr_20.trigger = 'location' + cr_20.detail = data + cr_20.save() + ep.is_at_work = False + ep.save() + + +def get_area_from_point(x: int, y: int, floorNo: str): + """ + 从位置信息获取所在固定区域 + 返回一个固定区域, 一个临时区域 + """ + area_fix = None + area_temp = None + area_list = cache.get('area_list', None) + if not area_list: + area_list = cache_areas_info() + point = Point(x, y) + for i in area_list: + if floorNo == i['floor_no']: + if i['polygon'].intersects(point): # 如果点在多边形中 + if i['type'] == Area.AREA_TYPE_FIX: + area_fix = i + elif i['type'] == Area.AREA_TYPE_TEMP: + area_temp = i + return area_fix, area_temp diff --git a/apps/ecm/tasks.py b/apps/ecm/tasks.py index 0da37ce0..d2f4ebc0 100644 --- a/apps/ecm/tasks.py +++ b/apps/ecm/tasks.py @@ -5,6 +5,8 @@ from threading import Thread from celery import shared_task from apps.am.models import Area +from apps.ecm.models import EventCate, Eventdo +from apps.ecm.service import notify_event from apps.third.clients import xxClient from apps.third.models import TDevice from apps.third.tapis import xxapis @@ -26,10 +28,25 @@ def update_count_people(i: Area): i.save() if i.count_people >= i.count_people_max: # 触发超员事件 - pass + handle_xx_event_3('over_man', i) elif i.count_people < i.count_people_min: # 触发缺员事件 - pass + handle_xx_event_3('lack_man', i) + + +def handle_xx_event_3(name: str, area: Area): + cate = EventCate.objects.filter(code=name).first() + if cate: + event = Event() + event.area = area + event.obj_cate = 'area' + event.happen_time = timezone.now() + event.save() + Eventdo.objects.get_or_create(cate=cate, event=event, defaults={ + 'cate': cate, + 'event': event + }) + notify_event(event) @shared_task @@ -50,5 +67,3 @@ def check_event_timeout(): if cate.hanle_minute > 0 and (timezone.now()-i.create_time).seconds > cate.hanle_minute * 60: i.is_timeout = True i.save() - - diff --git a/apps/hrm/filters.py b/apps/hrm/filters.py index 40900c3d..80ff15d7 100755 --- a/apps/hrm/filters.py +++ b/apps/hrm/filters.py @@ -10,7 +10,7 @@ class ClockRecordFilterSet(filters.FilterSet): class Meta: model = ClockRecord - fields = ['create_by', 'create_time_start', 'create_time_end', 'year', 'month'] + fields = ['employee', 'create_time_start', 'create_time_end', 'year', 'month'] def filter_year(self, queryset, name, value): return queryset.filter(create_time_date__year=value) diff --git a/apps/hrm/migrations/0006_auto_20220810_1512.py b/apps/hrm/migrations/0006_auto_20220810_1512.py new file mode 100644 index 00000000..de28c761 --- /dev/null +++ b/apps/hrm/migrations/0006_auto_20220810_1512.py @@ -0,0 +1,51 @@ +# Generated by Django 3.2.12 on 2022-08-10 07:12 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('hrm', '0005_alter_employee_phone'), + ] + + operations = [ + migrations.RemoveField( + model_name='clockrecord', + name='create_by', + ), + migrations.RemoveField( + model_name='clockrecord', + name='update_by', + ), + migrations.AddField( + model_name='clockrecord', + name='detail', + field=models.JSONField(blank=True, default=dict, verbose_name='相关记录'), + ), + migrations.AddField( + model_name='clockrecord', + name='employee', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='hrm.employee', verbose_name='对应人员'), + preserve_default=False, + ), + migrations.AddField( + model_name='clockrecord', + name='trigger', + field=models.CharField(default='door', max_length=20, verbose_name='触发'), + preserve_default=False, + ), + migrations.AlterField( + model_name='clockrecord', + name='type', + field=models.PositiveSmallIntegerField(choices=[(10, '上班打卡'), (20, '下班打卡')], default=10, verbose_name='打卡类型'), + ), + migrations.AlterField( + model_name='employee', + name='user', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='employee', to=settings.AUTH_USER_MODEL, verbose_name='系统账号'), + ), + ] diff --git a/apps/hrm/models.py b/apps/hrm/models.py index 82a53600..85b818a5 100755 --- a/apps/hrm/models.py +++ b/apps/hrm/models.py @@ -1,7 +1,7 @@ from django.db import models from apps.system.models import Post, User -from apps.utils.models import CommonADModel, CommonAModel, CommonBModel +from apps.utils.models import BaseModel, CommonADModel, CommonAModel, CommonBModel class Employee(CommonBModel): @@ -22,6 +22,7 @@ class Employee(CommonBModel): type = models.CharField('人员类型', default='employee', max_length=10, choices=PEOPLE_TYPE_CHOICES) user = models.OneToOneField(User, verbose_name='系统账号', + related_name='employee', on_delete=models.PROTECT, null=True, blank=True) name = models.CharField('姓名', max_length=20) phone = models.CharField('手机号', max_length=11, null=True, blank=True, unique=True) @@ -69,15 +70,20 @@ class NotWorkRemark(CommonAModel): remark = models.CharField('未打卡说明', null=True, blank=True, max_length=200) -class ClockRecord(CommonADModel): +class ClockRecord(BaseModel): """ 打卡记录 """ - ClOCK_WORK1 = 10 + ClOCK_ON = 10 + CLOCK_OFF = 20 type_choice = ( - (ClOCK_WORK1, '上班打卡'), + (ClOCK_ON, '上班打卡'), + (CLOCK_OFF, '下班打卡'), ) - type = models.PositiveSmallIntegerField('打卡类型', choices=type_choice, default=ClOCK_WORK1) + type = models.PositiveSmallIntegerField('打卡类型', choices=type_choice, default=ClOCK_ON) + employee = models.ForeignKey(Employee, verbose_name='对应人员', on_delete=models.CASCADE) + trigger = models.CharField('触发', max_length=20) + detail = models.JSONField('相关记录', default=dict, null=False, blank=True) class Certificate(CommonAModel): diff --git a/apps/hrm/services.py b/apps/hrm/services.py index 15f00c56..d6f1ce7e 100755 --- a/apps/hrm/services.py +++ b/apps/hrm/services.py @@ -1,10 +1,10 @@ -from apps.hrm.models import Employee +from apps.hrm.models import ClockRecord, Employee from apps.third.models import TDevice from apps.third.tapis import dhapis from apps.third.clients import dhClient from apps.utils.tools import rannum, ranstr from datetime import datetime - +from django.utils import timezone class HrmService: @@ -193,7 +193,53 @@ class HrmService: """ deviceCode = data['infoArray']['deviceCode'] device = TDevice.objects.filter(code=deviceCode).first() - if not device: - device = TDevice.objects.create(type=TDevice.DEVICE_DCHANNEL, code=deviceCode) - if device.is_clock: - pass + if device and device.is_clock: + # 如果设置为关联考勤 + if data['info']['extend']['enterOrExit'] == "1": + # 如果是进门 + id_number = data['info']['extend']['paperNumber'] + if id_number: + ep = Employee.objects.filter(id_number=id_number, type="employee").first() + # 如果是内部员工创建上班打卡记录(更新) + if ep and ep.is_at_work is False: + now = timezone.now() + cr_10 = ClockRecord.objects.filter(type=10, employee=ep, create_time__year=now.year, create_time__month=now.month, create_time__day=now.day).first() + if cr_10: + if now < cr_10.create_by: + cr_10.create_by = now + cr_10.trigger = 'door' + cr_10.detail = data['info']['extend'] + cr_10.save() + else: + cr_10 = ClockRecord() + cr_10.type = 10 + cr_10.employee = ep + cr_10.trigger = 'door' + cr_10.detail = data['info']['extend'] + cr_10.save() + ep.is_at_work = True + ep.save() + elif data['info']['extend']['enterOrExit'] == "2": + # 如果是出门 + id_number = data['info']['extend']['paperNumber'] + if id_number: + ep = Employee.objects.filter(id_number=id_number, type="employee").first() + # 如果是内部员工创建下班打卡记录(更新) + if ep and ep.is_at_work: + now = timezone.now() + cr_20 = ClockRecord.objects.filter(type=20, employee=ep, create_time__year=now.year, create_time__month=now.month, create_time__day=now.day).first() + if cr_20: + if now > cr_20.create_by: + cr_20.create_by = now + cr_20.trigger = 'door' + cr_20.detail = data['info']['extend'] + cr_20.save() + else: + cr_20 = ClockRecord() + cr_20.type = 20 + cr_20.employee = ep + cr_20.trigger = 'door' + cr_20.detail = data['info']['extend'] + cr_20.save() + ep.is_at_work = False + ep.save() diff --git a/apps/hrm/views.py b/apps/hrm/views.py index cd322c1b..b20de223 100755 --- a/apps/hrm/views.py +++ b/apps/hrm/views.py @@ -207,7 +207,7 @@ class ClockRecordViewSet(ListModelMixin, CustomGenericViewSet): authentication_classes = [] permission_classes = [AllowAny] queryset = ClockRecord.objects.all() - select_related_fields = ['create_by'] + select_related_fields = ['employee'] serializer_class = ClockRecordListSerializer filterset_class = ClockRecordFilterSet ordering = ['-pk'] diff --git a/apps/opm/services.py b/apps/opm/services.py index 9c740e2b..fac1d58f 100644 --- a/apps/opm/services.py +++ b/apps/opm/services.py @@ -61,4 +61,5 @@ def opl_audit_end(ticket: Ticket): op.save() # 授予相关工作人员区域进入权限 - worker_ep_ids = list(OplWorker.objects.filter(opl=opl).values_list('worker__id', flat=True)) + # worker_ep_ids = list(OplWorker.objects.filter(opl=opl).values_list('worker__id', flat=True)) + # 发送通知 diff --git a/apps/third/dahua.py b/apps/third/dahua.py index 00be94a5..e48f2250 100644 --- a/apps/third/dahua.py +++ b/apps/third/dahua.py @@ -9,8 +9,10 @@ from rest_framework.exceptions import APIException, ParseError from apps.third.errors import DH_REQUEST_ERROR from apps.third.models import Tlog +from apps.utils.my_rsa import encrypt_data from apps.utils.tools import print_roundtrip from django.utils.timezone import now +from apps.third.tapis import dhapis requests.packages.urllib3.disable_warnings() @@ -139,4 +141,18 @@ class DhClient: } if '$' in code: d_code = code.split('$')[0] - json_data['deviceCode'] = d_code \ No newline at end of file + json_data['deviceCode'] = d_code + + def get_password_token(self): + _, res = self.request(**dhapis['oauth_key']) + e_pwd = encrypt_data(settings.DAHUA_PASSWORD, res['publicKey']) + res['public_key'] = res['publicKey'] + res['password'] = e_pwd + res.update({ + "grant_type": "password", + "username": settings.DAHUA_USERNAME, + "client_id": settings.DAHUA_CLIENTID, + "client_secret": settings.DAHUA_SECRET, + }) + _, res2 = self.request(**dhapis['oauth_token'], json=res) + return res2 diff --git a/apps/third/tapis.py b/apps/third/tapis.py index 3c6899c1..3f3bbbcc 100755 --- a/apps/third/tapis.py +++ b/apps/third/tapis.py @@ -107,6 +107,14 @@ dhapis = { "close_door": { "url": "/evo-apigw/evo-accesscontrol/1.2.0/card/accessControl/channelControl/closeDoor", "method": "post" + }, + "oauth_key": { + "url": "/evo-apigw/evo-oauth/1.0.0/oauth/public-key", + "method": "get" + }, + "oauth_token": { + "url": "/evo-apigw/evo-oauth/1.0.0/oauth/extend/token", + "method": "post" } } @@ -143,7 +151,7 @@ xxapis = { "blt_info": { "url": "/api/devicesV3/bltInfoByMac", "method": "post" - } + }, } diff --git a/apps/third/views.py b/apps/third/views.py index b66a2d12..22ada7ca 100755 --- a/apps/third/views.py +++ b/apps/third/views.py @@ -27,9 +27,7 @@ class DahuaTestView(MyLoggingMixin, APIView): permission_classes = [IsAuthenticated] def get(self, request, *args, **kwargs): - _, res = dhClient.request( - url='/evo-apigw/evo-brm/1.0.0/person/subsystem/{}'.format(2059335), - method='get') + res = dhClient.get_password_token() return Response(res) diff --git a/apps/utils/my_rsa.py b/apps/utils/my_rsa.py new file mode 100644 index 00000000..0d755a1b --- /dev/null +++ b/apps/utils/my_rsa.py @@ -0,0 +1,13 @@ +import base64 +from Crypto.PublicKey import RSA +from Crypto.Hash import SHA +from Crypto.Signature import PKCS1_v1_5 as PKCS1_signature +from Crypto.Cipher import PKCS1_v1_5 as PKCS1_cipher + + +def encrypt_data(msg, pub_key): + pub_key = '-----BEGIN RSA PUBLIC KEY-----\n'+pub_key+'\n-----END RSA PUBLIC KEY-----' + public_key = RSA.importKey(pub_key) + cipher = PKCS1_cipher.new(public_key) + encrypt_text = base64.b64encode(cipher.encrypt(bytes(msg.encode("utf8")))) + return encrypt_text.decode('utf-8') diff --git a/apps/wf/services.py b/apps/wf/services.py index d33ad023..fffa5c6b 100755 --- a/apps/wf/services.py +++ b/apps/wf/services.py @@ -86,13 +86,6 @@ class WfService(object): nsteps_list.append(i) return nsteps_list - @classmethod - def get_ticket_transitions(cls, ticket: Ticket): - """ - 获取工单可执行的操作 - """ - return cls.get_state_transitions(ticket.state) - @classmethod def get_transition_by_args(cls, kwargs: dict): """ diff --git a/requirements.txt b/requirements.txt index 8ba8f73e..c2056036 100755 --- a/requirements.txt +++ b/requirements.txt @@ -27,3 +27,4 @@ requests==2.28.1 grpcio==1.47.0 grpcio-tools==1.47.0 protobuf==3.20.1 +pycryptodome==3.15.0