386 lines
14 KiB
Python
386 lines
14 KiB
Python
|
|
from datetime import datetime
|
||
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
|
from sqlalchemy import select, func
|
||
|
|
from pydantic import BaseModel
|
||
|
|
from app.core.database import get_db
|
||
|
|
from app.core.deps import get_current_user, require_roles
|
||
|
|
from app.models.management import Regulation, Standard, ProcessDoc, EmergencyPlan
|
||
|
|
from app.models.user import User
|
||
|
|
|
||
|
|
router = APIRouter(prefix="/management", tags=["管理体系"])
|
||
|
|
|
||
|
|
|
||
|
|
# ── Pydantic Schemas ──────────────────────────────────────────────────
|
||
|
|
|
||
|
|
class RegulationCreate(BaseModel):
|
||
|
|
title: str
|
||
|
|
category: str | None = None
|
||
|
|
content: str | None = None
|
||
|
|
effective_date: datetime | None = None
|
||
|
|
status: str = "active"
|
||
|
|
attachment_url: str | None = None
|
||
|
|
|
||
|
|
|
||
|
|
class StandardCreate(BaseModel):
|
||
|
|
name: str
|
||
|
|
code: str | None = None
|
||
|
|
type: str | None = None
|
||
|
|
description: str | None = None
|
||
|
|
compliance_status: str = "pending"
|
||
|
|
review_date: datetime | None = None
|
||
|
|
|
||
|
|
|
||
|
|
class ProcessDocCreate(BaseModel):
|
||
|
|
title: str
|
||
|
|
category: str | None = None
|
||
|
|
content: str | None = None
|
||
|
|
version: str = "1.0"
|
||
|
|
approved_by: str | None = None
|
||
|
|
effective_date: datetime | None = None
|
||
|
|
|
||
|
|
|
||
|
|
class EmergencyPlanCreate(BaseModel):
|
||
|
|
title: str
|
||
|
|
scenario: str | None = None
|
||
|
|
steps: list[dict] | None = None
|
||
|
|
responsible_person: str | None = None
|
||
|
|
review_date: datetime | None = None
|
||
|
|
is_active: bool = True
|
||
|
|
|
||
|
|
|
||
|
|
# ── Regulations (规章制度) ────────────────────────────────────────────
|
||
|
|
|
||
|
|
@router.get("/regulations")
|
||
|
|
async def list_regulations(
|
||
|
|
category: str | None = None,
|
||
|
|
status: str | None = None,
|
||
|
|
page: int = Query(1, ge=1),
|
||
|
|
page_size: int = Query(20, ge=1, le=100),
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
user: User = Depends(get_current_user),
|
||
|
|
):
|
||
|
|
query = select(Regulation)
|
||
|
|
if category:
|
||
|
|
query = query.where(Regulation.category == category)
|
||
|
|
if status:
|
||
|
|
query = query.where(Regulation.status == status)
|
||
|
|
count_q = select(func.count()).select_from(query.subquery())
|
||
|
|
total = (await db.execute(count_q)).scalar()
|
||
|
|
query = query.order_by(Regulation.id.desc()).offset((page - 1) * page_size).limit(page_size)
|
||
|
|
result = await db.execute(query)
|
||
|
|
return {
|
||
|
|
"total": total,
|
||
|
|
"items": [_regulation_to_dict(r) for r in result.scalars().all()],
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
@router.post("/regulations")
|
||
|
|
async def create_regulation(
|
||
|
|
data: RegulationCreate,
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
user: User = Depends(require_roles("admin", "energy_manager")),
|
||
|
|
):
|
||
|
|
reg = Regulation(**data.model_dump(), created_by=user.id)
|
||
|
|
db.add(reg)
|
||
|
|
await db.flush()
|
||
|
|
return _regulation_to_dict(reg)
|
||
|
|
|
||
|
|
|
||
|
|
@router.put("/regulations/{reg_id}")
|
||
|
|
async def update_regulation(
|
||
|
|
reg_id: int,
|
||
|
|
data: RegulationCreate,
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
user: User = Depends(require_roles("admin", "energy_manager")),
|
||
|
|
):
|
||
|
|
result = await db.execute(select(Regulation).where(Regulation.id == reg_id))
|
||
|
|
reg = result.scalar_one_or_none()
|
||
|
|
if not reg:
|
||
|
|
raise HTTPException(status_code=404, detail="规章制度不存在")
|
||
|
|
for k, v in data.model_dump(exclude_unset=True).items():
|
||
|
|
setattr(reg, k, v)
|
||
|
|
return _regulation_to_dict(reg)
|
||
|
|
|
||
|
|
|
||
|
|
@router.delete("/regulations/{reg_id}")
|
||
|
|
async def delete_regulation(
|
||
|
|
reg_id: int,
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
user: User = Depends(require_roles("admin", "energy_manager")),
|
||
|
|
):
|
||
|
|
result = await db.execute(select(Regulation).where(Regulation.id == reg_id))
|
||
|
|
reg = result.scalar_one_or_none()
|
||
|
|
if not reg:
|
||
|
|
raise HTTPException(status_code=404, detail="规章制度不存在")
|
||
|
|
await db.delete(reg)
|
||
|
|
return {"message": "已删除"}
|
||
|
|
|
||
|
|
|
||
|
|
# ── Standards (标准规范) ──────────────────────────────────────────────
|
||
|
|
|
||
|
|
@router.get("/standards")
|
||
|
|
async def list_standards(
|
||
|
|
type: str | None = None,
|
||
|
|
compliance_status: str | None = None,
|
||
|
|
page: int = Query(1, ge=1),
|
||
|
|
page_size: int = Query(20, ge=1, le=100),
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
user: User = Depends(get_current_user),
|
||
|
|
):
|
||
|
|
query = select(Standard)
|
||
|
|
if type:
|
||
|
|
query = query.where(Standard.type == type)
|
||
|
|
if compliance_status:
|
||
|
|
query = query.where(Standard.compliance_status == compliance_status)
|
||
|
|
count_q = select(func.count()).select_from(query.subquery())
|
||
|
|
total = (await db.execute(count_q)).scalar()
|
||
|
|
query = query.order_by(Standard.id.desc()).offset((page - 1) * page_size).limit(page_size)
|
||
|
|
result = await db.execute(query)
|
||
|
|
return {
|
||
|
|
"total": total,
|
||
|
|
"items": [_standard_to_dict(s) for s in result.scalars().all()],
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
@router.post("/standards")
|
||
|
|
async def create_standard(
|
||
|
|
data: StandardCreate,
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
user: User = Depends(require_roles("admin", "energy_manager")),
|
||
|
|
):
|
||
|
|
std = Standard(**data.model_dump())
|
||
|
|
db.add(std)
|
||
|
|
await db.flush()
|
||
|
|
return _standard_to_dict(std)
|
||
|
|
|
||
|
|
|
||
|
|
@router.put("/standards/{std_id}")
|
||
|
|
async def update_standard(
|
||
|
|
std_id: int,
|
||
|
|
data: StandardCreate,
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
user: User = Depends(require_roles("admin", "energy_manager")),
|
||
|
|
):
|
||
|
|
result = await db.execute(select(Standard).where(Standard.id == std_id))
|
||
|
|
std = result.scalar_one_or_none()
|
||
|
|
if not std:
|
||
|
|
raise HTTPException(status_code=404, detail="标准规范不存在")
|
||
|
|
for k, v in data.model_dump(exclude_unset=True).items():
|
||
|
|
setattr(std, k, v)
|
||
|
|
return _standard_to_dict(std)
|
||
|
|
|
||
|
|
|
||
|
|
@router.delete("/standards/{std_id}")
|
||
|
|
async def delete_standard(
|
||
|
|
std_id: int,
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
user: User = Depends(require_roles("admin", "energy_manager")),
|
||
|
|
):
|
||
|
|
result = await db.execute(select(Standard).where(Standard.id == std_id))
|
||
|
|
std = result.scalar_one_or_none()
|
||
|
|
if not std:
|
||
|
|
raise HTTPException(status_code=404, detail="标准规范不存在")
|
||
|
|
await db.delete(std)
|
||
|
|
return {"message": "已删除"}
|
||
|
|
|
||
|
|
|
||
|
|
# ── Process Docs (管理流程) ───────────────────────────────────────────
|
||
|
|
|
||
|
|
@router.get("/process-docs")
|
||
|
|
async def list_process_docs(
|
||
|
|
category: str | None = None,
|
||
|
|
page: int = Query(1, ge=1),
|
||
|
|
page_size: int = Query(20, ge=1, le=100),
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
user: User = Depends(get_current_user),
|
||
|
|
):
|
||
|
|
query = select(ProcessDoc)
|
||
|
|
if category:
|
||
|
|
query = query.where(ProcessDoc.category == category)
|
||
|
|
count_q = select(func.count()).select_from(query.subquery())
|
||
|
|
total = (await db.execute(count_q)).scalar()
|
||
|
|
query = query.order_by(ProcessDoc.id.desc()).offset((page - 1) * page_size).limit(page_size)
|
||
|
|
result = await db.execute(query)
|
||
|
|
return {
|
||
|
|
"total": total,
|
||
|
|
"items": [_process_doc_to_dict(d) for d in result.scalars().all()],
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
@router.post("/process-docs")
|
||
|
|
async def create_process_doc(
|
||
|
|
data: ProcessDocCreate,
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
user: User = Depends(require_roles("admin", "energy_manager")),
|
||
|
|
):
|
||
|
|
doc = ProcessDoc(**data.model_dump())
|
||
|
|
db.add(doc)
|
||
|
|
await db.flush()
|
||
|
|
return _process_doc_to_dict(doc)
|
||
|
|
|
||
|
|
|
||
|
|
@router.put("/process-docs/{doc_id}")
|
||
|
|
async def update_process_doc(
|
||
|
|
doc_id: int,
|
||
|
|
data: ProcessDocCreate,
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
user: User = Depends(require_roles("admin", "energy_manager")),
|
||
|
|
):
|
||
|
|
result = await db.execute(select(ProcessDoc).where(ProcessDoc.id == doc_id))
|
||
|
|
doc = result.scalar_one_or_none()
|
||
|
|
if not doc:
|
||
|
|
raise HTTPException(status_code=404, detail="管理流程文档不存在")
|
||
|
|
for k, v in data.model_dump(exclude_unset=True).items():
|
||
|
|
setattr(doc, k, v)
|
||
|
|
return _process_doc_to_dict(doc)
|
||
|
|
|
||
|
|
|
||
|
|
@router.delete("/process-docs/{doc_id}")
|
||
|
|
async def delete_process_doc(
|
||
|
|
doc_id: int,
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
user: User = Depends(require_roles("admin", "energy_manager")),
|
||
|
|
):
|
||
|
|
result = await db.execute(select(ProcessDoc).where(ProcessDoc.id == doc_id))
|
||
|
|
doc = result.scalar_one_or_none()
|
||
|
|
if not doc:
|
||
|
|
raise HTTPException(status_code=404, detail="管理流程文档不存在")
|
||
|
|
await db.delete(doc)
|
||
|
|
return {"message": "已删除"}
|
||
|
|
|
||
|
|
|
||
|
|
# ── Emergency Plans (应急预案) ────────────────────────────────────────
|
||
|
|
|
||
|
|
@router.get("/emergency-plans")
|
||
|
|
async def list_emergency_plans(
|
||
|
|
scenario: str | None = None,
|
||
|
|
is_active: bool | None = None,
|
||
|
|
page: int = Query(1, ge=1),
|
||
|
|
page_size: int = Query(20, ge=1, le=100),
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
user: User = Depends(get_current_user),
|
||
|
|
):
|
||
|
|
query = select(EmergencyPlan)
|
||
|
|
if scenario:
|
||
|
|
query = query.where(EmergencyPlan.scenario == scenario)
|
||
|
|
if is_active is not None:
|
||
|
|
query = query.where(EmergencyPlan.is_active == is_active)
|
||
|
|
count_q = select(func.count()).select_from(query.subquery())
|
||
|
|
total = (await db.execute(count_q)).scalar()
|
||
|
|
query = query.order_by(EmergencyPlan.id.desc()).offset((page - 1) * page_size).limit(page_size)
|
||
|
|
result = await db.execute(query)
|
||
|
|
return {
|
||
|
|
"total": total,
|
||
|
|
"items": [_emergency_plan_to_dict(p) for p in result.scalars().all()],
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
@router.post("/emergency-plans")
|
||
|
|
async def create_emergency_plan(
|
||
|
|
data: EmergencyPlanCreate,
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
user: User = Depends(require_roles("admin", "energy_manager")),
|
||
|
|
):
|
||
|
|
plan = EmergencyPlan(**data.model_dump())
|
||
|
|
db.add(plan)
|
||
|
|
await db.flush()
|
||
|
|
return _emergency_plan_to_dict(plan)
|
||
|
|
|
||
|
|
|
||
|
|
@router.put("/emergency-plans/{plan_id}")
|
||
|
|
async def update_emergency_plan(
|
||
|
|
plan_id: int,
|
||
|
|
data: EmergencyPlanCreate,
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
user: User = Depends(require_roles("admin", "energy_manager")),
|
||
|
|
):
|
||
|
|
result = await db.execute(select(EmergencyPlan).where(EmergencyPlan.id == plan_id))
|
||
|
|
plan = result.scalar_one_or_none()
|
||
|
|
if not plan:
|
||
|
|
raise HTTPException(status_code=404, detail="应急预案不存在")
|
||
|
|
for k, v in data.model_dump(exclude_unset=True).items():
|
||
|
|
setattr(plan, k, v)
|
||
|
|
return _emergency_plan_to_dict(plan)
|
||
|
|
|
||
|
|
|
||
|
|
@router.delete("/emergency-plans/{plan_id}")
|
||
|
|
async def delete_emergency_plan(
|
||
|
|
plan_id: int,
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
user: User = Depends(require_roles("admin", "energy_manager")),
|
||
|
|
):
|
||
|
|
result = await db.execute(select(EmergencyPlan).where(EmergencyPlan.id == plan_id))
|
||
|
|
plan = result.scalar_one_or_none()
|
||
|
|
if not plan:
|
||
|
|
raise HTTPException(status_code=404, detail="应急预案不存在")
|
||
|
|
await db.delete(plan)
|
||
|
|
return {"message": "已删除"}
|
||
|
|
|
||
|
|
|
||
|
|
# ── Compliance Overview ───────────────────────────────────────────────
|
||
|
|
|
||
|
|
@router.get("/compliance")
|
||
|
|
async def compliance_overview(
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
user: User = Depends(get_current_user),
|
||
|
|
):
|
||
|
|
"""合规概览 - count by compliance_status for standards"""
|
||
|
|
result = await db.execute(
|
||
|
|
select(Standard.compliance_status, func.count(Standard.id))
|
||
|
|
.group_by(Standard.compliance_status)
|
||
|
|
)
|
||
|
|
stats = {row[0]: row[1] for row in result.all()}
|
||
|
|
return {
|
||
|
|
"compliant": stats.get("compliant", 0),
|
||
|
|
"non_compliant": stats.get("non_compliant", 0),
|
||
|
|
"pending": stats.get("pending", 0),
|
||
|
|
"in_progress": stats.get("in_progress", 0),
|
||
|
|
"total": sum(stats.values()),
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
# ── Serializers ───────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
def _regulation_to_dict(r: Regulation) -> dict:
|
||
|
|
return {
|
||
|
|
"id": r.id, "title": r.title, "category": r.category,
|
||
|
|
"content": r.content, "effective_date": str(r.effective_date) if r.effective_date else None,
|
||
|
|
"status": r.status, "attachment_url": r.attachment_url,
|
||
|
|
"created_by": r.created_by,
|
||
|
|
"created_at": str(r.created_at) if r.created_at else None,
|
||
|
|
"updated_at": str(r.updated_at) if r.updated_at else None,
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def _standard_to_dict(s: Standard) -> dict:
|
||
|
|
return {
|
||
|
|
"id": s.id, "name": s.name, "code": s.code, "type": s.type,
|
||
|
|
"description": s.description, "compliance_status": s.compliance_status,
|
||
|
|
"review_date": str(s.review_date) if s.review_date else None,
|
||
|
|
"created_at": str(s.created_at) if s.created_at else None,
|
||
|
|
"updated_at": str(s.updated_at) if s.updated_at else None,
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def _process_doc_to_dict(d: ProcessDoc) -> dict:
|
||
|
|
return {
|
||
|
|
"id": d.id, "title": d.title, "category": d.category,
|
||
|
|
"content": d.content, "version": d.version,
|
||
|
|
"approved_by": d.approved_by,
|
||
|
|
"effective_date": str(d.effective_date) if d.effective_date else None,
|
||
|
|
"created_at": str(d.created_at) if d.created_at else None,
|
||
|
|
"updated_at": str(d.updated_at) if d.updated_at else None,
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def _emergency_plan_to_dict(p: EmergencyPlan) -> dict:
|
||
|
|
return {
|
||
|
|
"id": p.id, "title": p.title, "scenario": p.scenario,
|
||
|
|
"steps": p.steps, "responsible_person": p.responsible_person,
|
||
|
|
"review_date": str(p.review_date) if p.review_date else None,
|
||
|
|
"is_active": p.is_active,
|
||
|
|
"created_at": str(p.created_at) if p.created_at else None,
|
||
|
|
"updated_at": str(p.updated_at) if p.updated_at else None,
|
||
|
|
}
|