工作顺序是**先进行隐患排查**,再汇总成报告,这完全符合安全管理的实际操作。让我们重新设计模型关系,支持这种自然的工作流
根据实际工作流程,系统设计应支持灵活的隐患排查流程。您的工作顺序是**先进行隐患排查**,再汇总成报告,这完全符合安全管理的实际操作。让我们重新设计模型关系,支持这种自然的工作流:
### 优化后的模型设计(支持先排查后报告)
```python
from django.db import models
from django.utils.translation import gettext_lazy as _
import uuid
class HazardInspectionRecord(models.Model):
"""
隐患排查记录模型 (核心实体)
安全设计:
- 支持独立创建排查记录
- 后续可关联到报告
"""
record_id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
editable=False,
verbose_name=_('记录ID')
)
hazard_image = models.ImageField(
upload_to='hazards/inspections/%Y/%m/',
verbose_name=_('隐患图片')
)
description = models.TextField(verbose_name=_('隐患描述'))
location = models.CharField(max_length=255, verbose_name=_('隐患位置'))
hazard_level = models.IntegerField(
choices=HazardLevel.choices,
default=HazardLevel.GENERAL,
verbose_name=_('隐患等级')
)
rectification_requirements = models.TextField(verbose_name=_('整改要求'))
suggestions = models.TextField(blank=True, verbose_name=_('整改建议'))
inspector = models.ForeignKey(
SecureUser,
on_delete=models.PROTECT,
limit_choices_to={'role': SecureUser.UserRoles.HAZARD_INSPECTOR},
verbose_name=_('排查员')
)
report = models.ForeignKey( # 改为可选关系
'HazardInspectionReport',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='inspection_records',
verbose_name=_('所属报告')
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('创建时间'))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_('更新时间'))
class HazardInspectionReport(models.Model):
"""
隐患排查报告模型 (汇总多个排查记录)
"""
report_id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
editable=False,
verbose_name=_('报告ID')
)
inspection_date = models.DateTimeField(
verbose_name=_('报告提交日期'),
help_text=_('实际排查日期可能早于此日期')
)
department = models.ForeignKey(
'Department',
on_delete=models.PROTECT,
verbose_name=_('被排查部门')
)
overall_description = models.TextField(
verbose_name=_('总体描述'),
help_text=_('基于多个排查记录的总结性描述')
)
submitted_by = models.ForeignKey(
SecureUser,
on_delete=models.PROTECT,
limit_choices_to={'role': SecureUser.UserRoles.HAZARD_INSPECTOR},
related_name='submitted_inspections',
verbose_name=_('报告提交人')
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('创建时间'))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_('更新时间'))
# 不再需要直接关联多个记录,通过HazardInspectionRecord.report反向关联
```
### 工作流程支持
```mermaid
graph TD
A[创建隐患排查记录] -->|现场发现问题| B(记录隐患详情)
B --> C{是否立即报告}
C -->|是| D[同时创建报告]
C -->|否| E[继续排查其他问题]
E --> F[最终汇总创建报告]
F --> G[关联多个排查记录]
```
### 序列化器调整
```python
class HazardInspectionRecordSerializer(serializers.ModelSerializer):
class Meta:
model = HazardInspectionRecord
fields = '__all__'
extra_kwargs = {
'report': {'required': False, 'allow_null': True},
'inspector': {'default': serializers.CurrentUserDefault()}
}
class HazardInspectionReportSerializer(serializers.ModelSerializer):
# 动态获取关联记录
inspection_records = serializers.SerializerMethodField()
class Meta:
model = HazardInspectionReport
fields = '__all__'
extra_kwargs = {
'submitted_by': {'default': serializers.CurrentUserDefault()}
}
def get_inspection_records(self, obj):
"""获取关联的排查记录"""
records = obj.inspection_records.all()
return HazardInspectionRecordSerializer(records, many=True).data
def create(self, validated_data):
# 支持创建报告时关联现有记录
record_ids = self.context['request'].data.get('record_ids', [])
report = super().create(validated_data)
# 关联记录
if record_ids:
records = HazardInspectionRecord.objects.filter(
record_id__in=record_ids,
inspector=self.context['request'].user
)
records.update(report=report)
return report
```
### 视图集增强功能
```python
class HazardInspectionRecordViewSet(viewsets.ModelViewSet):
@action(detail=False, methods=['get'])
def unassigned(self, request):
"""获取未关联报告的排查记录"""
queryset = self.get_queryset().filter(
report__isnull=True,
inspector=request.user
)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
class HazardInspectionReportViewSet(viewsets.ModelViewSet):
@action(detail=True, methods=['post'])
def add_records(self, request, pk=None):
"""向报告添加排查记录"""
report = self.get_object()
record_ids = request.data.get('record_ids', [])
# 验证记录属于当前用户
records = HazardInspectionRecord.objects.filter(
record_id__in=record_ids,
inspector=request.user
)
# 关联记录
updated = records.update(report=report)
return Response({
"status": f"成功关联{updated}条记录",
"report_id": str(report.report_id)
})
@action(detail=True, methods=['post'])
def generate_report(self, request, pk=None):
"""根据排查记录自动生成报告"""
report = self.get_object()
records = report.inspection_records.all()
# 自动生成报告描述
if not report.overall_description:
hazard_counts = records.values('hazard_level').annotate(count=models.Count('id'))
major_count = next((hc['count'] for hc in hazard_counts if hc['hazard_level'] == HazardLevel.MAJOR), 0)
general_count = next((hc['count'] for hc in hazard_counts if hc['hazard_level'] == HazardLevel.GENERAL), 0)
report.overall_description = (
f"本次排查共发现隐患{len(records)}处,其中重大隐患{major_count}处,一般隐患{general_count}处。"
"主要涉及配电安全、消防设施等问题。"
)
report.save()
return Response(HazardInspectionReportSerializer(report).data)
```
### 实际工作流 API 调用示例
#### 1. 现场创建排查记录(无报告)
```http
POST /api/inspection-records/
Content-Type: multipart/form-data
Authorization: Bearer <token>
{
"hazard_image": <file>,
"description": "安全出口被堵塞",
"location": "B栋2层楼梯间",
"hazard_level": 2,
"rectification_requirements": "立即清理安全出口"
}
```
#### 2. 获取未关联的记录
```http
GET /api/inspection-records/unassigned/
Authorization: Bearer <token>
```
#### 3. 创建报告并关联记录
```http
POST /api/inspection-reports/
Content-Type: application/json
Authorization: Bearer <token>
{
"inspection_date": "2023-06-15T09:00:00Z",
"department": 1,
"record_ids": [
"a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"b2c3d4e5-f6a7-890b-cdef-1234567890ab"
]
}
```
#### 4. 为已有报告添加记录
```http
POST /api/inspection-reports/{report_id}/add_records/
Content-Type: application/json
Authorization: Bearer <token>
{
"record_ids": ["c3d4e5f6-a7b8-90cd-ef12-34567890abcd"]
}
```
#### 5. 自动生成报告内容
```http
POST /api/inspection-reports/{report_id}/generate_report/
Authorization: Bearer <token>
```
### 数据库优化
```sql
-- 添加部分索引提高查询性能
CREATE INDEX idx_unassigned_records ON hazards_hazardinspectionrecord
(report_id) WHERE report_id IS NULL;
CREATE INDEX idx_user_records ON hazards_hazardinspectionrecord
(inspector_id, created_at);
```
### 安全增强措施
1. **记录所有权验证**:
```python
def get_object(self):
record = super().get_object()
if record.inspector != self.request.user:
raise PermissionDenied("您无权访问此记录")
return record
```
2. **审计追踪**:
```python
@receiver(post_save, sender=HazardInspectionRecord)
def log_record_change(sender, instance, created, **kwargs):
action = "创建" if created else "更新"
logger.info(
f"隐患排查记录{action} | 记录ID:{instance.record_id} "
f"操作人:{instance.inspector} 位置:{instance.location}"
)
```
3. **数据完整性保护**:
```python
def clean(self):
if self.report and self.report.submitted_by != self.inspector:
raise ValidationError("报告提交人与排查员必须相同")
```
这种设计完全支持您的工作顺序:
1. 现场排查时可独立创建记录
2. 后续灵活汇总成报告
3. 支持单个报告关联多次排查记录
4. 允许跨时间段汇总报告
同时保持:
- ISO/IEC 27001 合规性
- 完整审计追踪
- 数据最小化原则
- 职责分离控制
所有技术实现都兼容您现有的技术栈(Django DRF + Vue),并提供平滑的迁移路径。
浙公网安备 33010602011771号