From 2030812c180d4389057f50283646bd58035f40b7 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Wed, 22 Nov 2023 16:07:00 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=83=BD=E7=AE=A1=E7=9A=84=E5=B7=A5?= =?UTF-8?q?=E6=AE=B5=E5=BC=82=E5=B8=B8=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/develop/views.py | 22 ++-- apps/enm/services.py | 39 ++++-- apps/enm/tasks.py | 10 +- apps/wpm/filters.py | 25 ++-- .../wpm/migrations/0034_auto_20231122_1551.py | 117 ++++++++++++++++++ apps/wpm/models.py | 46 +++---- apps/wpm/serializers.py | 72 +++++++++-- apps/wpm/services.py | 11 ++ apps/wpm/tasks.py | 44 +++---- apps/wpm/views.py | 25 ++-- 10 files changed, 305 insertions(+), 106 deletions(-) create mode 100644 apps/wpm/migrations/0034_auto_20231122_1551.py diff --git a/apps/develop/views.py b/apps/develop/views.py index 268c80f0..71e8bd6e 100755 --- a/apps/develop/views.py +++ b/apps/develop/views.py @@ -127,7 +127,7 @@ class CorrectViewSet(CustomGenericViewSet): 矫正工单流水号 """ - data = request.data + data = request.data from apps.wf.models import Ticket from apps.wf.services import WfService from django.utils.timezone import localtime @@ -150,10 +150,11 @@ class CorrectViewSet(CustomGenericViewSet): from apps.enm.models import Mpoint for mpoint in Mpoint.objects.exclude(mgroup=None): mgroup = mpoint.mgroup - mpoint.mgroups_allocate = [{'mgroup': mgroup.id, 'mgroup_name': mgroup.name, 'ratio': 1}] + mpoint.mgroups_allocate = [ + {'mgroup': mgroup.id, 'mgroup_name': mgroup.name, 'ratio': 1}] mpoint.save() return Response() - + @action(methods=['post'], detail=False, serializer_class=Serializer) def mpointstat(self, request, pk=None): """矫正测点统计数据 @@ -194,7 +195,7 @@ class CorrectViewSet(CustomGenericViewSet): item.shift = item.sflog.shift item.save() return Response() - + # @action(methods=['post'], detail=False, serializer_class=Serializer) # def global_img_compressed(self, request, pk=None): # """矫正全景压缩照片 @@ -209,6 +210,7 @@ class CorrectViewSet(CustomGenericViewSet): # event.save() # return Response() + class TestViewSet(CustomGenericViewSet): authentication_classes = () permission_classes = () @@ -261,7 +263,8 @@ class TestViewSet(CustomGenericViewSet): m = importlib.import_module(module) f = getattr(m, func) if vdata['delay']: - task = f.delay(*vdata.get('args', []), **vdata.get('kwargs', {})) # 同步执行 + task = f.delay(*vdata.get('args', []), ** + vdata.get('kwargs', {})) # 同步执行 return Response({'task_id': task.id}) else: f(*vdata.get('args', []), **vdata.get('kwargs', {})) @@ -299,7 +302,8 @@ class TestViewSet(CustomGenericViewSet): from apps.hrm.services import HrmService # data = {'id': 25198, 'category': 'alarm', 'method': 'alarm.msg', 'info': {'orgName': '河北省', 'nodeCode': '1000096$7$0$0', 'deviceCode': '1000096', 'alarmCode': 'd38c98b842334581a8219b3125ca72d5', 'alarmPicture': '6ad010cf-ce45-11ec-9715-e4246c7d1635/20221123/1/dsf_43aee9e6-6ae2-11ed-9872-e4246c7d1635_42018473_42037732.jpg', 'nodeType': '2', 'alarmDate': 1669187805, 'alarmGrade': 2, 'isSave': False, 'unitType': 7, 'extend': {'deptName': '设备管理部', 'deptIdList': [25], 'acsChannelCode': '1000096$7$0$0', 'maskState': 0, 'enterOrExit': 1, 'openTypeStr': '人脸刷门', 'swingTime': '2022-11-23 15:16:45', 'deviceName': '厂区门入', 'personCode': '8jtfoa', 'openType': '61', 'isOverTemp': False, 'orgCode': '001', 'paperNumber': '61012419850304335X', 'errorDetail': '', 'from': 'evo-accesscontrol', 'id': 1044994923113353216, 'beginTime': 1669187805000, 'acsChannelName': '厂区门入_门禁通道_1', 'cardStatus': '0', 'faceImageUrl': ['http://10.99.5.24:8927/6ad010cf-ce45-11ec-9715-e4246c7d1635/20221123/1/dsf_43aee9e6-6ae2-11ed-9872-e4246c7d1635_42018473_42037732.jpg'], 'orgName': '河北省', 'openFailedCode': 0, 'sex': 1, 'deptId': '25', 'cardType': 0, 'curTemp': 35.5, 'deviceCode': '1000096', 'personName': '谭刚位', 'personImg': '6ae577ee-ce45-11ec-bb54-e4246c7d1635/20221110/1/3313679c-6092-11ed-b963-e4246c7d1635.jpg', 'openResult': 1, 'personId': 673, 'recordImage1': '6ad010cf-ce45-11ec-9715-e4246c7d1635/20221123/1/dsf_43aee9e6-6ae2-11ed-9872-e4246c7d1635_42018473_42037732.jpg', 'category': '0', 'cardNumber': '0525871590', 'rfidType': 0, 'age': 0}, 'alarmType': 61, 'channelSeq': 0, 'orgCode': '001', 'channelName': '厂区门入_门禁通道_1', 'alarmStat': 1, 'isEvent': True}, 'subsystem': 'evo-accesscontrol', 'userIds': None, 'sid': None, 'domainId': None, 'infoArray': None, 'protocol': None} # data = {'id': 29870, 'category': 'alarm', 'method': 'alarm.msg', 'info': {'orgName': '河北省', 'nodeCode': '1002222$7$0$0', 'deviceCode': '1002222', 'alarmCode': '191f4a8d0a2c4b6282448af04df6ec11', 'alarmPicture': '6ad010cf-ce45-11ec-9715-e4246c7d1635/20221124/1/dsf_b8662018-6b9c-11ed-9872-e4246c7d1635_51799202_51816153.jpg', 'nodeType': '2', 'alarmDate': 1669263013, 'alarmGrade': 2, 'isSave': False, 'unitType': 7, 'extend': {'deptName': '安全生产部', 'deptIdList': [28], 'acsChannelCode': '1002222$7$0$0', 'maskState': 3, 'enterOrExit': 3, 'openTypeStr': '人脸刷门', 'swingTime': '2022-11-24 12:10:13', 'deviceName': '办公楼考勤面板', 'personCode': '13911097513', 'openType': '61', 'orgCode': '001', 'paperNumber': '110107196804040335', 'errorDetail': '', 'from': 'evo-accesscontrol', 'id': 1045310360703340544, 'beginTime': 1669263013000, 'acsChannelName': '办公楼考勤面板_门禁通道_1', 'cardStatus': '0', 'faceImageUrl': ['http://10.99.5.24:8927/6ad010cf-ce45-11ec-9715-e4246c7d1635/20221124/1/dsf_b8662018-6b9c-11ed-9872-e4246c7d1635_51799202_51816153.jpg'], 'orgName': '河北省', 'openFailedCode': 0, 'sex': 1, 'deptId': '28', 'cardType': 0, 'deviceCode': '1002222', 'personName': '刘静元', 'personImg': '6ae577ee-ce45-11ec-bb54-e4246c7d1635/20220907/1/d841d821-2e67-11ed-b073-e4246c7d1635.jpg', 'openResult': 1, 'personId': 237, 'recordImage1': '6ad010cf-ce45-11ec-9715-e4246c7d1635/20221124/1/dsf_b8662018-6b9c-11ed-9872-e4246c7d1635_51799202_51816153.jpg', 'category': '0', 'cardNumber': '5973291203', 'rfidType': 0, 'age': 0}, 'alarmType': 61, 'channelSeq': 0, 'orgCode': '001', 'channelName': '办公楼考勤面板_门禁通道_1', 'alarmStat': 1, 'isEvent': True}, 'subsystem': 'evo-accesscontrol', 'userIds': None, 'sid': None, 'domainId': None, 'infoArray': None, 'protocol': None} - data = {'id': 70781, 'category': 'alarm', 'method': 'alarm.msg', 'info': {'orgName': '河北省', 'nodeCode': '1002222$7$0$0', 'deviceCode': '1002222', 'alarmCode': '74ac9b9511774388a975dc5daa6ad8b9', 'alarmPicture': '6ad010cf-ce45-11ec-9715-e4246c7d1635/20230419/1/dsf_93e4c0ab-de83-11ed-af40-e4246c7d1635_45533104_45552146.jpg', 'nodeType': '2', 'alarmDate': 1681897834, 'alarmGrade': 2, 'isSave': False, 'unitType': 7, 'extend': {'deptName': '企业管理部', 'deptIdList': [21], 'acsChannelCode': '1002222$7$0$0', 'maskState': 0, 'enterOrExit': 3, 'openTypeStr': '人脸刷门', 'swingTime': '2023-04-19 17:50:34', 'deviceName': '办公楼考勤面板', 'personCode': '13731293508', 'openType': '61', 'orgCode': '001', 'paperNumber': '130634198612035821', 'errorDetail': '', 'from': 'evo-accesscontrol', 'id': 1098304215199125504, 'beginTime': 1681897834000, 'acsChannelName': '办公楼考勤面板_门禁通道_1', 'cardStatus': '0', 'faceImageUrl': ['http://10.99.5.24:8927/6ad010cf-ce45-11ec-9715-e4246c7d1635/20230419/1/dsf_93e4c0ab-de83-11ed-af40-e4246c7d1635_45533104_45552146.jpg'], 'orgName': '河北省', 'openFailedCode': 0, 'sex': 1, 'deptId': '21', 'cardType': 0, 'deviceCode': '1002222', 'personName': '李贝', 'personImg': '6ae577ee-ce45-11ec-bb54-e4246c7d1635/20220907/1/9ab9a1eb-2e74-11ed-b073-e4246c7d1635.jpg', 'openResult': 1, 'personId': 331, 'recordImage1': '6ad010cf-ce45-11ec-9715-e4246c7d1635/20230419/1/dsf_93e4c0ab-de83-11ed-af40-e4246c7d1635_45533104_45552146.jpg', 'category': '0', 'cardNumber': '6705501569', 'rfidType': 0, 'age': 0}, 'alarmType': 61, 'channelSeq': 0, 'orgCode': '001', 'channelName': '办公楼考勤面板_门禁通道_1', 'alarmStat': 1, 'isEvent': True}, 'subsystem': 'evo-accesscontrol', 'userIds': None, 'sid': None, 'domainId': None, 'infoArray': None, 'protocol': None} + data = {'id': 70781, 'category': 'alarm', 'method': 'alarm.msg', 'info': {'orgName': '河北省', 'nodeCode': '1002222$7$0$0', 'deviceCode': '1002222', 'alarmCode': '74ac9b9511774388a975dc5daa6ad8b9', 'alarmPicture': '6ad010cf-ce45-11ec-9715-e4246c7d1635/20230419/1/dsf_93e4c0ab-de83-11ed-af40-e4246c7d1635_45533104_45552146.jpg', 'nodeType': '2', 'alarmDate': 1681897834, 'alarmGrade': 2, 'isSave': False, 'unitType': 7, 'extend': {'deptName': '企业管理部', 'deptIdList': [21], 'acsChannelCode': '1002222$7$0$0', 'maskState': 0, 'enterOrExit': 3, 'openTypeStr': '人脸刷门', 'swingTime': '2023-04-19 17:50:34', 'deviceName': '办公楼考勤面板', 'personCode': '13731293508', 'openType': '61', 'orgCode': '001', 'paperNumber': '130634198612035821', 'errorDetail': '', 'from': 'evo-accesscontrol', 'id': 1098304215199125504, 'beginTime': 1681897834000, 'acsChannelName': '办公楼考勤面板_门禁通道_1', 'cardStatus': '0', 'faceImageUrl': [ + 'http://10.99.5.24:8927/6ad010cf-ce45-11ec-9715-e4246c7d1635/20230419/1/dsf_93e4c0ab-de83-11ed-af40-e4246c7d1635_45533104_45552146.jpg'], 'orgName': '河北省', 'openFailedCode': 0, 'sex': 1, 'deptId': '21', 'cardType': 0, 'deviceCode': '1002222', 'personName': '李贝', 'personImg': '6ae577ee-ce45-11ec-bb54-e4246c7d1635/20220907/1/9ab9a1eb-2e74-11ed-b073-e4246c7d1635.jpg', 'openResult': 1, 'personId': 331, 'recordImage1': '6ad010cf-ce45-11ec-9715-e4246c7d1635/20230419/1/dsf_93e4c0ab-de83-11ed-af40-e4246c7d1635_45533104_45552146.jpg', 'category': '0', 'cardNumber': '6705501569', 'rfidType': 0, 'age': 0}, 'alarmType': 61, 'channelSeq': 0, 'orgCode': '001', 'channelName': '办公楼考勤面板_门禁通道_1', 'alarmStat': 1, 'isEvent': True}, 'subsystem': 'evo-accesscontrol', 'userIds': None, 'sid': None, 'domainId': None, 'infoArray': None, 'protocol': None} HrmService.swipe(data=data) return Response() @@ -572,6 +576,6 @@ class TestViewSet(CustomGenericViewSet): @action(methods=['post'], detail=False, serializer_class=Serializer, permission_classes=[]) def test_cal(self, request, pk=None): - from apps.wpm.tasks import cal_shut_hour - cal_shut_hour('3397169058570170368') - return Response() \ No newline at end of file + from apps.wpm.tasks import cal_exp_duration_hour + cal_exp_duration_hour('3397169058570170368') + return Response() diff --git a/apps/enm/services.py b/apps/enm/services.py index d8ec25b1..8904505e 100644 --- a/apps/enm/services.py +++ b/apps/enm/services.py @@ -14,7 +14,8 @@ def translate_eval_formula(exp_str: str, year: int, month: int, day: int, hour: pattern = r"\${(.*?)}" matches = re.findall(pattern, exp_str) for match in matches: - mpst = MpointStat.objects.filter(Q(mpoint__id=match)|Q(mpoint__name=match)|Q(mpoint__code=match), type='hour', year=year, month=month, day=day, hour=hour).first() + mpst = MpointStat.objects.filter(Q(mpoint__id=match) | Q(mpoint__name=match) | Q( + mpoint__code=match), type='hour', year=year, month=month, day=day, hour=hour).first() if mpst: exp_str = exp_str.replace(f"${{{match}}}", str(mpst.val)) rval = eval(exp_str) @@ -55,7 +56,7 @@ def get_day_s(year: int, month: int, day: int, hour: int, hour_split: int = 21): # month_s = data['month_s'] # for item in data['qua_data']: # qua_rate[f'{item["material_name"]}_{item["testitem_name"]}'] = item["rate_pass"] - + # goal_dict = get_mgroup_goals(data['mgroup'], data['year_s'], False) # goal_data = {} # try: @@ -76,27 +77,43 @@ def get_day_s(year: int, month: int, day: int, hour: int, hour_split: int = 21): # print(traceback.format_exc()) # return goal_data, score + def shutdown_or_startup(mplog: MpLog): from apps.wpm.models import StLog - from apps.wpm.tasks import cal_shut_hour + from apps.wpm.tasks import cal_exp_duration_hour + from apps.wpm.services import get_sflog mpoint = mplog.mpoint mgroup = mpoint.mgroup - last_stlog = StLog.objects.filter(mgroup=mgroup).order_by('start_time').last() + last_stlog = StLog.objects.filter( + mgroup=mgroup, is_shutdown=True).order_by('start_time').last() # 找到最后一次停机记录 if last_stlog: if mplog.tag_update >= last_stlog.start_time: # 认为是有效信号 - if last_stlog.end_time is None and mplog.tag_val==1: + if last_stlog.end_time is None and mplog.tag_val == 1: # 从停到开 last_stlog.end_time = mplog.tag_update - last_stlog.duration = (last_stlog.end_time - last_stlog.start_time).total_seconds()/3600 + last_stlog.duration = ( + last_stlog.end_time - last_stlog.start_time).total_seconds()/3600 last_stlog.save() mgroup.is_runing = True mgroup.save() - cal_shut_hour(last_stlog.id) # 触发停机时间分配 - elif last_stlog.end_time and mplog.tag_val==0 and mplog.tag_update > last_stlog.end_time: - StLog.objects.create(mgroup=mgroup, end_time=None, start_time=mplog.tag_update) + cal_exp_duration_hour(last_stlog.id) # 触发时间分配 + elif last_stlog.end_time and mplog.tag_val == 0 and mplog.tag_update > last_stlog.end_time: # 从开到停 + StLog.objects.create( + title='停机', + is_shutdown=True, + mgroup=mgroup, + end_time=None, + start_time=mplog.tag_update, + sflog=get_sflog(mgroup, mplog.tag_update) + ) mgroup.is_runing = False mgroup.save() else: - StLog.objects.create(mgroup=mgroup, end_time=None, start_time=mplog.tag_update) + StLog.objects.create( + title='停机', + is_shutdown=True, + mgroup=mgroup, + end_time=None, + start_time=mplog.tag_update, + sflog=get_sflog(mgroup, mplog.tag_update)) mgroup.is_runing = False mgroup.save() - \ No newline at end of file diff --git a/apps/enm/tasks.py b/apps/enm/tasks.py index b6ebe627..576f3ee2 100644 --- a/apps/enm/tasks.py +++ b/apps/enm/tasks.py @@ -12,7 +12,7 @@ import datetime from django.db.models import Sum, Avg from dateutil import tz from django.conf import settings -from apps.wpm.services import make_sflogs +from apps.wpm.services import get_sflog from apps.mtm.models import Mgroup, Material from apps.fim.services import get_cost_unit, get_price_unit from apps.fim.models import Fee @@ -156,13 +156,7 @@ def cal_mpointstat_hour(mpointId: str, year: int, month: int, day: int, hour: in mgroup = Mgroup.objects.get(id=allocate['mgroup']) ratio = allocate['ratio'] # 查找并绑定值班记录 - sflog = SfLog.objects.filter( - start_time__lt=dt, end_time__gte=dt, mgroup=mgroup).first() - if sflog is None: # 需要创建值班记录 - make_sflogs(mgroup=mgroup, start_date=( - dt-datetime.timedelta(days=1)).date(), end_date=dt.date()) - sflog = SfLog.objects.filter( - start_time__lt=dt, end_time__gte=dt, mgroup=mgroup).first() + sflog = get_sflog(mgroup, dt) year_s, month_s, day_s = sflog.get_ymd params_hour_s = {'type': 'hour_s', 'mpoint': mpoint, 'sflog': sflog, 'mgroup': mgroup, 'year': year, diff --git a/apps/wpm/filters.py b/apps/wpm/filters.py index 1bb803fd..1beff80d 100644 --- a/apps/wpm/filters.py +++ b/apps/wpm/filters.py @@ -1,5 +1,5 @@ from django_filters import rest_framework as filters -from apps.wpm.models import SfLog, SfLogExp, WMaterial, Mlog, Handover +from apps.wpm.models import SfLog, StLog, WMaterial, Mlog, Handover ['mgroup', 'shift', 'team', 'leader', 'team__belong_dept'] @@ -17,27 +17,18 @@ class SfLogFilter(filters.FilterSet): } -class SfLogExpFilter(filters.FilterSet): - is_st = filters.BooleanFilter(method='filter_is_st', label='是否停机') - +class StLogFilter(filters.FilterSet): class Meta: - model = SfLogExp + model = StLog fields = { + "mgroup": ["exact"], + "mgroup__name": ["exact"], + "start_time": ["day", "month", "year"], + "end_time": ["isnull"], "sflog": ["exact"], - "sflog__mgroup": ["exact"], - "sflog__mgroup__name": ["exact"], - "happen_time": ["day", "month", "year"], - "sflog__end_time": ["day", "month", "year"], - "stlog": ["exact"], - "stlog__start_time": ["day", "month", "year"], - "stlog__end_time": ["day", "month", "year"] + "sflogs": ["exact"] } - def filter_is_st(self, queryset, name, value): - if value: - return queryset.exclude(stlog=None) - return queryset.filter(stlog=None) - class WMaterialFilter(filters.FilterSet): class Meta: diff --git a/apps/wpm/migrations/0034_auto_20231122_1551.py b/apps/wpm/migrations/0034_auto_20231122_1551.py new file mode 100644 index 00000000..29f63f81 --- /dev/null +++ b/apps/wpm/migrations/0034_auto_20231122_1551.py @@ -0,0 +1,117 @@ +# Generated by Django 3.2.12 on 2023-11-22 07:51 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('wpm', '0033_sflog_total_hour'), + ] + + operations = [ + migrations.RemoveField( + model_name='sflogexp', + name='cate', + ), + migrations.RemoveField( + model_name='sflogexp', + name='handler', + ), + migrations.RemoveField( + model_name='sflogexp', + name='happen_time', + ), + migrations.RemoveField( + model_name='sflogexp', + name='is_current_down', + ), + migrations.RemoveField( + model_name='sflogexp', + name='measure', + ), + migrations.RemoveField( + model_name='sflogexp', + name='reason', + ), + migrations.RemoveField( + model_name='sflogexp', + name='title', + ), + migrations.AddField( + model_name='sflogexp', + name='note', + field=models.TextField(blank=True, default='', verbose_name='处理备注'), + ), + migrations.AddField( + model_name='stlog', + name='cate', + field=models.CharField(blank=True, max_length=10, null=True, verbose_name='原因类别'), + ), + migrations.AddField( + model_name='stlog', + name='handler', + field=models.CharField(default='', max_length=100, verbose_name='处理人'), + ), + migrations.AddField( + model_name='stlog', + name='is_shutdown', + field=models.BooleanField(default=False, verbose_name='是否是停机'), + ), + migrations.AddField( + model_name='stlog', + name='measure', + field=models.TextField(default='', max_length=100, verbose_name='处置措施'), + ), + migrations.AddField( + model_name='stlog', + name='reason', + field=models.TextField(default='', max_length=100, verbose_name='事件原因'), + ), + migrations.AddField( + model_name='stlog', + name='sflog', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='wpm.sflog', verbose_name='发生时所在值班'), + ), + migrations.AddField( + model_name='stlog', + name='sflogs', + field=models.ManyToManyField(related_name='stlog_sflogs', through='wpm.SfLogExp', to='wpm.SfLog', verbose_name='关联所有当班'), + ), + migrations.AddField( + model_name='stlog', + name='title', + field=models.CharField(default='', max_length=20, verbose_name='异常名称'), + ), + migrations.AlterField( + model_name='sflog', + name='stlogs', + field=models.ManyToManyField(related_name='sflog_stlogs', through='wpm.SfLogExp', to='wpm.StLog', verbose_name='关联异常记录'), + ), + migrations.AlterField( + model_name='sflogexp', + name='duration', + field=models.FloatField(blank=True, null=True, verbose_name='持续时长(h)'), + ), + migrations.AlterField( + model_name='sflogexp', + name='stlog', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wpm.stlog', verbose_name='关联异常记录'), + ), + migrations.AlterField( + model_name='stlog', + name='duration', + field=models.FloatField(blank=True, null=True, verbose_name='持续时间(h)'), + ), + migrations.AlterField( + model_name='stlog', + name='end_time', + field=models.DateTimeField(blank=True, null=True, verbose_name='结束时间'), + ), + migrations.AlterField( + model_name='stlog', + name='start_time', + field=models.DateTimeField(verbose_name='发生时间'), + ), + ] diff --git a/apps/wpm/models.py b/apps/wpm/models.py index 4b3feb82..315c5775 100644 --- a/apps/wpm/models.py +++ b/apps/wpm/models.py @@ -10,17 +10,6 @@ from apps.system.models import Dept # Create your models here. -class StLog(CommonADModel): - """ - 停机记录 - """ - mgroup = models.ForeignKey( - Mgroup, verbose_name='关联工段', on_delete=models.CASCADE) - start_time = models.DateTimeField('停机开始') - end_time = models.DateTimeField('停机结束', null=True, blank=True) - duration = models.FloatField('停机时长(h)', null=True, blank=True) - - class SfLog(CommonADModel): """值班记录 """ @@ -37,7 +26,7 @@ class SfLog(CommonADModel): end_time = models.DateTimeField('值班结束') note = models.TextField('其他备注', null=True, blank=True) stlogs = models.ManyToManyField( - 'wpm.stlog', verbose_name='关联停机记录', through='wpm.sflogexp') + 'wpm.stlog', verbose_name='关联异常记录', through='wpm.sflogexp', related_name='sflog_stlogs') last_test_time = models.DateTimeField('最后质检时间', null=True, blank=True) total_hour_now = models.FloatField('总时长动', default=0) total_hour = models.FloatField('总时长', default=12) @@ -53,22 +42,37 @@ class SfLog(CommonADModel): return end_time_local.year, end_time_local.month, end_time_local.day -class SfLogExp(CommonADModel): +class StLog(CommonADModel): """ - 生产异常情况记录 + 异常记录 """ + title = models.CharField('异常名称', max_length=20, default='') + is_shutdown = models.BooleanField('是否是停机', default=False) + mgroup = models.ForeignKey( + Mgroup, verbose_name='关联工段', on_delete=models.CASCADE) sflog = models.ForeignKey( - SfLog, on_delete=models.CASCADE, verbose_name='关联值班记录') - stlog = models.ForeignKey( - StLog, verbose_name='关联停机记录', on_delete=models.CASCADE, null=True, blank=True) - title = models.CharField('异常名称', max_length=20) - happen_time = models.DateTimeField('发生时间', null=True, blank=True) + SfLog, on_delete=models.CASCADE, verbose_name='发生时所在值班', null=True, blank=True) + sflogs = models.ManyToManyField( + 'wpm.sflog', verbose_name='关联所有当班', through='wpm.sflogexp', related_name='stlog_sflogs') + start_time = models.DateTimeField('发生时间') + end_time = models.DateTimeField('结束时间', null=True, blank=True) + duration = models.FloatField('持续时间(h)', null=True, blank=True) cate = models.CharField('原因类别', max_length=10, null=True, blank=True) reason = models.TextField('事件原因', default='', max_length=100) measure = models.TextField('处置措施', default='', max_length=100) handler = models.CharField('处理人', default='', max_length=100) - is_current_down = models.BooleanField('是否本班停机', default=False) - duration = models.FloatField('停机时长(h)', null=True, blank=True) + + +class SfLogExp(CommonADModel): + """ + 异常处理 + """ + sflog = models.ForeignKey( + SfLog, on_delete=models.CASCADE, verbose_name='关联值班记录') + stlog = models.ForeignKey( + StLog, verbose_name='关联异常记录', on_delete=models.CASCADE) + duration = models.FloatField('持续时长(h)', null=True, blank=True) + note = models.TextField('处理备注', default='', blank=True) class WMaterial(CommonBDModel): diff --git a/apps/wpm/serializers.py b/apps/wpm/serializers.py index 435d817e..5526b2b3 100644 --- a/apps/wpm/serializers.py +++ b/apps/wpm/serializers.py @@ -1,13 +1,15 @@ from apps.utils.constants import EXCLUDE_FIELDS from apps.utils.serializers import CustomModelSerializer from rest_framework import serializers -from rest_framework.exceptions import ValidationError +from rest_framework.exceptions import ValidationError, ParseError +from datetime import datetime from .models import SfLog, StLog, SfLogExp, WMaterial, Mlog, Handover, Mlogb, AttLog from apps.system.models import Dept, User from apps.system.serializers import UserSimpleSerializer from apps.pm.models import Mtask -from apps.wpm.tasks import cal_enstat_when_pcoal_heat_change, cal_enstat_when_team_change +from apps.wpm.tasks import cal_enstat_when_pcoal_heat_change, cal_enstat_when_team_change, cal_exp_duration_hour +from apps.wpm.services import get_sflog from apps.mtm.models import Mgroup, TeamMember, Shift from apps.mtm.serializers import MaterialSimpleSerializer from django.db import transaction @@ -16,10 +18,62 @@ from django.utils import timezone class StLogSerializer(CustomModelSerializer): mgroup_name = serializers.CharField(source='mgroup.name', read_only=True) + current_sflog = serializers.CharField(label='当前处理值班', write_only=True) + current_note = serializers.CharField( + label='值班处理备注', write_only=True, allow_blank=True) class Meta: model = StLog fields = '__all__' + read_only_fields = EXCLUDE_FIELDS + \ + ['is_shutdown', 'sflog', 'duration'] + + def create(self, validated_data): + current_sflog = validated_data.pop('current_sflog') + current_note = validated_data.pop('note', '') + with transaction.atomic(): + validated_data['sflog'] = get_sflog( + validated_data['mgroup'], validated_data['start_time']) + if current_sflog != validated_data['sflog']: + raise ParseError('值班时间与发生时间不一致') + instance = super().create(validated_data) + SfLogExp.objects.create( + sflog=current_sflog, stlog=instance, create_by=self.context['request'].user, note=current_note) + return instance + + def update(self, instance, validated_data): + if instance.is_shutdown: # 停机不可编辑end_time + validated_data.pop('end_time', None) + if instance.end_time: + raise ParseError('该异常已结束无需编辑') + with transaction.atomic(): + validated_data.pop('mgroup', None) + validated_data.pop('start_time', None) + end_time = validated_data.pop('end_time', None) + if end_time: # 需要把涉及到的sflog都拉入 + cal_exp_duration_hour(instance.id) + current_sflog = validated_data.pop('current_sflog') + current_note = validated_data.pop('current_note', '') + instance = super().update(instance, validated_data) + try: + sflogexp = SfLogExp.objects.get( + stlog=instance.stlog, sflog=current_sflog) + sflogexp.note = current_note + sflogexp.save() + except SfLogExp.DoesNotExist: + raise ParseError('该异常无需本班填写') + return instance + + def validate(self, attrs): + start_time: datetime = attrs['start_time'] + end_time: datetime = attrs.get('end_time', None) + if end_time: + if end_time > start_time: + attrs['duration'] = ( + end_time - start_time).total_seconds / 3600 + else: + raise ParseError('结束时间应大于开始时间') + return super().validate(attrs) class SfLogSerializer(CustomModelSerializer): @@ -68,17 +122,17 @@ class SfLogSerializer(CustomModelSerializer): class SflogExpSerializer(CustomModelSerializer): - mgroup = serializers.CharField(source='sflog.mgroup.id', read_only=True) - mgroup_name = serializers.CharField( - source='sflog.mgroup.name', read_only=True) - stlog_ = StLogSerializer(source='stlog', read_only=True) - happen_time = serializers.DateTimeField(required=True, label='发生时间') - cate = serializers.CharField(required=True, label='原因类别') + # mgroup = serializers.CharField(source='sflog.mgroup.id', read_only=True) + # mgroup_name = serializers.CharField( + # source='sflog.mgroup.name', read_only=True) + # stlog_ = StLogSerializer(source='stlog', read_only=True) + # happen_time = serializers.DateTimeField(required=True, label='发生时间') + # cate = serializers.CharField(required=True, label='原因类别') class Meta: model = SfLogExp fields = '__all__' - read_only_fields = EXCLUDE_FIELDS + ['stlog', 'is_current_down'] + read_only_fields = EXCLUDE_FIELDS + ['sflog', 'stlog', 'duration'] class WMaterialSerializer(CustomModelSerializer): diff --git a/apps/wpm/services.py b/apps/wpm/services.py index c77f2fe7..a09eeed8 100644 --- a/apps/wpm/services.py +++ b/apps/wpm/services.py @@ -16,6 +16,17 @@ from apps.mtm.models import Mgroup, Shift, Material, Route from .models import SfLog, SfLogExp, WMaterial, Mlog, Mlogb, Handover +def get_sflog(mgroup: Mgroup, happen_time: datetime): + sflog = SfLog.objects.filter( + start_time__lt=happen_time, end_time__gte=happen_time, mgroup=mgroup).first() + if sflog is None: # 需要创建值班记录 + make_sflogs(mgroup=mgroup, start_date=( + happen_time-datetime.timedelta(days=1)).date(), end_date=happen_time.date()) + sflog = SfLog.objects.filter( + start_time__lt=happen_time, end_time__gte=happen_time, mgroup=mgroup).first() + return sflog + + def make_sflogs(mgroup: Mgroup, start_date: datetime.date, end_date: datetime.date, create_by=None): shift_rule = mgroup.shift_rule shifts = Shift.objects.filter(rule=shift_rule) # 根据排班规则制定排班记录 diff --git a/apps/wpm/tasks.py b/apps/wpm/tasks.py index a5e70a0d..378ca7d4 100644 --- a/apps/wpm/tasks.py +++ b/apps/wpm/tasks.py @@ -58,33 +58,34 @@ def get_total_hour_now(sflogId: str): @shared_task(base=CustomTask) -def cal_shut_hour(stlogId: str): +def cal_exp_duration_hour(stlogId: str): """ - 计算停机记录对应的班停时长 + 计算异常记录对应的每班持续时间 """ - from apps.enm.tasks import cal_enstat + from apps.enm.tasks import cal_enstat # 如果是停机需要进行统计停机时长 if stlogId: stlogs = StLog.objects.filter(id=stlogId) - else: + else: # 不传就默认更新所有的 stlogs = StLog.objects.filter(end_time=None) now = timezone.now() for stlog in stlogs: + need_cal_enstat = True + if stlog.is_shutdown is False: + need_cal_enstat = False st_start = stlog.start_time if st_start >= now: break - if stlog.end_time is None: # 说明停机还未结束,此时也需要计算duration + if stlog.end_time is None: # 说明异常还未结束,此时也需要计算duration st_end = now else: st_end = stlog.end_time sf_qs = SfLog.objects.filter(mgroup=stlog.mgroup) sf_qs = (sf_qs.filter(start_time__gte=st_start, start_time__lt=st_end) | sf_qs.filter(end_time__gt=st_start, end_time__lte=st_end) | sf_qs.filter(start_time__lte=st_start, end_time__gte=st_end)).order_by('start_time').distinct() + SfLogExp.objects.filter(stlog=stlog, sflog__in=sf_qs).delete() for ind, sflog in enumerate(sf_qs): - is_current_down = False - if ind == 0: - is_current_down = True sflogexp, _ = SfLogExp.objects.get_or_create(stlog=stlog, sflog=sflog, defaults={ - 'stlog': stlog, 'sflog': sflog, 'is_current_down': is_current_down, 'title': '停机'}) + 'stlog': stlog, 'sflog': sflog}) # 计算duration sf_end, sf_start = sflog.end_time, sflog.start_time duration_item_delta = min(sf_end, st_end) - max(sf_start, st_start) @@ -95,18 +96,19 @@ def cal_shut_hour(stlogId: str): duration_item = total_seconds/3600 sflogexp.duration = duration_item sflogexp.save() - # 计算每班的总停机时间 - ret = SfLogExp.objects.filter(sflog=sflog).exclude( - stlog=None).aggregate(sum=Sum('duration')) - if ret.get('sum', 0): - sflog.shut_hour = ret['sum'] - sflog.save() - # 更新sflog总时长 - if sflog.end_time > now: - get_total_hour_now(sflog.id) - if stlogId: - cal_enstat('sflog', sflog.id, sflog.mgroup.id, None, None, None, - None, None, None, None, cascade=True, cal_attrs=['run_hour']) + if need_cal_enstat: + # 计算每班的总停机时间 + ret = SfLogExp.objects.filter( + sflog=sflog, stlog__is_shutdown=True).aggregate(sum=Sum('duration')) + if ret.get('sum', 0): + sflog.shut_hour = ret['sum'] + sflog.save() + # 更新sflog总时长 + if sflog.end_time > now: + get_total_hour_now(sflog.id) + if stlogId: + cal_enstat('sflog', sflog.id, sflog.mgroup.id, None, None, None, + None, None, None, None, cascade=True, cal_attrs=['run_hour']) @shared_task(base=CustomTask) diff --git a/apps/wpm/views.py b/apps/wpm/views.py index 346d6173..bc41f5bc 100644 --- a/apps/wpm/views.py +++ b/apps/wpm/views.py @@ -13,7 +13,7 @@ from apps.pm.models import Mtask from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet from apps.utils.mixins import BulkCreateModelMixin -from .filters import SfLogExpFilter, SfLogFilter, WMaterialFilter, MlogFilter, HandoverFilter +from .filters import StLogFilter, SfLogFilter, WMaterialFilter, MlogFilter, HandoverFilter from .models import SfLog, SfLogExp, StLog, WMaterial, Mlog, Handover, Mlogb, AttLog from .serializers import (SflogExpSerializer, SfLogSerializer, StLogSerializer, WMaterialSerializer, MlogSerializer, MlogRelatedSerializer, DeptBatchSerializer, HandoverSerializer, @@ -22,19 +22,24 @@ from .services import mlog_submit, update_mtask, handover_submit # Create your views here. -class StLogViewSet(ListModelMixin, CustomGenericViewSet): +class StLogViewSet(CustomModelViewSet): """ - list:停机记录 + list:异常记录 - 停机记录 + 异常记录 """ - perms_map = {'get': '*'} queryset = StLog.objects.all() serializer_class = StLogSerializer select_related_fields = ['mgroup'] - filterset_fields = ['mgroup'] + filterset_class = StLogFilter ordering = ['-start_time'] + def destroy(self, request, *args, **kwargs): + obj: StLog = self.get_object() + if obj.is_shutdown: + raise ParseError('停机记录不可删除') + return super().destroy(request, *args, **kwargs) + class SfLogViewSet(UpdateModelMixin, ListModelMixin, DestroyModelMixin, CustomGenericViewSet): """ @@ -76,16 +81,16 @@ class SfLogViewSet(UpdateModelMixin, ListModelMixin, DestroyModelMixin, CustomGe return Response(sr.data) -class SfLogExpViewSet(CustomModelViewSet): +class SfLogExpViewSet(ListModelMixin, UpdateModelMixin, CustomGenericViewSet): """ - list:生产异常动态 + list:异常值班处理 - 生产异常动态 + 异常值班处理 """ queryset = SfLogExp.objects.all() serializer_class = SflogExpSerializer select_related_fields = ['sflog', 'sflog__mgroup', 'stlog'] - filterset_class = SfLogExpFilter + filterset_fields = ['sflog', 'stlog'] class WMaterialViewSet(ListModelMixin, CustomGenericViewSet):