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, }