"""改善行动计划 API — 管理会计OS""" from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from datetime import datetime from typing import Optional from app.database import get_db from app.auth_middleware import require_role, require_auth from app.models import ActionPlan, KPIAlert, KPIDefinition, User import logging logger = logging.getLogger("cma.action_plans") router = APIRouter(prefix="/api/cma/action-plans", tags=["改善行动"], dependencies=[Depends(require_role("ceo", "finance", "business", "it"))], ) def plan_to_dict(p: ActionPlan) -> dict: return { "id": p.id, "alert_id": p.alert_id, "kpi_id": p.kpi_id, "title": p.title, "description": p.description, "assignee": p.assignee, "priority": p.priority, "due_date": p.due_date.isoformat() if p.due_date else None, "status": p.status, "progress": p.progress or 0, "result": p.result, "created_by": p.created_by, "created_at": p.created_at.isoformat() if p.created_at else None, "updated_at": p.updated_at.isoformat() if p.updated_at else None, } @router.get("") def list_plans( status: Optional[str] = None, kpi_id: Optional[int] = None, alert_id: Optional[int] = None, db: Session = Depends(get_db), current_user: User = Depends(require_auth), ): """获取行动计划列表""" query = db.query(ActionPlan).order_by(ActionPlan.created_at.desc()) if status: query = query.filter(ActionPlan.status == status) if kpi_id: query = query.filter(ActionPlan.kpi_id == kpi_id) if alert_id: query = query.filter(ActionPlan.alert_id == alert_id) # business角色只看自己的 if current_user.role == "business": query = query.filter( (ActionPlan.assignee == current_user.username) | (ActionPlan.assignee == current_user.name) ) plans = query.all() result = [] for p in plans: item = plan_to_dict(p) # 附带KPI名称 kpi = db.query(KPIDefinition).filter(KPIDefinition.id == p.kpi_id).first() item["kpi_name"] = kpi.kpi_name if kpi else "未知KPI" result.append(item) return {"data": result} @router.post("") def create_plan( data: dict, db: Session = Depends(get_db), current_user: User = Depends(require_auth), ): """创建改善行动计划""" required = ["title", "kpi_id"] for field in required: if field not in data: raise HTTPException(400, f"缺少必填字段: {field}") plan = ActionPlan( alert_id=data.get("alert_id"), kpi_id=data["kpi_id"], title=data["title"], description=data.get("description"), assignee=data.get("assignee"), priority=data.get("priority", "medium"), due_date=datetime.fromisoformat(data["due_date"]) if data.get("due_date") else None, status="pending", progress=0, created_by=current_user.name or current_user.username, ) db.add(plan) db.commit() db.refresh(plan) return plan_to_dict(plan) @router.put("/{plan_id}") def update_plan( plan_id: int, data: dict, db: Session = Depends(get_db), ): """更新行动计划""" plan = db.query(ActionPlan).filter(ActionPlan.id == plan_id).first() if not plan: raise HTTPException(404, "计划不存在") if "title" in data: plan.title = data["title"] if "description" in data: plan.description = data["description"] if "assignee" in data: plan.assignee = data["assignee"] if "priority" in data: plan.priority = data["priority"] if "due_date" in data: plan.due_date = datetime.fromisoformat(data["due_date"]) if data["due_date"] else None if "status" in data: plan.status = data["status"] if "progress" in data: plan.progress = max(0, min(100, data["progress"])) if "result" in data: plan.result = data["result"] db.commit() db.refresh(plan) return plan_to_dict(plan) @router.delete("/{plan_id}") def delete_plan(plan_id: int, db: Session = Depends(get_db)): """删除行动计划""" plan = db.query(ActionPlan).filter(ActionPlan.id == plan_id).first() if not plan: raise HTTPException(404, "计划不存在") db.delete(plan) db.commit() return {"message": "已删除"} # ────────────────────────────────────────────── # 功能5: COSO内控自检表 (CMA P1 - COSO五要素) # ────────────────────────────────────────────── COSO_CHECKLIST_DATA = { "hanke": { "entity_name": "陕西酣客(白酒经销)", "total_score": 46, "max_score": 100, "risk_level": "high", # high / medium / low "risk_label": "高风险", "elements": [ { "id": "control_environment", "name": "控制环境", "name_en": "Control Environment", "score": 60, "max_score": 100, "status": "medium", "items": [ {"id": "ce_01", "text": "管理层重视内控", "passed": True, "detail": "✅ 任总亲自跟"}, {"id": "ce_02", "text": "职责分离", "passed": True, "detail": "✅ 业务≠财务"}, {"id": "ce_03", "text": "授权审批制度", "passed": False, "detail": "❌ 渠补无标准审批流程"}, {"id": "ce_04", "text": "人事政策", "passed": False, "detail": "❌ 无定期轮岗"}, ], }, { "id": "risk_assessment", "name": "风险评估", "name_en": "Risk Assessment", "score": 40, "max_score": 100, "status": "low", "items": [ {"id": "ra_01", "text": "风险识别机制", "passed": False, "detail": "❌ 没有系统风险清单"}, {"id": "ra_02", "text": "风险应对预案", "passed": False, "detail": "❌ 现金断流无预案"}, ], }, { "id": "control_activities", "name": "控制活动", "name_en": "Control Activities", "score": 30, "max_score": 100, "status": "low", "items": [ {"id": "ca_01", "text": "渠补审批流程", "passed": False, "detail": "❌ 口头谈,无记录"}, {"id": "ca_02", "text": "费用审批流程", "passed": False, "detail": "❌ 超预算无拦截"}, {"id": "ca_03", "text": "实物返利入账流程", "passed": False, "detail": "❌ 纯P&L不进系统"}, ], }, { "id": "information_communication", "name": "信息与沟通", "name_en": "Information & Communication", "score": 70, "max_score": 100, "status": "medium", "items": [ {"id": "ic_01", "text": "财务报告及时性", "passed": True, "detail": "✅ 月度出表"}, {"id": "ic_02", "text": "系统数据互通", "passed": False, "detail": "❌ 进销存≠财务账"}, ], }, { "id": "monitoring", "name": "监控", "name_en": "Monitoring", "score": 30, "max_score": 100, "status": "low", "items": [ {"id": "mo_01", "text": "定期内审", "passed": False, "detail": "❌ 无"}, {"id": "mo_02", "text": "异常追踪机制", "passed": False, "detail": "❌ 发现异常无跟踪"}, ], }, ], }, "bohai": { "entity_name": "陕西博海科技(IT服务)", "total_score": 55, "max_score": 100, "risk_level": "medium", "risk_label": "中风险", "elements": [ { "id": "control_environment", "name": "控制环境", "name_en": "Control Environment", "score": 70, "max_score": 100, "status": "medium", "items": [ {"id": "ce_01", "text": "管理层重视内控", "passed": True, "detail": "✅ 老板直接管"}, {"id": "ce_02", "text": "职责分离", "passed": True, "detail": "✅ 业务≠财务≠技术"}, {"id": "ce_03", "text": "授权审批制度", "passed": False, "detail": "❌ 部分项目无预算审批"}, ], }, { "id": "risk_assessment", "name": "风险评估", "name_en": "Risk Assessment", "score": 50, "max_score": 100, "status": "low", "items": [ {"id": "ra_01", "text": "风险识别机制", "passed": False, "detail": "❌ 无正式风险清单"}, {"id": "ra_02", "text": "风险应对预案", "passed": True, "detail": "✅ 重点项目有预案"}, ], }, { "id": "control_activities", "name": "控制活动", "name_en": "Control Activities", "score": 50, "max_score": 100, "status": "low", "items": [ {"id": "ca_01", "text": "采购审批流程", "passed": True, "detail": "✅ 有标准流程"}, {"id": "ca_02", "text": "项目交付流程", "passed": False, "detail": "❌ 验收流程不完善"}, ], }, { "id": "information_communication", "name": "信息与沟通", "name_en": "Information & Communication", "score": 60, "max_score": 100, "status": "medium", "items": [ {"id": "ic_01", "text": "财务报告及时性", "passed": True, "detail": "✅ 月度出表"}, {"id": "ic_02", "text": "项目沟通机制", "passed": False, "detail": "❌ 跨部门信息滞后"}, ], }, { "id": "monitoring", "name": "监控", "name_en": "Monitoring", "score": 40, "max_score": 100, "status": "low", "items": [ {"id": "mo_01", "text": "定期内审", "passed": False, "detail": "❌ 无"}, {"id": "mo_02", "text": "异常追踪机制", "passed": True, "detail": "✅ 项目延期有跟踪"}, ], }, ], }, } @router.get("/coso-checklist") def get_coso_checklist(entity: str = "hanke"): """COSO内控自检表 - CMA P1 COSO五要素""" data = COSO_CHECKLIST_DATA.get(entity) if not data: data = COSO_CHECKLIST_DATA["hanke"] data["entity_name"] = f"未知实体({entity}),默认返回酣客数据" return data