diff --git a/apps/dpm/__init__.py b/apps/dpm/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/dpm/admin.py b/apps/dpm/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/apps/dpm/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/dpm/apps.py b/apps/dpm/apps.py new file mode 100644 index 00000000..456386e9 --- /dev/null +++ b/apps/dpm/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class DpmConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'dpm' + verbose_name = '双重预防' \ No newline at end of file diff --git a/apps/dpm/migrations/__init__.py b/apps/dpm/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/dpm/models.py b/apps/dpm/models.py new file mode 100644 index 00000000..6ae90017 --- /dev/null +++ b/apps/dpm/models.py @@ -0,0 +1,155 @@ +from django.db import models +from apps.utils.models import CommonBModel, CommonAModel +from django_celery_beat.models import PeriodicTask + +# Create your models here. +class RiskPoint(CommonBModel): + """ + 风险点表 + """ + R_LEVEL = ( + (10, '低风险'), + (20, '一般风险'), + (30, '较大风险'), + (40, '重大风险') + ) + R_TYPE = ( + (10, '作业活动类'), + (20, '设备设施类') + ) + R_M_LEVEL = ( + (10, '岗位级'), + (20, '班组级'), + (30, '部门级'), + (40, '公司级') + ) + name = models.CharField('名称', max_length=40) + area = models.ForeignKey('am.area', on_delete=models.CASCADE, verbose_name='所在区域', related_name='riskpoint_area', null=True, blank=True) + place = models.CharField('具体地点', max_length=100, null=True, blank=True) + level = models.PositiveSmallIntegerField('风险点等级', default=10, choices=R_LEVEL) + type = models.PositiveSmallIntegerField('风险点类型', default=10, choices=R_TYPE) + manage_level = models.PositiveSmallIntegerField('管控层级', default=10, choices=R_M_LEVEL) + dept_res = models.ForeignKey('system.dept', on_delete=models.SET_NULL, verbose_name='责任部门', related_name='riskpoint_dept_res', null=True, blank=True) + user_res = models.ForeignKey('system.user', on_delete=models.CASCADE, verbose_name='责任人', related_name='hazard_user_discover', null=True, blank=True) + +class Risk(CommonBModel): + """ + 风险表 + """ + name = models.TextField('项目/步骤') + level = models.PositiveSmallIntegerField('风险等级', default=10, choices=RiskPoint.R_LEVEL) + standard = models.TextField('检查标准', null=True, blank=True) + hazard = models.TextField('危害因素', null=True, blank=True) + results = models.ManyToManyField('system.dictionary', verbose_name='可能后果', related_name='risk_results', null=True) + sort = models.PositiveSmallIntegerField('排序', default=1) + measure_tech = models.TextField('工程技术措施', null=True, blank=True) + measure_manage = models.TextField('管理措施', null=True, blank=True) + measure_edu = models.TextField('培训教育措施', null=True, blank=True) + measure_protect = models.TextField('个体防护措施', null=True, blank=True) + measure_emer = models.TextField('应急处置措施', null=True, blank=True) + riskl = models.DecimalField(max_digits=6, decimal_places=1, null=True, blank=True) + riske = models.DecimalField(max_digits=6, decimal_places=1, null=True, blank=True) + riskc = models.DecimalField(max_digits=6, decimal_places=1, null=True, blank=True) + riskd = models.DecimalField(max_digits=6, decimal_places=1, null=True, blank=True) + riskpoint = models.ForeignKey(RiskPoint, on_delete=models.CASCADE, verbose_name='所属风险点') + + +class CheckTaskSet(CommonBModel): + """ + 检查任务派发设置 + """ + riskpoint = models.ForeignKey(RiskPoint, verbose_name='关联风险点', related_name='ctask_riskpoint', on_delete=models.SET_NULL, null=True, blank=True) + note = models.TextField('派发备注', null=True, blank=True) + expire = models.PositiveSmallIntegerField('执行有效期(h)', default=0, help_text='0代表不设限制直到下个任务派发') + user_duty = models.ForeignKey('system.user', verbose_name='指派人', on_delete=models.SET_NULL, null=True, blank=True) + myschedule = models.ForeignKey('system.myschedule', verbose_name='周期', on_delete=models.SET_NULL, null=True, blank=True) + periodictask = models.ForeignKey(PeriodicTask, verbose_name='关联定时任务', on_delete=models.CASCADE, related_name='checktask_periodictask') + + +class CheckWork(CommonBModel): + """ + 检查工作 + """ + CW_TYPE = ( + (10, '手动'), + (20, '派发') + ) + name = models.CharField('名称', max_length=40) + description = models.TextField('工作描述', null=True, blank=True) + type = models.PositiveSmallIntegerField('任务类型', default=10, choices=CW_TYPE) + category = models.ForeignKey('system.dictionary', on_delete=models.CASCADE, verbose_name='工作分类', null=True, blank=True) + time_start = models.DateTimeField('开启时间') + time_end = models.DateTimeField('关闭时间') + enabled = models.BooleanField('是否可用', default=True) + user_duty = models.ForeignKey('system.user', verbose_name='指派人', on_delete=models.SET_NULL, null=True, blank=True) + riskpoint = models.ForeignKey(RiskPoint, verbose_name='关联风险点', related_name='cwork_riskpoint', on_delete=models.SET_NULL, null=True, blank=True) + note = models.TextField('检查备注', null=True, blank=True) + time_check = models.BooleanField('最后检查时间', default=True) + checktaskset = models.ForeignKey(CheckTaskSet, verbose_name='关联派发任务', on_delete=models.CASCADE, null=True, blank=True) + + +class Hazard(CommonBModel): + """ + 事故隐患表 + """ + H_HARM = ( + (10, '无伤害'), + (20, '轻微伤'), + (30, '轻伤'), + (40, '重伤'), + (50, '死亡') + ) + H_LEVEL = ( + (10, '无隐患'), + (20, '一般隐患'), + (30, '重大隐患') + ) + H_STATE = ( + (10, '待提交'), + (20, '流程中'), + (30, '已结束') + ) + number = models.CharField('编号', max_length=20, null=True, blank=True) + checkwork = models.ForeignKey(CheckWork, verbose_name='关联检查工作', null=True, blank=True) + state = models.PositiveSmallIntegerField('隐患流程状态', default=10, choices=H_STATE) + time_discover = models.DateTimeField('发现时间') + dept_res = models.ForeignKey('system.dept', on_delete=models.SET_NULL, verbose_name='责任部门', related_name='hazard_dept_res', null=True, blank=True) + user_discover = models.ForeignKey('system.user', on_delete=models.CASCADE, verbose_name='发现人', related_name='hazard_user_discover') + area = models.ForeignKey('am.area', on_delete=models.CASCADE, verbose_name='所在区域', null=True, blank=True) + place = models.CharField('具体地点', max_length=100, null=True, blank=True) + level = models.PositiveSmallIntegerField('隐患等级', default=10, choices=H_LEVEL) + harm_access = models.PositiveSmallIntegerField('伤害评估', default=10, choices=H_HARM) + category = models.ForeignKey('system.dictionary', on_delete=models.CASCADE, verbose_name='隐患类别', null=True, blank=True) + description = models.TextField('隐患描述', null=True, blank=True) + reason = models.TextField('原因分析', null=True, blank=True) + correct_measure = models.TextField('整改措施', null=True, blank=True) + correct_solution = models.ForeignKey('system.file', verbose_name='整改方案', null=True, blank=True) + correct_deadline = models.DateTimeField('整改期限', null=True, blank=True) + photos = models.ManyToManyField('system.file', verbose_name='现场照片', related_name='hazard_photos', null=True) + correct_description = models.TextField('整改描述', null=True, blank=True) + photos_done = models.ManyToManyField('system.file', verbose_name='整改后照片', related_name='hazard_photos_done', null=True) + dept_correct = models.ForeignKey('system.dept', on_delete=models.CASCADE, verbose_name='整改部门', related_name='hazard_dept_correct', null=True, blank=True) + user_correct = models.ForeignKey('system.user', on_delete=models.SET_NULL, verbose_name='整改责任人', related_name='hazard_user_correct', null=True, blank=True) + correct_time = models.DateTimeField('整改完成时间', null=True, blank=True) + user_access = models.ForeignKey('system.user', on_delete=models.CASCADE, verbose_name='评估人', related_name='hazard_user_access') + opinion_access = models.TextField('评估意见', null=True, blank=True) + user_check = models.ForeignKey('system.user', on_delete=models.CASCADE, verbose_name='复查人', related_name='hazard_user_check') + opinion_check = models.TextField('复查意见', null=True, blank=True) + user_audit = models.ForeignKey('system.user', on_delete=models.CASCADE, verbose_name='审核人', related_name='hazard_user_audit') + opinion_audit = models.TextField('审核意见', null=True, blank=True) + ticket = models.ForeignKey('wf.ticket', on_delete=models.SET_NULL, verbose_name='关联工单', related_name='hazard_ticket', null=True, blank=True) + + +class CheckItem(CommonAModel): + """ + 检查工作-隐患关联表 + """ + CITEM_RESULT = ( + (10, '未检查'), + (20, '合格'), + (30, '不合格') + ) + checkwork = models.ForeignKey(CheckWork, verbose_name='关联检查工作', related_name='citem_checkwork', on_delete=models.CASCADE) + risk = models.ForeignKey(Risk, verbose_name='关联风险', related_name='citem_risk', on_delete=models.SET_NULL, null=True, blank=True) + hazard = models.ForeignKey(Hazard, verbose_name='关联隐患', related_name='citem_hazard', on_delete=models.SET_NULL, null=True, blank=True) + result = models.PositiveSmallIntegerField('检查结果', default=10) diff --git a/apps/dpm/serializers.py b/apps/dpm/serializers.py new file mode 100644 index 00000000..ab85d9bc --- /dev/null +++ b/apps/dpm/serializers.py @@ -0,0 +1,128 @@ +import json +from apps.dpm.models import CheckTaskSet, CheckWork, RiskPoint, Risk +from rest_framework import serializers +from apps.utils.serializers import CustomModelSerializer +from apps.utils.constants import EXCLUDE_FIELDS +from apps.system.models import Dictionary +from django_celery_beat.models import PeriodicTask +import time + + +class RiskPointCreateUpdateSerializer(CustomModelSerializer): + class Meta: + model = RiskPoint + exclude = EXCLUDE_FIELDS + + +class RiskPointSerializer(CustomModelSerializer): + class Meta: + model = RiskPoint + fields = '__all__' + + +class RiskCreateUpdateSerializer(CustomModelSerializer): + class Meta: + model = Risk + exclude = EXCLUDE_FIELDS + ['riskd'] + + def save(self, **kwargs): + obj = super().save(**kwargs) + if obj.riskl and obj.riske and obj.riskc: + obj.riskd = obj.riskl * obj.riske * obj.riskc + if obj.riskd >= 320: + obj.level = 40 + elif 320 > obj.riskd >= 160: + obj.level = 30 + elif 160 > obj.riskd >= 70: + obj.level = 20 + else: + obj.level = 10 + obj.save() + self.cal_riskpoint_level(obj) + return obj + + @staticmethod + def cal_riskpoint_level(risk): + """ + 计算风险点风险等级 + """ + riskpoint = risk.riskpoint + levels = Risk.objects.filter(riskpoint=riskpoint).values_list('level', flat=True) + riskpoint.level = max(levels) + riskpoint.save() + RiskCreateUpdateSerializer.cal_area_level(riskpoint) + + @staticmethod + def cal_area_level(riskpoint): + """ + 计算区域风险等级 + """ + area = riskpoint.area + if area: + levels = RiskPoint.objects.filter(area=area).values_list('level', flat=True) + area.level = max(levels) + area.save() + + +class RiskSerializer(CustomModelSerializer): + class Meta: + model = Risk + fields = '__all__' + + +class CheckTaskSetCreateUpdateSerializer(CustomModelSerializer): + class Meta: + model = CheckTaskSet + exclude = EXCLUDE_FIELDS + ['periodictask'] + + def save(self, **kwargs): + ins = super().save(**kwargs) + if ins.periodictask: + pobj = ins.periodictask + pobj.name = 'checktask_' + time.strftime('%Y%m%d%H%M%S') + pobj.task = 'apps.dpm.dispath_checkwork_task' + pobj.kwargs = json.dumps({'checktaskset': ins.id}) + else: + pobj = PeriodicTask() + sch = ins.myschedule + if sch.crontab: + pobj.crontab = sch.crontab + elif sch.interval: + pobj.interval = sch.interval + pobj.save() + ins.periodictask = pobj + ins.save() + return ins + + +class CheckTaskSetSerializer(CustomModelSerializer): + enabled = serializers.BooleanField(source='periodictask.enabled', read_only=True) + last_run_at = serializers.DateTimeField(source='periodictask.last_run_at', read_only=True) + total_run_count = serializers.IntegerField(source='periodictask.total_run_count', read_only=True) + + class Meta: + model = CheckTaskSet + fields = '__all__' + + +class CheckWork_10_CreateUpdateSerializer(CustomModelSerializer): + category = serializers.PrimaryKeyRelatedField( + label='检查分类', queryset=Dictionary.objects.filter(type__code='checkwork_category')) + + class Meta: + model = CheckWork + fields = ['name', 'description', 'category', 'time_start', 'time_end', 'enabled'] + + def save(self, **kwargs): + kwargs['type'] = 10 # 创建/编辑只有手动类型 + return super().save(**kwargs) + + +class CheckWorkSerializer(CustomModelSerializer): + category_name = serializers.CharField(source='category.name', read_only=True) + user_duty_name = serializers.CharField(source='user_duty.name', read_only=True) + riskpoint_name = serializers.CharField(source='riskpoint.name', read_only=True) + + class Meta: + model = CheckWork + fields = '__all__' diff --git a/apps/dpm/tasks.py b/apps/dpm/tasks.py new file mode 100644 index 00000000..12f7a7a9 --- /dev/null +++ b/apps/dpm/tasks.py @@ -0,0 +1,30 @@ +from apps.utils.tasks import CustomTask +from celery import shared_task +from apps.dpm.models import CheckTaskSet, CheckWork +from django.utils import timezone +from datetime import timedelta + + +@shared_task(base=CustomTask) +def dispath_checkwork_task(checktaskset: str): + cts = CheckTaskSet.objects.get(id=checktaskset) + cw = CheckWork() + cw.type = 20 + cw.checktaskset = cts + cw.name = '风险点检查(自动派发)' + now = timezone.now() + cw.time_start = now + if cts.expire: + cw.time_end = now + timedelta(hours=cts.expire) + cw.user_duty = cts.user_duty + cw.riskpoint = cts.riskpoint + cw.note = cts.note + cw.save() + # 发送通知 + pass + + +@shared_task(base=CustomTask) +def expire_checkwork(): + now = timezone.now() + CheckWork.objects.filter(expire__gte=now).update(enabled=False) diff --git a/apps/dpm/tests.py b/apps/dpm/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/apps/dpm/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/dpm/urls.py b/apps/dpm/urls.py new file mode 100644 index 00000000..ee1ca829 --- /dev/null +++ b/apps/dpm/urls.py @@ -0,0 +1,13 @@ +from apps.dpm.views import RiskPointViewSet, RiskViewSet +from django.urls import path, include +from rest_framework.routers import DefaultRouter + +API_BASE_URL = 'api/dpm/' +HTML_BASE_URL = 'dpm/' +# cache_areas_info() +router = DefaultRouter() +router.register('riskpoint', RiskPointViewSet, basename='riskpoint') +router.register('risk', RiskViewSet, basename='risk') +urlpatterns = [ + path(API_BASE_URL, include(router.urls)), +] diff --git a/apps/dpm/views.py b/apps/dpm/views.py new file mode 100644 index 00000000..51c0fa30 --- /dev/null +++ b/apps/dpm/views.py @@ -0,0 +1,71 @@ +from apps.dpm.serializers import RiskPointCreateUpdateSerializer, RiskPointSerializer, RiskCreateUpdateSerializer, RiskSerializer, CheckTaskSetCreateUpdateSerializer, CheckTaskSetSerializer, CheckWork_10_CreateUpdateSerializer, CheckWorkSerializer +from apps.utils.viewsets import CustomModelViewSet +from apps.dpm.models import CheckTaskSet, CheckWork, RiskPoint, Risk +from rest_framework.decorators import action +from rest_framework.response import Response +from django.db import transaction + + +# Create your views here. +class RiskPointViewSet(CustomModelViewSet): + queryset = RiskPoint.objects.all() + create_serializer_class = RiskPointCreateUpdateSerializer + update_serializer_class = RiskPointCreateUpdateSerializer + serializer_class = RiskPointSerializer + select_related_fields = ['area', 'dept_res', 'user_res'] + search_fields = ['name'] + filterset_fields = ['area', 'type', 'level', 'manage_level'] + + +class RiskViewSet(CustomModelViewSet): + queryset = Risk.objects.all() + create_serializer_class = RiskCreateUpdateSerializer + update_serializer_class = RiskCreateUpdateSerializer + serializer_class = RiskSerializer + select_related_fields = ['riskpoint'] + prefetch_related_fields = ['results'] + search_fields = ['name'] + filterset_fields = ['results'] + + +class CheckTaskSetViewSet(CustomModelViewSet): + queryset = CheckTaskSet.objects.all() + create_serializer_class = CheckTaskSetCreateUpdateSerializer + update_serializer_class = CheckTaskSetCreateUpdateSerializer + serializer_class = CheckTaskSetSerializer + select_related_fields = ['periodictask'] + search_fields = ['note'] + filterset_fields = ['periodictask__enabled'] + + @action(methods=['put'], detail=True, perms_map={'put': 'checktaskset.update'}) + def toggle(self, request, pk=None): + """修改启用禁用状态 + + 修改启用禁用状态 + """ + obj = self.get_object() + periodictask = obj.periodictask + periodictask.enabled = False if periodictask.enabled else True + periodictask.save() + return Response() + + def perform_destroy(self, instance): + with transaction.atomic(): + periodictask = instance.periodictask + instance.delete() + periodictask.enabled = False + periodictask.save() + + +class CheckWorkViewSet(CustomModelViewSet): + queryset = CheckWork.objects.all() + create_serializer_class = CheckWork_10_CreateUpdateSerializer + update_serializer_class = CheckWork_10_CreateUpdateSerializer + serializer_class = CheckWorkSerializer + search_fields = ['name', 'description'] + select_related_fields = ['category', 'user_duty', 'riskpoint'] + filterset_fields = ['type', 'category', 'riskpoint'] + + +class HazardViewSet(CustomModelViewSet): + pass